Logika Hoare a z jawnymi aktualizacjami stanu Instytut Informatyki, Uniwersytet Wrocławski 25 października 2010
Plan prezentacji 1 Wprowadzenie 2 Logika Hoare a Floyda 3 4 Prezentacja programu KeY Hoare 5 Informacje końcowe
Gramatyka języka while P rogram ::= (Statement)? Statement ::= EmptyStatement AssignmentStatement CompoundStatement ConditionalStatement LoopStatement EmptyStatement ::= ; AssignStatement ::= Location = Expression ; CompoundStatement ::= Statement Statement ConditionalStatement ::= if ( BooleanExp ) { Statement } else { Statement } LoopStatement ::= while ( BooleanExp ) { Statement } Expression ::= BooleanExp IntExp BooleanExp ::= IntExp ComparisonOp IntExp IntExp == IntExp BooleanExp BooleanOp BooleanExp!BooleanExp Location true false IntExp ::= IntExp IntOp IntExp Z Location ComparisonOp ::= < <= >= > BooleanOp ::= & == IntOp ::= * / % + -
Logika Hoare a Floyda Zwana także logiką Hoare a lub semantyką aksjomatyczną. System formalny służący do wnioskowania o poprawności programów. Wprowadzony w roku 1969 przez sir Charlesa Antony ego Richarda Hoare a (wynalazcę Quicksorta, laureata Nagrody Turinga w 1980 r.), idea została wzięta z pracy z 1967 r. Roberta W Floyda (twórcy algorytmu Floyda Warshalla, laureata Nagrody Turinga w 1978 r.) dotyczącej analizy schematów blokowych.
Semantyka formalna Pamięć Pamięć utożsamiamy z funkcją przypisującą adresom zmiennych ich wartości. Bieżący stan pamięci możemy opisać jako natomiast zbiór stanów pamięci to π : Location (Z {true, false}), Π = (Z {true, false}) Location. Program W naszym rozumieniu, programy są częściowymi funkcjami modyfikującymi pamięć: [σ ] : Π Π takimi, że dom([σ ]) = {π Π program σ uruchomiony w stanie π się zatrzyma}.
Naturalna semantyka operacyjna Relacja przejścia Relacja przejścia naturalnej semantyki operacyjnej określa, że jeśli pewien program σ uruchomimy w pamięci π to otrzymamy pamięć π. σ, π π, gdzie : (Π Π) Π Π. Notację tą rozszerzamy na ewaluację wyrażeń arytmetycznych oraz boolowskich σ, e i n, σ, e b b, gdzie e i : IntExp, n Z gdzie e b : BooleanExp, b {true, false}
Reguły naturalnej semantyki operacyjnej skip ;, π π e, π v assignment x = e;, π π[x/v] composition s 1, π π s 2, π π s 1 s 2, π π conditionalt b, π true s 1, π π if(b){s 1 }else{s 2 }, π π conditionalf b, π false s 2, π π if(b){s 1 }else{s 2 }, π π loopt b, π true s, π π while(b){s}, π π while(b){s}, π π b, π false loopf while(b){s}, π π
Formuły logiki Hoare a Trójki Hoare a Formuły logiki Hoare a przedstawiamy w postaci trójek {ϕ} σ {ψ} gdzie ϕ i ψ są zamkniętymi formułami pierwszego rzędu, zaś σ jest programem. Prawdziwość formuł Formuła jest prawdziwa w sensie częściowej poprawności (co zapisujemy = {ϕ}σ{ψ}), jeśli σ : Π Π. π, π : Π. π = ϕ σ, π π π = ψ. Formuła jest prawdziwa w sensie całkowitej poprawności (co zapisujemy = [ϕ]σ[ψ]), jeśli σ : Π Π. π : Π. π : Π. π = ϕ σ, π π π = ψ.
Reguły logiki Hoare a skip {P } ; {P } assignment {P [x/e]} x = e; {P } {P } s 1 {R} {R} s 2 {Q} composition {P } s 1 s 2 {Q} {P & b = true} s 1 {Q} {P & b = false} s 2 {Q} conditional {P } if(b){s 1 }else{s 2 } {Q} loop {I & b = true} s {I} {I} while(b){s} {I & b = false} P > Q {Q} s {R} weakeningleft {P } s {R} weakeningright {P } s {Q} Q > R {P } s {R} oracle P 1 1 poprawna formuła pierwszego rzędu
Reguły logiki Hoare a skip {P } ; {P } assignment {P [x/e]} x = e; {P } {P } s 1 {R} {R} s 2 {Q} composition {P } s 1 s 2 {Q} {P & b = true} s 1 {Q} {P & b = false} s 2 {Q} conditional {P } if(b){s 1 }else{s 2 } {Q} loop {I & b = true} s {I} {I} while(b){s} {I & b = false} P > Q {Q} s {R} weakeningleft {P } s {R} weakeningright {P } s {Q} Q > R {P } s {R} oracle P 1 loop T {I & b = true & dec = dec } s {I & dec 0 & dec < dec } {I} while(b){s} {I & b = false} 1 poprawna formuła pierwszego rzędu
Dedukcja asercji {y >= 0} z = 0; n = y; while (n > 0) { z = z + x; n = n - 1; } {z = x y}
Dedukcja asercji {y >= 0} z = 0; {y >= 0 & z = 0} n = y; while (n > 0) { } z = z + x; n = n - 1; } {z = x y}
Dedukcja asercji {y >= 0} z = 0; {y >= 0 & z = 0} n = y; {y >= 0 & z = 0 & n = y} while (n > 0) { } z = z + x; n = n - 1; } {z = x y}
Dedukcja asercji {y >= 0} z = 0; {y >= 0 & z = 0} n = y; {y >= 0 & z = 0 & n = y} {n >= 0 & z = x (y n) } while (n > 0) { z = z + x; } n = n - 1; {n >= 0 & z = x (y n) } {z = x y}
Dedukcja asercji {y >= 0} z = 0; {y >= 0 & z = 0} n = y; {y >= 0 & z = 0 & n = y} {n >= 0 & z = x (y n) } while (n > 0) { {n >= 0 & z = x (y n) & n > 0 } z = z + x; n = n - 1; {n >= 0 & z = x (y n) } } {n >= 0 & z = x (y n) &!n > 0} {z = x y}
Dedukcja asercji {y >= 0} z = 0; {y >= 0 & z = 0} n = y; {y >= 0 & z = 0 & n = y} {n >= 0 & z = x (y n) } while (n > 0) { {n >= 0 & z = x (y n) & n > 0 } z = z + x; {n 1 >= 0 & z = x (y (n 1)) } n = n - 1; {n >= 0 & z = x (y n) } } {n >= 0 & z = x (y n) &!n > 0} {z = x y}
Dedukcja asercji {y >= 0} z = 0; {y >= 0 & z = 0} n = y; {y >= 0 & z = 0 & n = y} {n >= 0 & z = x (y n) } while (n > 0) { {n >= 0 & z = x (y n) & n > 0 } {n 1 >= 0 & z + x = x (y (n 1)) } z = z + x; {n 1 >= 0 & z = x (y (n 1)) } n = n - 1; {n >= 0 & z = x (y n) } } {n >= 0 & z = x (y n) &!n > 0} {z = x y}
Dedukcja asercji {y >= 0} z = 0; {y >= 0 & z = 0} n = y; {y >= 0 & z = 0 & n = y} {n >= 0 & z = x (y n) & n = n } while (n > 0) { {n >= 0 & z = x (y n) & n > 0 & n = n } {n 1 >= 0 & z + x = x (y (n 1)) & n = n } z = z + x; {n 1 >= 0 & z = x (y (n 1)) & n 1 < n } n = n - 1; {n >= 0 & z = x (y n) & n < n & n >= 0} } {n >= 0 & z = x (y n) &!n > 0} {z = x y}
Najsłabsze warunki wstępne Definicja Najsłabszy warunek wstępny dla programu σ i formuły ψ (co oznaczamy jako wp(σ, ψ)) jest to formuła ϕ taka, że ( = {ϕ} σ {ψ} ) ( γ. = {γ} σ {ψ} (γ ϕ)) wp(;, ψ) wp(x = e;, ψ) wp(s 1 s 2, ψ) wp(if(b){s 1 }else{s 2 }, ψ) wp(while(b){s}, ψ) = ψ = ψ[x/e] = wp(s 1, wp(s 2, ψ)) = (b wp(s 1, ψ)) ( b wp(s 2, ψ)) = ( b ψ) (b wp(s, wp(while(b){s}, ψ)))
Logika Hoare a z jawnymi aktualizacjami stanu Wykorzystywana w programie KeY Hoare tak, aby mogła stanowić wsparcie dla kursów nauczających formalnej weryfikacji programów.
Dlaczego modyfikować logikę Hoare a Powody Ze względu na regułę dla przypisania, zazwyczaj explicite wyznacza się najsłabszy warunek wstępny, a potem przeprowadza się wnioskowanie wstecz co jest nieintuicyjne. Reguła dla złożenia rozdziela dowód i wymaga określenia pośredniego stanu, często również stosując wnioskowanie od tyłu. Ręczne tworzenie dowodów (nawet dla małych programów) jest żmudne i często obarczone błędami takie dowody nie będą poprawne dla automatycznej weryfikacji. Zmiana formuły wymaga użycia reguł wnioskowania dla formuł i ponownego jej napisania. Nie jest łatwo przypisać węzeł w drzewie dowodu konkretnemu miejscu w programie.
Aktualizacje stanu Definicja Aktualizacją jest wyrażenie postaci: Location := F OLT erm a. Jeśli U i V są aktualizacjami stanu to są nimi też: U, V (aktualizacja szeregowa) i U V (aktualizacja równoległa). a term formuły pierwszego rzędu Aktualizacje równoległe Niech U = l 1 := t 1... l m := t m i załóżmy, że znajdujemy się w stanie obliczeń π. Wtedy aktualizacje zmienią stan na π U taki, że { π(l) jeśli l / {l1,..., l π U = m } t k jeśli l = l k oraz l / {l k+1,..., l m } Lemat Dla każdej aktualizacji U istnieje aktualizacja równoległa V taka, że U i V są równoważne (tzn. dla każdego stanu π zachodzi π U = π V ).
Aktualizacje stanu c.d. Lemat Dla każdych aktualizacji U i x := t aktualizacje U, x := t i U x := U(t) są równoważne. Trójki Hoare a z aktualizacjami stanu Trójki takie zamieniają się w czwórki i mają postać {ϕ} [U] σ {ψ}, gdzie ϕ, ψ, σ są takie jak przy logice Hoare a, zaś U jest aktualizacją nad sygnaturą ϕ i σ. Semantyka takiej czwórki jest następująca: Dla każdego stanu π spełniającego ϕ, jeśli uruchomimy program σ w stanie π U wtedy, jeśli się zakończy, to stan końcowy będzie spełniał warunek ψ.
Reguły logiki Hoare a ze aktualizacjami stanu exit assignment P U(Q) {P } [U] {Q} {P } [U, x := e] s {Q} {P } [U] x = e; s {Q} skip {P } [U] s {Q} {P } [U] ; s {Q} {P & U(b = true)} [U] s 1 s {Q} {P & U(b = false)} [U] s 2 s {Q} conditional {P } [U] if(b){s 1 }else{s 2 }s {Q} loop P U(I) {I & b = true} [] s 1 {I} {I & b = false} [] s {Q} {P } [U] while(b){s 1 }s {Q}
Reguły zmian stanu Niech x będzie zmienną programu, t termem logiki pierwszego rzędu, P równoległą aktualizacją postaci l 1 := t 1,..., l m := t m, y zmienną logiczną, F n-arną funkcją, spójnikiem logicznym, zaś λ kwantyfikatorem. U, x := t = U x { := U(t) x jeśli l / {l1,..., l P (x) = m } t k jeśli x = l k oraz l / {l k+1,..., l m } U(y) = y U(F (t 1,..., t m )) = F (U(t 1 ),..., U(t n )) U(P Q) = U(P ) U(Q)) U(λy. P ) = λy. U(P ), y / free(u)
Prezentacja programu KeY Hoare Środowisko pozwalające na dowodzenie poprawności programów (częściowej, całkowitej oraz ze względu na pesymistyczny czas działania) napisanych w podzbiorze języka Java.
Materiały Strona programu http://www.key-project.org/download/hoare Bezpośrednie odnośniki Automatyczny instalator : http://www.key-project.org/download/hoare/download/ webstart/key-hoare.jnlp Tutorial: http://www.key-project.org/download/hoare/students.pdf Przykłady: http://www.key-project.org/download/hoare/download/ examples-0.1.9.zip
Interfejs Zarządzanie plikami KeY Hoare akceptuje pliki z rozszerzeniem.key (zwyczajowo zawierające jedynie specyfikację problemu) oraz.proof w których znajduje się dodatkowo dowód (lub jego część). Program pozwala również na zapisywanie aktualnie przeprowadzanego dowodu w pliku.proof. Najważniejsze składowe Po lewej w zakładce Proof znajduje się drzewo dowodu w którym możemy oglądać już wykonane kroki oraz wybierać zadania do udowodnienia (oznaczone jako OPEN GOAL i pokolorowane na czerwono). Główne okno zatytułowane jest Inner Node jeśli oglądamy fragment odpowiadający już wykonanemu krokowi dowodu lub Current Goal wtedy wyświetlany jest cel, który aktualnie wymaga udowodnienia.
Dowodzenie Aplikacja reguł logiki Hoare a Jeśli cel jest trójką Hoare a z aktualizacjami stanu, czyli ma formę {...} [...]... {...} wtedy należy najechać kursorem nad kwadratowe nawiasy i po wciśnięciu prawego przycisku wybrać z menu odpowiednią regułę do zastosowania (zawsze będzie dostępna tylko jedna z wymienionych): assignment, exit, skip, conditional lub Loop Invariant. W przypadku pętli pojawi się dodatkowe okno w którym należy podać jej niezmiennik. Gdy dowodzona jest całkowita poprawność, pokaże się także drugie okno, przeznaczone na term stanowiący miarę pętli. Dowodzenie formuł logicznych Jeśli cel jest postaci -... wtedy należy najechać kursorem na znak - i po wciśnięciu prawego przycisku wybrać opcję Apply rules automatically here.
Dowodzenie c.d. Automatyzacja dowodu W zakładce Proof Search Strategy można (po ustawieniu odpowiedniego limitu czasowego) zaznaczyć opcję Autoresume strategy. Wtedy cele będące formułami logicznymi będą rozwiązywane natychmiast, bez potrzeby ingerencji użytkownika. Polecenia aplikacji reguł logiki Hoare a wciąż trzeba wydawać ręcznie.
Struktura plików.key \functions { Deklaracja stałych, np. int startval; } \programvariables { Deklaracja wszystkich wykorzystywanych zmiennych. } \hoare Sekcja zawierająca trójkę Hoare a z aktualizacjami stanu. Możliwe komendy: hoare dla częściowej poprawności, hoaretotal dla całkowitej, hoareet dla czasu wykonania. { { Warunek wstępny } [ Inicjująca aktualizacja (w formie równoległej) ] \[{ Kod programu }\] { Warunek końcowy } }
Częściowa poprawność: multiplication.key \functions { } \programvariables { int n, x, y, z; } \hoare { {y >= 0} \[{ z = 0; n = y; while (n>0) { z = z + x; n = n-1; } }\] {z = x * y} }
Częściowa poprawność: multiplication.key \functions { } \programvariables { int n, x, y, z; } \hoare { {y >= 0} \[{ z = 0; n = y; while (n>0) { z = z + x; n = n-1; } }\] {z = x * y} } Niezmiennik: n >= 0 & z = x * (y - n)
Reguły dla całkowitej poprawności loop T P U(I & dec >= 0) {I & b = true & dec = dec } [] s 1 {I & dec 0 & dec < dec } {I & b = false} [] s {Q} {P } [U] while(b){s 1 }s {Q}
Całkowita poprawność: countdowntotal.key \functions { int startval; } \programvariables { int timer; } \hoaretotal { { startval >= 0 } [timer := startval] \[{ while (timer>0) { timer = timer -1; } }\] { timer = 0 } }
Całkowita poprawność: countdowntotal.key \functions { int startval; } \programvariables { int timer; } \hoaretotal { { startval >= 0 } [timer := startval] \[{ while (timer>0) { timer = timer -1; } }\] { timer = 0 } } Niezmiennik: timer >= 0 Miara: timer
Reguły dla czasu wykonania exit ET assignment ET P U(Q) {P } [U] {Q} conditional ET loop ET {P } [U, x := e, et := et + 1] s {Q} {P } [U] x = e; s {Q} skip ET {P } [U, et := et + 1] s {Q} {P } [U] ; s {Q} {P & U(b = true)} [U, et := et + 1] s 1 s {Q} {P & U(b = false)} [U, et := et + 1] s 2 s {Q} {P } [U] if(b){s 1 }else{s 2 }s {Q} P U(I & dec >= 0) {I & b = true & dec = dec } [et := et + 1] s 1 {I & dec 0 & dec < dec } {I & b = false} [et := et + 1] s {Q} {P } [U] while(b){s 1 }s {Q} et odpowiada specjalnemu licznikowi executiontime którego wartość można ustawić na przed wykonaniem programu.
Czas wykonania: countdownexecutiontime.key \functions { int startval; } \programvariables { int timer; } \hoareet { { startval >= 0 & executiontime = 0} [timer := startval] \[{ while (timer>0) { timer = timer -1; } }\] { timer = 0 & executiontime = 2*startVal + 1} }
Czas wykonania: countdownexecutiontime.key \functions { int startval; } \programvariables { int timer; } \hoareet { { startval >= 0 & executiontime = 0} [timer := startval] \[{ while (timer>0) { timer = timer -1; } }\] { timer = 0 & executiontime = 2*startVal + 1} } Niezmiennik: timer>=0 & executiontime = 2*(startVal-timer) Miara: timer
Zadania na koniec multiplicationtotal
Zadania na koniec multiplicationtotal Niezmiennik: n >= 0 & z = x * (y - n) Miara: n
Zadania na koniec multiplicationtotal Niezmiennik: n >= 0 & z = x * (y - n) Miara: n multiplicationexecutiontime Warunek początkowy: y >= 0 & executiontime=0 Warunek końcowy: z = x * y & executiontime = 3 * y + 3
Zadania na koniec multiplicationtotal Niezmiennik: n >= 0 & z = x * (y - n) Miara: n multiplicationexecutiontime Warunek początkowy: y >= 0 & executiontime=0 Warunek końcowy: z = x * y & executiontime = 3 * y + 3 Niezmiennik: n >= 0 & z = x * (y - n) & executiontime = 3 * (y - n) + 2 Miara: n
Zadania na koniec multiplicationtotal Niezmiennik: n >= 0 & z = x * (y - n) Miara: n multiplicationexecutiontime Warunek początkowy: y >= 0 & executiontime=0 Warunek końcowy: z = x * y & executiontime = 3 * y + 3 Niezmiennik: n >= 0 & z = x * (y - n) & executiontime = 3 * (y - n) + 2 Miara: n Do obejrzenia/udowodnienia max.key gcd.key arraysort.key
Bibliografia Rainer Hähnle, Richard Bubel, A Hoare-Style Calculus with Explicit State Updates, Departament of Computer Science and Engineering, Chalmers University of Technologic and Göteborg University K. R. Apt, E.-R. Olderog, Verification of Sequential and Concurrent Programs, Springer-Verlag New York, Inc Mateusz Styrczula, Programowanie. Notatki do wykładów, Instytut Informatyki, Uniwersytet Wrocławski P. Urzyczyn, Materiały do wykładu z semantyki, Instytut Informatyki, Uniwersytet Warszawski Wikipedia contributors, Wikipedia, The Free Encyclopedia, [Internet]
Koniec