96 5. Theorem-Beweisen • Vor.: formale Spezifikation und formale Semantik der Programmiersprache • Idee: formaler Beweis, dass Programm Spezifikation erfüllt • notwendig: Unterstützung durch (semi-automatisches) Beweis-Werkzeug (Theorem-Prover (sonst unklar, ob Beweis korrekt)) bzw. Model-Checker • häufig verwendete Theorembeweiser: Isabelle/HOL, PVS • heutiger Stand: in Entwicklung • geeignet insbesondere bei extrem sicherheitskritischen Anwendungen 97 Programmverifikation am Beispiel der Sprache IMP Syntax der einfachen, imperativen Sprache IMP • Z ganze Zahlen; z, z 0, z1, z2, . . . • IB := {true, f alse} boolesche Werte; β, β1, β2 • Operationssymbole Op := Aop ∪ Bop ∪ Relop = {+, −, ∗, . . .} ∪ {∨, ∧, ¬} ∪ {==, <=, . . .} • V ar Variablen; x, y, x0, y 0, . . . • Aexp arithm. Ausdrücke; e, e0, e1, e2, . . . • Bexp bool. Ausdrücke; b, b0, b1, b2, . . . • Com Anweisungen; c, c0, c1, c2, . . . 98 Syntax von IMP (Forts.) • Mengen Z, IB, Op, V ar vorgegeben • Mengen Aexp, Bexp, Com gemäß folgender Backus-Naur-Form (BNF) Aexp : e ::= z | x | e1 ⊕ e2 ⊕ ∈ Aop Bexp : b ::= true | false | e1 e2 | ¬b | b1 ⊗ b2 mit ∈ Relop, ⊗ ∈ Bop − {¬} Com : c ::= skip | x := e | c1; c2 | if b then c1 else c2 | while b do c 99 Beweis von Programmeigenschaften • Zusicherungen (assertions) der Form {A} |{z} Vorbedingung c {B} |{z} Nachbedingung • hier: Zusicherungen partieller Korrektheit: ? nach erfolgreicher (terminierender!) Ausführung von c in einem Zustand, der A erfüllt, erreicht man einen Zustand, der B erfüllt • alternativ: Zusicherung totaler Korrektheit [A] c [B]: partielle Korrektheit + Termination 100 Zusicherungssprache Assn Aexpv : a::= z | x | i | a1 ⊕ a2 Assn : i ∈ Intvar (Integervariable) A::= true | false | a1 a2 | A1 ⊗ A2 | ¬A1 | A1 ⇒ A2 | ∀i.A | ∃i.A (∼ Prädikatenlogik 1. Stufe) • jede Integer-Variable “muss zu einem Quantor (∃, ∀) gehören” (keine freien Integer-Variablen!) • Notation: |= A: A “ist wahr” (unabhängig von Belegung der Variablen) Vor.: formale Semantik von A 101 Beweisregeln für partielle Korrektheit (Hoare-Logik) • Syntax-orientiert 1) {A} skip {A} 2) {B[x/e]} x := e {B} 3) {A} c1 {C}, {C} c2 {B} {A} c1; c2 {B} 4) {A ∧ b} c1 {B}, {A ∧ ¬b} c2 {B} {A} if b then c1 else c2 {B} 5) {A ∧ b} c {A} {A} while b do c {A ∧ ¬b} |= (A ⇒ A0) {A0} c {B 0} 6) {A} c {B} |= (B 0 ⇒ B) (Konsequenz-Regel) 102 Hoare-Logik (2) • mit den Beweisregeln können Theoreme über das betrachtete Programm bewiesen werden Schreibweise: ` {A} c {B} für {A} c {B} ist ein Theorem. Satz: Sei {A}c{B} eine Zusicherung partieller Korrektheit. Wenn ` {A}c{B}, dann |= {A}c{B} (ohne Beweis) Bemerkung: Die Umkehrung gilt nicht (→ Gödel’scher Unvollständigkeitssatz!) 103 Beispiel: Hoare-Logik Beispiel: P: while x>0 do (y:= x*y; x:= x-1) zeige: P berechnet n! (falls x = n, y = 1) genauer: {x = n ∧ y = 1 ∧ x ≥ 0} P {y = n!} wähle: Inv := y ∗ x! = n! ∧ x ≥ 0 als Invariante für while-Schleife nach Regel 5) für while-Schleife und Lemma (s.u.): {Inv} P {Inv ∧ x ≤ 0} weiter: x = n ∧ n ≥ 0 ∧ y = 1 ⇒ Inv ≡ y ∗ x! = n! ∧ x ≥ 0 ∧ x ≤ 0 ⇒ y ∗ x! = n! ∧ x = 0 ⇒ y ∗ 0! = n! ⇒ y = n! und Inv ∧ x ≤ 0 insgesamt mit der Konsequenzregel 6): {x = n ∧ n ≥ 0 ∧ y = 1} P {y = n!} 2 104 Lemma: {Inv ∧ x > 0} y:= x*y; x:=x-1 {Inv} Beweis: { Inv[x/(x − 1)] | {z } } x:= x-1 {Inv} → 2) * =y∗(x−1)!=n! ∧ (x−1)≥0 weiter: {x ∗ y ∗ (x − 1)! = n! ∧ (x − 1) ≥ 0} y:= x*y {Inv[x/(x − 1)]} → 2) ** nach * und ** mit Regel 3) für Sequenz: {x ∗ y ∗ (x − 1)! = n! ∧ (x − 1) ≥ 0} y:= x*y; x:= x-1 {Inv} weiter: nach Def. “!”, Komm. u. Ass. von “*”, “≥” Inv ∧ x > 0 ⇒ x ∗ y ∗ (x − 1)! = n! ∧ (x − 1) ≥ 0 {Inv ∧ x > 0} y:= x*y; x:= x-1 {Inv} → 6) 2 105 Bemerkung: • die Schleifen-Invariante muss “stark genug” gewählt werden (Wertebereich der Variablen einschränken!) • bei Sequenzen von rechts nach links vorgehen • Korrektheitsbeweise sind meist nicht-trivial (→ autom. Beweisen)