Hoare-Regel für die bedingte Anweisung I1 : I2 : Beispiel 1 zur Verifikation eines bedingten Anweisung {B P } S1 {Q} { B P } → {Q} {P } if (B) then S1 {Q} {B P } S1 {Q} { B P } S2 {Q} {P } if (B) then S1 else S2 {Q} • In der Regel für bedingte Anweisungen wird die Bedingung der Anweisung in weitere Bedingungen in den Zusicherungen umgewandelt. • Da die Handlungsstränge des Programmes wieder zusammenlaufen, muss in beiden Alternativen die selbe Nachbedingung erreicht werden. • Eine gemeinsame Nachbedingung kann durch Abschwächungsregeln erreicht werden: {a ∈ Z} if (a > 0) then {a ∈ Z a > 0} → {a ∈ Z a > 0 a = a} b := a; {a ∈ Z a > 0 b = a} → {a ∈ Z b = |a|} else {a ∈ Z a > 0} → {a ∈ Z a ≤ 0 − a = −a} b := −a; {a ∈ Z a ≤ 0 b = −a} → {a ∈ Z b = |a|} {a ∈ Z b = |a|} Vorgehensweise bei bekannter Vorbedingung {P } durch Abstraktion wie im Beispiel oder durch disjunktive Verknüpfung der einzelnen • Vorbedingung zu den Vorbedingungen {P B} im then-Teil und {P B} im else-Teil ergänzen. Nachbedingungen. • then-Teil und else-Teil verifizieren mit Nachbedinugungen {Q1} bzw. {Q2}. • Nachbedinungen zu gemeinsamer Nachbedingung {Q} abschwächen (z.B. Q ⇔ (Q1 Q2). • Nachbedingung {Q} der bedingten Anweisung einfügen. c LETTMANN 2003/04 Modellierung — Verifikation V-31 Beispiel 2 zur Verifikation eines bedingten Anweisung {a > 0 b > 0 a = b} if (a > b) then { →{ a := a − b; { else { →{ b := b − a; { { c LETTMANN 2003/04 Modellierung — Verifikation c LETTMANN 2003/04 Modellierung — Verifikation V-32 Beispiel 2 zur Verifikation eines bedingten Anweisung {a > 0 b > 0 a = b} if (a > b) then {a > 0 b > 0 a = b a > b} → {a − b > 0 b > 0} a := a − b; {a > 0 b > 0} else {a > 0 b > 0 a = b a ≤ b} → {a > 0 b − a > 0} b := b − a; {a > 0 b > 0} {a > 0 b > 0} } } } } } } } V-33 c LETTMANN 2003/04 Modellierung — Verifikation V-34 Hoare-Regel für die Schleife L: Beispiel 1 zur Verifikation einer Schleife {I B} S {I} {I} while (B) do S {I B} • Gilt die Zusicherung I vor Ausführung der Schleife und gilt ferner, dass die Ausführung des Schleifenrumpfes S, falls sie terminiert, einen Zustand erzeugt, in dem I gilt, vorausgesetzt vor Ausführung der Schleife galt die Zusicherung (I B), so gilt I nach jeder Ausführung des Schleifenrumpfes S. • Da I nach jeder Ausführung des Schleifenrumpfes gilt, so gilt I nach der Schleife, sofern diese terminiert. Zusätzlich gilt nach der Schleife die Schleifenbedingung natürlich nicht und kann daher negiert zur Nachbedingung ergänzt werden. • Wenn die Schleifenbedingung von Anfang an nicht gilt, so wird die Schleife nicht durchlaufen. Da I vor der Schleife gilt, so gilt I daher auch nach der Schleife und damit auch (I B). • Die Verifikation mit der Schleifenregel zeigt NICHT die Terminierung der Schleife. Das Finden der Invarianten stellt in der Programmverifikation i.d.R. den schwierigsten Schritt dar. {x + y = a x ≥ 0} Invariante while (x > 0) do {x + y = a x ≥ 0 x > 0} → {x − 1 + y + 1 = a x − 1 ≥ 0} x := x − 1; {x + y + 1 = a x ≥ 0} y := y + 1; {x + y = a x ≥ 0} {x + y = a x ≥ 0 x ≤ 0} → {x + y = a x = 0} • Wie entdeckt man eine Invariante? Bei der Programmerstellung eine Invariante zur Verifikation vorgeben und als Kommentar in den Programmtext einfügen. Ansonsten ist die einzige Möglichkeit, ∗ die Implementation vollständig zu begreifen, ∗ aus Durchläufen durch den Schleifenrumpf mit Beispielwerten ein Verständnis für die Veränderung der Zustände zu entwickeln und ∗ dieses Verständnis in eine Invariante umzusetzen. • Invarianten sind nicht eindeutig. Die Verifikation einer Schleife entspricht einem induktiven Beweis. Die Invariante entspricht der Induktionsbehauptung. c LETTMANN 2003/04 Modellierung — Verifikation V-35 Beispiel zur Verifikation: Potenzieren (1) Modellierung — Verifikation V-36 Beispiel zur Verifikation: Potenzieren (2) Spezifikation: Vorbedingung: {x ∈ R n ∈ N} Nachbedingung: {b = xn } Idee des Algorithmus: {x ∈ R n ∈ N} begin { →{ a := x; { →{ b := 1; { →{ i := n; { →{ while (i > 0) do begin { →{ →{ b := b ∗ a; { →{ i := i − 1; { end { →{ end {b = xn} Rückführung der Potenzierung auf die iterierte Multiplikation Spezifikation: Vorbedingung: {x ∈ R n ∈ N} Nachbedingung: {b = xn } begin a := x; b := 1; i := n; while (i > 0) do begin b := b ∗ a; i := i − 1; end end Variable x und n bleiben unverändert, damit Eingabewerte zugreifbar bleiben. Variable b speichert das Ergebnis. c LETTMANN 2003/04 c LETTMANN 2003/04 Modellierung — Verifikation V-37 c LETTMANN 2003/04 } } } } } } } } } } } } } } } } Modellierung — Verifikation V-38 Beispiel zur Verifikation: Potenzieren (3) Beispiel zur Verifikation: Potenzieren(4) Spezifikation: Vorbedingung: {x ∈ R n ∈ N} Nachbedingung: {b = xn } Suche nach einer Invarianten • Tautologische Aussagen sind immer invariant, aber sie helfen nicht. • Betrachte Werteverlauf in der Schleife für beteiligte Variablen. begin a := x; b := 1; i := n; while (i > 0) do begin b := b ∗ a; i := i − 1; end end Variablenwerte bei Test der Bedingung in while x 2 2 2 2 2 n 4 4 4 4 4 a 2 2 2 2 2 b 1 2 4 8 16 i 4 3 2 1 0 Invariante: {xn = b ∗ ai i ≥ 0} c LETTMANN 2003/04 Modellierung — Verifikation V-39 {x ∈ R n ∈ N} begin {x ∈ R n ∈ N} → {x ∈ R n ∈ N x = x} a := x; {x ∈ R n ∈ N a = x} → {x ∈ R n ∈ N a = x 1 = 1} b := 1; {x ∈ R n ∈ N a = x b = 1} → {x ∈ R n ∈ N a = x b = 1 n = n} i := n; {x ∈ R n ∈ N a = x b = 1 i = n} Invariante → {xn = b ∗ ai i ≥ 0} while (i > 0) do begin {xn = b ∗ ai i ≥ 0 i > 0} → {xn = b ∗ ai i > 0} → {xn = b ∗ a ∗ ai−1 i > 0} b := b ∗ a; {xn = b ∗ ai−1 i > 0} → {xn = b ∗ ai−1 i − 1 ≥ 0} i := i − 1; {xn = b ∗ ai i ≥ 0} end {xn = b ∗ ai i ≥ 0 i ≤ 0} → {xn = b} → {xn = b ∗ ai i = 0} end {b = xn} c LETTMANN 2003/04 Modellierung — Verifikation V-40 Beispiel zur Verifikation: Effizientes Potenzieren (1) Beispiel zur Verifikation: Effizientes Potenzieren (2) Idee des Algorithmus: Spezifikation: Vorbedingung: {x ∈ R n ∈ N} Nachbedingung: {b = xn } {x ∈ R n ∈ N} begin {x ∈ R n ∈ N} a := x; b := 1; i := n; {x ∈ R n ∈ N a = x b = 1 i = n} Invariante → {xn = b ∗ ai i ≥ 0} while (i > 0) do begin {xn = b ∗ ai i ≥ 0 i > 0} → {xn = b ∗ ai i > 0} if (i ) then {xn = b ∗ ai i > 0 i } → {xn = b ∗ a ∗ (a2)[i/2] i > 0 i } b := b ∗ a; {xn = b ∗ (a2)[i/2] i > 0 i } → {xn = b ∗ (a2)[i/2] i > 0} {xn = b ∗ ai i > 0 i } → {xn = b ∗ (a2)[i/2] i > 0 i } → {xn = b ∗ (a2)[i/2] i > 0} {xn = b ∗ (a2)[i/2] i > 0} a := a2; {xn = b ∗ a[i/2] i > 0} → {xn = b ∗ a[i/2] [i/2] ≥ 0} i := i/2; {xn = b ∗ ai i ≥ 0} end {xn = b ∗ ai i ≥ 0 i ≤ 0} → {xn = b ∗ ai i = 0} → {xn = b} end {b = xn} Quadrieren von Teilergebnissen spart die Hälfte der Multiplikationen. Sei nk nk−1 . . . n2n1n0 die Dualdarstellung von n, also n = k n i 2i . i=0 Es gilt dann xn = k i (x2 )ni , wobei in diesem Produkt nur die i=0 Faktoren für ni = 1 eine Rolle spielen, und außerdem gilt n/2i 1 ni = 0 Damit ergibt sich folgender Algorithmus zur Potenzierung: Spezifikation: Vorbedingung: {x ∈ R n ∈ N} Nachbedingung: {b = xn } begin a := x; b := 1; i := n; while (i > 0) do begin if (i ) then b := b ∗ a; a := a2; i := i/2; end end c LETTMANN 2003/04 Modellierung — Verifikation V-41 c LETTMANN 2003/04 Modellierung — Verifikation V-42 Invarianten Terminierung • Schleifeninvarianten sind Zusicherungen, d.h. sie beschreiben Zusammenhänge von Programmgrößen. Der Nachweis der totalen Korrekheit eines Programms erfordert neben der bisher betrachteten partiellen Korrektheit den Nachweis seiner Terminierung. • Eine Invariante muss vor der Schleife gültig sein. Wann ist Terminierung ein Problem? • Eine Invariante muss nach Durchlauf durch den Schleifenrumpf gültig sein, wenn sie vor dem Schleifenrumpf gültig war (zusammen mit der Schleifenbedingung). • Invarianten sind zum Zeitpunkt der Implementierung leichter zu bestimmen. • In Zuweisungen, falls der arithmetische Ausdruck nicht berechenbar ist (z.B. Division durch 0, nicht initialisierte Variable), • in bedingten Anweisungen, falls die Bedingung nicht entschieden werden kann oder falls die Anweisungen im then-Teil oder im else-Teil nicht terminieren, • in Anweisungsfolgen, falls eine Anweisung darin nicht terminiert, • aber vor allem in Schleifen. Ausser in Schleifen kann die Terminierung garantiert werden,indem man die Art der arithmetischen oder booleschen Ausdrücke auf einfache Formen beschränkt (Komplexitätstheorie: Addition, Subtraktion von 1, also x := x + 1; und Test auf 0, also if (x = 0) . . .). Wir beschränken uns daher auf Terminierungsbeweise für Schleifen. c LETTMANN 2003/04 Modellierung — Verifikation V-43 Terminierung von Schleifen while (B) do S terminiert unter der Vorbedingung P genau dann, wenn jede Ausführung von S terminiert und wenn ein ganzzahliger Ausdruck T existiert, so dass folgende Aussagen gelten: 1. P ⇒ I 2. (T ≤ 0 I) ⇒ B 3. {T = i + 1 B I} S {T = i I} V-44 Ganzzahliger Ausdruck: T = x Invariante: I = (x + y = a x ≥ 0) Schleifenbedingung: B = (x > 0) Nachweis (T ≤ 0) I ⇒ B x ≤ 0 (x + y = a x ≥ 0) ⇒ x=0 ⇒ x≤0 Nachweis der Invarianz von I und der Dekrementierung von T I bezeichnet dabei eine Invariante der Schleife und i ein Bezeichner für eine ganzzahlige Variable ist, die weder in T noch in der Schleife vorkommt, d.h. nicht in B und nicht in S. T heißt auch Terminierungsfunktion oder Variante der Schleife. Modellierung — Verifikation Modellierung — Verifikation Beispiel zum Terminierungsbeweis einer Schleife Eine Schleife c LETTMANN 2003/04 c LETTMANN 2003/04 V-45 while (x > 0) do {x = i + 1 x + y = a x ≥ 0 x > 0} → {x − 1 = i x − 1 + y + 1 = a x − 1 ≥ 0} x := x − 1; {x = i x + y + 1 = a x ≥ 0} y := y + 1; {x = i x + y = a x ≥ 0} c LETTMANN 2003/04 Modellierung — Verifikation V-46 Nachweis der Terminierung von Schleifen Terminierung: Ein Problem? • Insgesamt sind fünf Nachweise erforderlich: • Manche Schleifen terminieren immer! Terminierung des Schleifenrumpfes Ganzzahligkeit von T Folgerbarkeit der Nicht-Gültigkeit der Schleifenbedingung aus T ≤ 0 und einer Invarianten I Folgerbarkeit dieser Invarianten I aus der Vorbedingung der Schleife Nachweis der Invarianz von I und Nachweis der {a > 0 b > 0} while (a = b) do begin while (a > b) do a := a − b; while (b > a) do b := b − a; end • Manche Schleifen terminieren nicht immer! Dekrementierung von T {a > 0 b > 0} while (a = b) do begin while (a ≥ b) do a := a − b; while (b > a) do b := b − a; end • Die Invariante I muss nicht mit der Invarianten für den Nachweis der partiellen Korrektheit der Schleife übereinstimmen. Die Gestalt der Hoare-Regel für die Schleife erlaubt keinen gleichzeitigen Nachweis von partieller Korrektheit und Terminierung. • Für manche Schleifen ist nicht bekannt, ob sie terminieren! {n ∈ N n > 0} while (n = 1) do if (n ) then n := n/2; else n := 3 ∗ n − 1; Ulam’s Funktion • Die Terminierung von Schleifen ist unentscheidbar. c LETTMANN 2003/04 Modellierung — Verifikation V-47 c LETTMANN 2003/04 Alternative Formulierung des Terminierungsnachweises Modellierung — Verifikation V-48 Nicht-Terminierung von Schleifen Eine Schleife while (B) do S • Zeige Terminierung des Schleifenrumpfes. • Bestimme einen ganzzahligen Ausdruck T über den Variablen des Programms. terminiert nicht, wenn eine Zusicherung INT existiert, so dass folgende Aussagen gelten: • Zeige, dass T in jedem Durchlauf der Schleife verkleinert wird (streng monoton fallend). 1. Es gibt Eingaben, so dass {B INT } vor der Schleife gültig ist. • Zeige, dass T nach unten beschränkt ist (T ≥ ist invariant). 2. {B INT } ist Invariante der Schleife. INT bezeichnet also eine Zusicherung, die nur in bestimmten Eingabesituationen gültig ist. Anstelle des streng monoton fallenden Ausdruckes T kann auch ein streng monoton wachsender ganzzahliger Ausdruck gewählt werden mit eine oberen Schranke. Beim Nachweis der partiellen Korrektheit und der Terminierung müssen die verwendeten Zusicherungen in allen denkbaren Zuständen des Programmes an den entsprechenden Stellen gelten. Sind die beiden Formulierungen gleichwertig? c LETTMANN 2003/04 Modellierung — Verifikation V-49 c LETTMANN 2003/04 Modellierung — Verifikation V-50 Anmerkungen zum Testen Notwendigkeit von Verifikation • Informationssicherheit (Security) Tests können die Anwesenheit von Fehlern beweisen, aber nie die Abwesenheit von Fehlern (bei unendlich vielen möglichen Eingaben). Vertraulichkeit Integrität Authentizität Nicht-Rückweisbarkeit (Signaturgesetz) Klassifikation von Testverfahren: • Schnittstellentest (Blackbox-Test) Zertifizierung von IT-Systemen durch das Bundesamt für Sicherheit in der Informationstechnik. (Höhere Stufen der Vertrauenswürdigkeit erfordern formale Spezifikation und formale Verifikation.) Die Ein- / Ausgaberelation wird auf Übereinstimmung mit der Spezifikation geprüft. • Programmabhängiger Test (Whitebox-Test) Möglichst große Teile aller Pfade durch das Programm werden getestet. Eine möglichst große Überdeckung (des Programmcodes) ist erwünscht. Beispiele Home Banking Geld- und Chipkarten Systematische Auswahl von Testfällen: • Schnittstellentest • Systemsicherheit (Safety) Software für sicherheitskritische Systeme ist formal zu spezifizieren und zu verifizieren. Beispiele: Eingebettete Systeme (Embedded Systems) als Regelungssysteme / reaktive Systeme unter Berücksichtigung von Realzeitaspekten in Autos, Flugzeugen, Raumfahrzeugen, Anlagensteuerungen. c LETTMANN 2003/04 Pro spezifizierter Bedingung mindestens einen Testfall prüfen, Randbereiche (ggf. von beiden Seiten) prüfen, Maximal-, Minmalwerte nicht vergessen, eine genügend große Anzahl von Normalfällen prüfen. • Überdeckungstest Erwünscht, aber kaum machbar ist eine Wegüberdeckung d.h. jeder Weg wird mindestens einmal durchlaufen. Auf jeden Fall nötig ist eine Anweisungsüberdeckung, d.h. jede Anweisung wird mindestens einmal durchlaufen. Hauptproblem des Testens: Kombinatorische Explosion der Testfälle Modellierung — Verifikation V-51 c LETTMANN 2003/04 Modellierung — Verifikation V-52