300 likes | 427 Vues
This presentation by Malte Schwerhoff from ETH Zürich discusses automated software verification, focusing on permission-based logic as a method to control access to mutable state. It highlights the significance of modularity and provides a practical example with the Cell class implementation in Viper. The verification covers conditions necessary for correctness such as preconditions and postconditions. By managing permissions effectively, the framework enables reasoning about shared state and concurrency, ultimately aiming for reliable software verification.
E N D
Automated Software Verification with aPermission-Based Logic Malte Schwerhoff, ETH Zürich 20th June 2014, Zürich
Outline Motivation Permissions Viper Demo
Automated Software Verification • We have • Mutable state (heap locations) • Method calls, loops • Concurrency • We want: • Automated static verification • Modularity
Example class Cell { varv: int methodadd(c: Cell) { v := v + c.v } } methodclient() { varc1 := newCell c1.v := 1 var c2 := newCell c2.v := 2 c1.add(c2) assertc1.v == 3 assertc2.v == 2 }
Modularity class Cell { varv: int methodadd(c: Cell) { v := v + c.v } } methodclient() { varc1 := newCell c1.v := 1 var c2 := newCell c2.v := 2 c1.add(c2) assert c1.v == 3 assertc2.v == 2 } ?
Specifications class Cell { varv: int methodadd(c: Cell) requires c != nullensures v == old(v) + old(c.v) { v := v + c.v } } methodclient() { varc1 := newCell c1.v := 1 var c2 := newCell c2.v := 2 c1.add(c2) assert c1.v == 3 assertc2.v == 2 }
Reasoning with Specifications class Cell { varv: int methodadd(c: Cell) requires c != nullensures v == old(v) + old(c.v) { v := v + c.v } } methodclient() { varc1 := newCell c1.v := 1 var c2 := newCell c2.v := 2 c1.add(c2) assert c1.v == 3 assertc2.v == 2 } ?
An Incorrect Implementation class Cell { varv: int methodadd(c: Cell) requires c != nullensures v == old(v) + old(c.v) { v := v + c.v c.v := 0 } } methodclient() { varc1 := newCell c1.v := 1 var c2 := newCell c2.v := 2 c1.add(c2) assert c1.v == 3 assertc2.v == 2 }
Strengthening Specifications class Cell { varv: int methodadd(c: Cell) requires c != nullensures v == old(v) + old(c.v) ensures c.v == old(c.v) { v := v + c.v c.v := 0 } } methodclient() { varc1 := newCell c1.v := 1 var c2 := newCell c2.v := 2 c1.add(c2) assert c1.v == 3 assertc2.v == 2 }
Strengthening Specifications class Cell { varv: int methodadd(c: Cell) requires c != nullensures v == old(v) + old(c.v) ensures c.v == old(c.v) { v := v + c.v } } methodclient() { varc1 := newCell c1.v := 1 var c2 := newCell c2.v := 2 c1.add(c2) assert c1.v == 3 assertc2.v == 2 } ?
Aliasing class Cell { varv: int methodadd(c: Cell) requires c != nullensures v == old(v) + old(c.v) ensures c.v == old(c.v) { v := v + c.v } } methodclient() { varc1 := newCell c1.v := 1 varc2 := new Cell c2.v := 2 c1.add(c1)// ensures c1.v == 1 + 1 // ensures c1.v == 1 assert c1.v == 3 assertc2.v == 2 }
Challenges Reason about Shared State & Control Aliasing
Permission-Based Verification • Use permissions to control access to shared state • Permissions only exist conceptually, not at run-time • Permissions • Per field x.f • Exclusive write permissions (allows reading as well) • Non-exclusive read permissions
Permission-Based Verification • Permissions to a location x.f can be • Splitinto multiple read permissions • Transferred between methods (or threads) • Recombinedagain
Fractional Permissions client(x) add(x)
Splitting & Transferring Fractional Permissions client(x) add(x) ? ?
Merging Fractional Permissions client(x) add(x) ?
Permission-Based Verification • Assumptions such as x.f == 0can only be made if permissions to x.f are available • If all permissions to x.f are lost, assumptions about x.f must be havoced (forgotten)
Syntax, Separating Conjunction • Permissions can be split: acc(x.f, 1) ⇔ acc(x.f, 1/2) && acc(x.f, 1/2) • Write permissions are exclusive: acc(x.f, 1) && acc(y.f, 1)⇒x ≠ y • Write permissions are “maximal”: acc(x.f, 1) && acc(y.f, 1/100000) ⇒ x ≠ y
Return of the Example method add(c: Cell) requiresacc(v) && acc(c.v, 1/2) ensuresacc(v) && acc(c.v, 1/2) ensures v == old(v) + c.v methodclient() { varc1 := newCell // acc(c1.v) c1.v := 1 //c1.v == 1 var c2 := new Cell // acc(c2.v) c2.v := 2 //c2.v == 2 c1.add(c2) assertc1.v == 3 && c2.v == 2 } ? 20
Return of the Example method add(c: Cell) requiresacc(v) && acc(c.v, 1/2) ensuresacc(v) && acc(c.v, 1/2) ensures v == old(v) + c.v methodclient() { varc1 := newCell // acc(c1.v, 1) c1.v := 1 //c1.v == 1 var c2 := new Cell // acc(c2.v, 1) c2.v := 2 //c2.v == 2 c1.add(c2) assertc1.v == 3 && c2.v == 2 } Reason about call byexhaling precondition followed byinhalingpostcondition 21
Return of the Example method add(c: Cell) requiresacc(v) && acc(c.v, 1/2) ensuresacc(v) && acc(c.v, 1/2) ensures v == old(v) + c.v methodclient() { ... // c1.add(c2) // acc(c1.v, 1) && c1.v == 1 && acc(c2.v, 1) && c2.v == 2 exhale acc(c1.v) && acc(c2.v, 1/2) // && acc(c2.v, 1/2) && c2.v == 2 inhale acc(c1.v) && acc(c.v, 1/2) // acc(c1.v, 1) && acc(c2.v, 1) && c2.v == 2 inhale c1.v == old(c1.v) + c2.v // acc(c1.v, 1) && c1.v == 3 && acc(c2.v, 1) && c2.v == 2 assert c1.v == 3 && c2.v == 2 } 22
Viper Silver verified by Carbon Silicon queries Boogie(Microsoft) Z3(Microsoft) VerificationConditionGeneration Symbolic Execution
Silver • Silver is an intermediate verification language • Encode source languages (with specs) in Silver • Use Silver verifier to verify encoding • Simple: • Objects, fields (heap), methods, loops, if-then-else • Rudimentary type system (primitives + Ref) • Verification features such as specifications • No concurrency primitives • Silver programs can be used for • Verification • Specification inference
Symbolic Execution with Silicon maintains verifies Verifier Program Symbolic State σ queries uses Prover - Symbolically each method (method-modular) - At each statement:- querystate (and prover) to decide if statement is executable - update state by exhaling precondition and inhaling postcondition - Branch over conditionals σ1 σ2 σ3 σ4 σ5
Symbolic Execution with Silicon Symbolic state σ comprises • γ:Store mapping local variables to symbolic values (terms) c1 ↦ tc1, c2 ↦ tc2 • h:Heap recording permissions to and values of fields in the form of heap chunks tc1.v ↦ tv1 # p1, tc2 ↦ tv2 # p2 • π:Path conditions with assumptions about values tc1 ≠ null, tv1 > 0
Slide of the Undead Example methodclient() { ... // γ: c1 ↦ tc1, c2 ↦ tc2 // h: tc1.v ↦ tv1 # 1, tc2.v ↦ tv2 # 1 // π: tv1 == 1, tv2 == 2 exhale acc(c1.v) && acc(c2.v, 1/2) // h: tc2.v ↦ tv2 # 1/2 // π: tv1 == 1, tv2 == 2 inhale acc(c1.v) && acc(c.v, 1/2) // h: tc1.v ↦ tv1 # 1, tc2.v ↦ tv3 # 1 // π: tv1 == 1, tv2 == 2 inhale c1.v == old(c1.v) + c2.v // π: tv1 == 1, tv2 == 2, tv3 == tv1 + tv2 assert c1.v == 3 && c2.v == 2 // π ⊢ tv3 == 3 && tv2 == 2 } 28
Outlook • Information hiding and abstraction • Recursive data structures • Opposite of permissions: obligations • Translation of high-level features • Immutable data (vs. permissions) • Lazy evaluation (vs. permissions) • Closures • Actor-based concurrency • Specification inference