Computergestützte Verifikation 18.6.2002
Zusammenfassung Abstraktion zentral: Simulation/Bisimulation Simulation ACTL* Bisimulation CTL* Jede Menge abstrakter Zustände kann zu Simulation fortgesetzt werden, aber nicht immer zu einer Bisimulation Die meisten Reduktionstechniken sind Abstraktionen
Allgemeine Abstraktionsverfeinerung Spaltung eines abstrakten Zustandes kann weitere Spaltungen notwendig machen Propagation nach rückwärts... ... bis sich nix mehr ändert
Scheingegenbeispiele dead end state = erreichbar von einem konkreten Zustand im ersten abstrakten Zustand bad state = hat Nachfolger erfüllbare Pfadformel nicht erfüllbare Pfadformel Verfeinerung = trenne dead end und bad states
Lasso-Gegenbeispiele Realisierbar im konkreten System? Schlaufe im konkreten System kann länger sein als im abstrakten System Satz: Wenn abstrakte Schleife überhaupt konkret realisierbar ist, so auch eine der Länge k x A A – Länge der abstrakten Schlaufe k – Anzahl der konkreten Zustände im “kleinsten” abstrakten Zustand
Zusammenfassung Abstraktionsverfeinerung allgemein: lokale Realisierung der Bisimulationskritierien werden durch das gesamte Transitionssystem propagiert. Wichtig: Umkehrbarkeit der Übergangsrelation b) gegenbeispielgesteuert: Wichtig: Realisierbarkeit abstrakter Pfade im konkreten System muß überprüfbar sein geeignete Heuristiken für Trennung von dead ends und bad states
8. Softwareverifikation 8.1 Predicate Abstraction 8.2 Abstract Interpretation 8.3 Slicing 8.4 ... --- Vorsicht Baustelle ---
Probleme bei der Softwareverifikation komplexe Datentypen und Expressions Pointer und dynamische Datenstrukturen Prozeduren und Rekursion 4. Bibliotheken
8.1 Predicate Abstraction Basis: n boolesche Prädikate auf Programmvariablen z.B. p = NULL x > y +1 Jedem Prädikat fi wird eine boolesche Variable bi zugeordnet konkreter Zustand = Belegung der Programmvariablen abstrakter Zustand = Formel über den bi Abstraktionsrelation r: c r a gdw. [f1(c), ...., fn(c)] a Halbordnung “impliziert” auf den abstrakten Zuständen pc wird fast immer explizit repräsentiert
Abstrakte Zustände c¬d ¬ c d a b ¬ a b (¬ b ¬ d)(¬ a ¬ c¬ d) Praxis: z.B. Repräsentation abstrakter Zustände als BDD oder: Restriktion auf einfache Konjunktionen (im Beispiel: ¬ d)
Wo kommen Prädikate her? nutzerdefiniert automatisch - Bedingungen in IF, WHILE, .... - elementare Aussagen in Formel
Abstrakter Nachfolger geg.: Abstrakter Zustand a, guarded command g: g x1..n:=T1..n ges.: a’ so daß - jeder konkrete Zustand, der von einem von a repr. Zustand aus per g entsteht, von a’ repr. wird - a’ mglst. stark Berechnung von a’ im allg. schwer, für Konjunktionen leichter: a’: - false, falls a[bifi]i=1..n ¬ g bj falls a[bifi]i=1..n g fj [ xiTi]i=1..n n - ¬bj falls a[bifi]i=1..n g ¬fj [ xiTi]i=1..n j = 1 true, sonst Theorembeweiser
Beziehung zu Dijkstras Weakest Preconditions geg: Statement s, Zustandseigenschaft f spost(s,f) = die stärkste Eigenschaft f’, so daß jeder Zustand, der vor Ausführung von s f erfüllt, nach s f’ erfüllt. wpre(s,f) = die schwächste Eigenschaft f’, so daß jeder Zustand, der nach Ausführung von s f erfüllt, vor s f’ erfüllt hat. Für Zuweisungen: wpre(x := E, f) = f[x E] Satz: (spost(s,f) f’ ) (f wpre(s,f’) ) exakter abstrakter Nachfolger von a mit command g: spost(g,a)
Theorembeweiser Eingabe: prädikatenlog. Formel, nicht immer Stufe 1 1. Formel aus entscheidbarer Theorie Entscheidungsalg. - SAT-Checker - Pressburger Arithmetik - Bitvektor-Arithmetik - Model Checker .... Rewriting - Regeln (z.B. Schlussregeln) Termersetzung, Unifikation - Ziel: Rückführung auf Axiome - erweiterbare Regelbasis - Taktiken
Abstraktionsverfeinerung = Hinzunahme weiterer Prädikate
Beispiel void partition() { bool b1,b2,b3,b4; typedef struct cell{ b1 = unknown(); b3 = unknown(); b2 = true; b4 = unknown(); skip; while(*) { assume(!b1); if(*) { assume(b3) if(*) { assume(!b2); } L: skip; } else { assume(!b3); b2 = b1; b4 = b3; assume(b1); Beispiel typedef struct cell{ int val; struct cell* next; } * list; list partition(list *l, int v) { list curr, prev, newl, nextCurr; curr = * l; prev = NULL; newl = NULL; while( curr != NULL) { nextCurr = curr -> next; if(curr -> val > v) { if(prev != NULL) { prev -> next = nextCurr; } if(curr == *l) { *l = nextCurr; curr -> next = newl; L: newl = curr; } else { prev = curr; curr = nextCurr; return newl; b1: curr==NULL b2: prev==NULL b3: curr->val>v b4: prev->val>v AG(@L curr!=NULL AND curr->val>v AND (prev->val<=v OR prev=NULL)) AG(@L !b1 AND b2 AND (!b3 OR b4))
Pointer bisher: wpre(x := E,f) = f[ x E] stimmt nicht, wenn Pointer beteiligt sind: Beispiel: Sei p = & x; wpre(x = 3, *p > 5) != *p > 5 (für einen potentiellen Alias y) wpre(x := E, f) = ( ( &x = &y f[y E]) ( &x != &y f) ) Nichtdeterminismus, es sei denn, Alias-Analyse kann eine Alternative ausschließen: “may point to”, “may not point to”
Bedingungen IF B THEN T ELSE E END IF * THEN assume(X); T ELSE assume(Y); END X: das stärkste Prädikat (über den gegebenen Prädikaten) das von B impliziert wird Y: das stärkste Prädikat (über den gegebenen Prädikaten) das von ¬B impliziert wird zwischen X und Y kann Lücke klaffen Nichtdet.
Prozeduren einfach: Inlining (mit Tiefenrestriktion bei Rekursion) schwierig: Fixpunktberechnung
Übung Transformiere folgendes C-Programm in ein boolesches main() { int x,y,z; x = 3; y = x + 1; while(y > 3) { z = 0; if(x == z) x = x + 1; z = 3; } else y = x - 1; y++; y = 2 * y; x = foo(y,z); Transformiere folgendes C-Programm in ein boolesches Programm, basierend auf den Prädikaten x > 0, y = x und y != 0