CSE507 Computer-Aided Reasoning for Software Bounded Verification - - PowerPoint PPT Presentation

cse507
SMART_READER_LITE
LIVE PREVIEW

CSE507 Computer-Aided Reasoning for Software Bounded Verification - - PowerPoint PPT Presentation

CSE507 Computer-Aided Reasoning for Software Bounded Verification courses.cs.washington.edu/courses/cse507/14au/ Emina Torlak emina@cs.washington.edu Today 2 Today Last lecture Full functional verification with Dafny, Boogie, and Z3 2


slide-1
SLIDE 1

CSE507

Emina Torlak

emina@cs.washington.edu

courses.cs.washington.edu/courses/cse507/14au/

Computer-Aided Reasoning for Software

Bounded Verification

slide-2
SLIDE 2

Today

2
slide-3
SLIDE 3

Today

2

Last lecture

  • Full functional verification with Dafny, Boogie, and Z3
slide-4
SLIDE 4

Today

2

Last lecture

  • Full functional verification with Dafny, Boogie, and Z3

Today

  • Bounded verification with Kodkod (Forge, Miniatur, TACO)
slide-5
SLIDE 5

Today

2

Last lecture

  • Full functional verification with Dafny, Boogie, and Z3

Today

  • Bounded verification with Kodkod (Forge, Miniatur, TACO)

Announcements

  • Homework 2 is due today at 11pm
  • Homework 3 has been released
slide-6
SLIDE 6

Confidence Cost (programmer effort, time, expertise)

The spectrum of program validation tools

3

Verification Static Analysis Extended Static Checking Concolic Testing & Whitebox Fuzzing Ad-hoc Testing Bounded Verification & Symbolic Execution

slide-7
SLIDE 7

Confidence Cost (programmer effort, time, expertise) E.g., Dafny, Coq, Leon:

  • support for rich (FOL+)

correctness properties

  • high annotation overhead

(pre/post conditions, invariants, etc.)

The spectrum of program validation tools

3

Verification Static Analysis Extended Static Checking Concolic Testing & Whitebox Fuzzing Ad-hoc Testing Bounded Verification & Symbolic Execution

slide-8
SLIDE 8

Confidence Cost (programmer effort, time, expertise) E.g., Astree:

  • small set of fixed

properties (e.g., “no null dereferences”)

  • no annotations but must

deal with false positives

The spectrum of program validation tools

3

Verification Static Analysis Extended Static Checking Concolic Testing & Whitebox Fuzzing Ad-hoc Testing Bounded Verification & Symbolic Execution

slide-9
SLIDE 9

Confidence Cost (programmer effort, time, expertise) E.g., Calysto, Saturn:

  • user-defined assertions

supported but optional

  • no annotations
  • some/low false positives

The spectrum of program validation tools

3

Verification Static Analysis Extended Static Checking Concolic Testing & Whitebox Fuzzing Ad-hoc Testing Bounded Verification & Symbolic Execution

slide-10
SLIDE 10

Confidence Cost (programmer effort, time, expertise) E.g., CBMC, Miniatur, Forge, TACO, JPF, Klee:

  • optional user-defined

harnesses, assertions, and/or FOL+ properties

  • no/low annotations
  • no/low false positives

The spectrum of program validation tools

3

Verification Static Analysis Extended Static Checking Concolic Testing & Whitebox Fuzzing Ad-hoc Testing Bounded Verification & Symbolic Execution

slide-11
SLIDE 11

Confidence Cost (programmer effort, time, expertise) E.g., SAGE, Pex, CUTE, DART:

  • test harnesses and/or

user-defined assertions

  • no annotations
  • no false positives

The spectrum of program validation tools

3

Verification Static Analysis Extended Static Checking Concolic Testing & Whitebox Fuzzing Ad-hoc Testing Bounded Verification & Symbolic Execution

slide-12
SLIDE 12

Confidence Cost (programmer effort, time, expertise)

The spectrum of program validation tools

3

Verification Static Analysis Extended Static Checking Concolic Testing & Whitebox Fuzzing Ad-hoc Testing Bounded Verification & Symbolic Execution

slide-13
SLIDE 13

Bounded verification

4

Bound everything

  • Execution length
  • Bitwidth
  • Heap size (number of objects per type)

Sound counterexamples but no proof

  • Exhaustive search within bounded scope

Empirical “small-scope hypothesis”

  • Bugs usually have small manifestations
slide-14
SLIDE 14

Bounded verification by example

5 class List { Node head; void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } } class Node { Node next; String data; } this n2 data: s1 head null n1 data: s2 next n0 data: null next next this n0 data: null head null n1 data: s2 next n2 data: s1 next next
slide-15
SLIDE 15

Bounded verification by example

5 class List { Node head; void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } } class Node { Node next; String data; } this n2 data: s1 head null n1 data: s2 next n0 data: null next next this n0 data: null head null n1 data: s2 next n2 data: s1 next next

Express the property either by writing a test harness or by providing FOL+ contracts.

slide-16
SLIDE 16

Pre/post/frame conditions & data invariants

6

@requires this.head != null && this.head.next != null

class List { Node head; void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } } class Node { Node next; String data; } this n2 data: s1 head null n1 data: s2 next n0 data: null next next this n0 data: null head null n1 data: s2 next n2 data: s1 next next
slide-17
SLIDE 17

Pre/post/frame conditions & data invariants

6

@requires this.head != null && this.head.next != null

@invariant no ^next ∩ iden

class List { Node head; void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } } class Node { Node next; String data; } this n2 data: s1 head null n1 data: s2 next n0 data: null next next this n0 data: null head null n1 data: s2 next n2 data: s1 next next
slide-18
SLIDE 18

@ensures this.head.*next = this.old(head).*old(next) &&

Pre/post/frame conditions & data invariants

6

@requires this.head != null && this.head.next != null

@invariant no ^next ∩ iden

class List { Node head; void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } } class Node { Node next; String data; } this n2 data: s1 head null n1 data: s2 next n0 data: null next next this n0 data: null head null n1 data: s2 next n2 data: s1 next next
slide-19
SLIDE 19

@ensures this.head.*next = this.old(head).*old(next) && let N = this.old(head).*old(next) - null | next = old(next) ++ this.old(head)×null ++ ~(old(next) ∩ N×N)

Pre/post/frame conditions & data invariants

6

@requires this.head != null && this.head.next != null

@invariant no ^next ∩ iden

class List { Node head; void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } } class Node { Node next; String data; } this n2 data: s1 head null n1 data: s2 next n0 data: null next next this n0 data: null head null n1 data: s2 next n2 data: s1 next next
slide-20
SLIDE 20

@ensures this.head.*next = this.old(head).*old(next) && let N = this.old(head).*old(next) - null | next = old(next) ++ this.old(head)×null ++ ~(old(next) ∩ N×N)

Pre/post/frame conditions & data invariants

6

@requires this.head != null && this.head.next != null

@invariant no ^next ∩ iden

class List { Node head; void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } } class Node { Node next; String data; }

Inv(next)

Pre(this, head, next) Post(this, old(head), head, old(next), next)

slide-21
SLIDE 21

A relational model of memory (heap)

7 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } this n2 data: s1 head null n1 data: s2 next n0 data: null next next
slide-22
SLIDE 22

Fields as binary relations

  • head : { ⟨this, n2⟩ }, next : { ⟨n2, n1⟩, … }

A relational model of memory (heap)

7 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } this n2 data: s1 head null n1 data: s2 next n0 data: null next next
slide-23
SLIDE 23

Fields as binary relations

  • head : { ⟨this, n2⟩ }, next : { ⟨n2, n1⟩, … }

Types as sets (unary relations)

  • List : { ⟨this⟩ }, Node : { ⟨n0⟩, ⟨n1⟩, ⟨n2⟩ }

A relational model of memory (heap)

7 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } this n2 data: s1 head null n1 data: s2 next n0 data: null next next
slide-24
SLIDE 24

Fields as binary relations

  • head : { ⟨this, n2⟩ }, next : { ⟨n2, n1⟩, … }

Types as sets (unary relations)

  • List : { ⟨this⟩ }, Node : { ⟨n0⟩, ⟨n1⟩, ⟨n2⟩ }

Objects as scalars (singleton sets)

  • this : { ⟨this⟩ }, null : { ⟨null⟩ }

A relational model of memory (heap)

7 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } this n2 data: s1 head null n1 data: s2 next n0 data: null next next
slide-25
SLIDE 25

Fields as binary relations

  • head : { ⟨this, n2⟩ }, next : { ⟨n2, n1⟩, … }

Types as sets (unary relations)

  • List : { ⟨this⟩ }, Node : { ⟨n0⟩, ⟨n1⟩, ⟨n2⟩ }

Objects as scalars (singleton sets)

  • this : { ⟨this⟩ }, null : { ⟨null⟩ }

Field read as relational join (.)

  • this.head : { ⟨this⟩ } . { ⟨this, n2⟩ } = { ⟨n2⟩ }

A relational model of memory (heap)

7 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } this n2 data: s1 head null n1 data: s2 next n0 data: null next next
slide-26
SLIDE 26

Fields as binary relations

  • head : { ⟨this, n2⟩ }, next : { ⟨n2, n1⟩, … }

Types as sets (unary relations)

  • List : { ⟨this⟩ }, Node : { ⟨n0⟩, ⟨n1⟩, ⟨n2⟩ }

Objects as scalars (singleton sets)

  • this : { ⟨this⟩ }, null : { ⟨null⟩ }

Field read as relational join (.)

  • this.head : { ⟨this⟩ } . { ⟨this, n2⟩ } = { ⟨n2⟩ }

Field write as relational override (++)

  • this.head = null : head ++ (this × null) =
{ ⟨this, n2⟩ } ++ { ⟨this, null⟩ } = { ⟨this, null⟩ }

A relational model of memory (heap)

7 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } this n2 data: s1 head null n1 data: s2 next n0 data: null next next
slide-27
SLIDE 27

Bounded verification: step 1/4

8 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; }
slide-28
SLIDE 28

Bounded verification: step 1/4

8 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; if (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } assume far == null; mid.next = near; head = mid; }

Execution finitization (inlining, unrolling, SSA)

slide-29
SLIDE 29

Bounded verification: step 1/4

8 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; while (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } mid.next = near; head = mid; } @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near = head; Node mid = near.next; Node far = mid.next; near.next = far; if (far != null) { mid.next = near; near = mid; mid = far; far = far.next; } assume far == null; mid.next = near; head = mid; } @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near0 = this.head; Node mid0 = near0.next; Node far0 = mid0.next; next0 = update(next, near0, far0); boolean guard = (far0 != null); next1 = update(next0, mid0, near0); near1 = mid0; mid1 = far0; far1 = far0.next1; near2 = phi(guard, near1, near0); mid2 = phi(guard, mid1, mid0); far2 = phi(guard, far1, far0); next2 = phi(guard, next1, next0); assume far2 == null; next3 = update(next2, mid2, near2); head0 = update(head, this, mid2); }

Execution finitization (inlining, unrolling, SSA)

slide-30
SLIDE 30 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near0 = this.head; Node mid0 = near0.next; Node far0 = mid0.next; next0 = update(next, near0, far0); boolean guard = (far0 != null); next1 = update(next0, mid0, near0); near1 = mid0; mid1 = far0; far1 = far0.next1; near2 = phi(guard, near1, near0); mid2 = phi(guard, mid1, mid0); far2 = phi(guard, far1, far0); next2 = phi(guard, next1, next0); assume far2 == null; next3 = update(next2, mid2, near2); head0 = update(head, this, mid2); }

Bounded verification: step 2/4

9

Forward VCG Execution finitization (inlining, unrolling, SSA) Symbolic interpretation of the code with respect to the relational heap model.

slide-31
SLIDE 31 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near0 = this.head; Node mid0 = near0.next; Node far0 = mid0.next; next0 = update(next, near0, far0); boolean guard = (far0 != null); next1 = update(next0, mid0, near0); near1 = mid0; mid1 = far0; far1 = far0.next1; near2 = phi(guard, near1, near0); mid2 = phi(guard, mid1, mid0); far2 = phi(guard, far1, far0); next2 = phi(guard, next1, next0); assume far2 == null; next3 = update(next2, mid2, near2); head0 = update(head, this, mid2); }

Bounded verification: step 2/4

9 this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ let near0 = this.head, mid0 = near0.next, far0 = mid0.next, next0 = next ++ (near0 × far0), guard = (far0 != null), next1 = next0 ++ (mid0 × near0), near1 = mid0, mid1 = far0, far1 = far0.next1, near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0, next3 = next2 ++ (mid2 × near2) head0 = head ++ (this × mid2) | far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ ¬ (Inv(next3) ∧ Post(this, head, head0, next, next3))
slide-32
SLIDE 32

Bounded verification: step 3/4

10 this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ let near0 = this.head, mid0 = near0.next, far0 = mid0.next, next0 = next ++ (near0 × far0), guard = (far0 != null), next1 = next0 ++ (mid0 × near0), near1 = mid0, mid1 = far0, far1 = far0.next1, near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0, next3 = next2 ++ (mid2 × near2) head0 = head ++ (this × mid2) | far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ ¬ (Inv(next3) ∧ Post(this, head, head0, next, next3))

Forward VCG Execution finitization (inlining, unrolling, SSA) Heap finitization (bounds for types, fields)

slide-33
SLIDE 33 { this, n0, n1, n2, s0, s1, s2, null } { ⟨null⟩ } ⊆ null ⊆ { ⟨null⟩ } {} ⊆ this ⊆ { ⟨ this ⟩ } {} ⊆ List ⊆ { ⟨ this ⟩ } {} ⊆ Node ⊆ { ⟨n0⟩, ⟨n1⟩, ⟨n2⟩ } {} ⊆ String ⊆ { ⟨s0⟩, ⟨s1⟩, ⟨s2⟩ } {} ⊆ head ⊆ { this } × { n0, n1, n2, null } {} ⊆ next ⊆ { n0, n1, n2 } × { n0, n1, n2, null } {} ⊆ data ⊆ { n0, n1, n2 } × { s0, s1, s2, null }

Bounded verification: step 3/4

10 this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ let near0 = this.head, mid0 = near0.next, far0 = mid0.next, next0 = next ++ (near0 × far0), guard = (far0 != null), next1 = next0 ++ (mid0 × near0), near1 = mid0, mid1 = far0, far1 = far0.next1, near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0, next3 = next2 ++ (mid2 × near2) head0 = head ++ (this × mid2) | far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ ¬ (Inv(next3) ∧ Post(this, head, head0, next, next3))
slide-34
SLIDE 34 { this, n0, n1, n2, s0, s1, s2, null } { ⟨null⟩ } ⊆ null ⊆ { ⟨null⟩ } {} ⊆ this ⊆ { ⟨ this ⟩ } {} ⊆ List ⊆ { ⟨ this ⟩ } {} ⊆ Node ⊆ { ⟨n0⟩, ⟨n1⟩, ⟨n2⟩ } {} ⊆ String ⊆ { ⟨s0⟩, ⟨s1⟩, ⟨s2⟩ } {} ⊆ head ⊆ { this } × { n0, n1, n2, null } {} ⊆ next ⊆ { n0, n1, n2 } × { n0, n1, n2, null } {} ⊆ data ⊆ { n0, n1, n2 } × { s0, s1, s2, null }

Bounded verification: step 3/4

10 this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ let near0 = this.head, mid0 = near0.next, far0 = mid0.next, next0 = next ++ (near0 × far0), guard = (far0 != null), next1 = next0 ++ (mid0 × near0), near1 = mid0, mid1 = far0, far1 = far0.next1, near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0, next3 = next2 ++ (mid2 × near2) head0 = head ++ (this × mid2) | far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ ¬ (Inv(next3) ∧ Post(this, head, head0, next, next3))

Finite universe of uninterpreted symbols.

slide-35
SLIDE 35 { this, n0, n1, n2, s0, s1, s2, null } { ⟨null⟩ } ⊆ null ⊆ { ⟨null⟩ } {} ⊆ this ⊆ { ⟨ this ⟩ } {} ⊆ List ⊆ { ⟨ this ⟩ } {} ⊆ Node ⊆ { ⟨n0⟩, ⟨n1⟩, ⟨n2⟩ } {} ⊆ String ⊆ { ⟨s0⟩, ⟨s1⟩, ⟨s2⟩ } {} ⊆ head ⊆ { this } × { n0, n1, n2, null } {} ⊆ next ⊆ { n0, n1, n2 } × { n0, n1, n2, null } {} ⊆ data ⊆ { n0, n1, n2 } × { s0, s1, s2, null }

Bounded verification: step 3/4

10 this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ let near0 = this.head, mid0 = near0.next, far0 = mid0.next, next0 = next ++ (near0 × far0), guard = (far0 != null), next1 = next0 ++ (mid0 × near0), near1 = mid0, mid1 = far0, far1 = far0.next1, near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0, next3 = next2 ++ (mid2 × near2) head0 = head ++ (this × mid2) | far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ ¬ (Inv(next3) ∧ Post(this, head, head0, next, next3))

Finite universe of uninterpreted symbols. Upper bound

  • n each relation:

tuples it may contain.

slide-36
SLIDE 36 { this, n0, n1, n2, s0, s1, s2, null } { ⟨null⟩ } ⊆ null ⊆ { ⟨null⟩ } {} ⊆ this ⊆ { ⟨ this ⟩ } {} ⊆ List ⊆ { ⟨ this ⟩ } {} ⊆ Node ⊆ { ⟨n0⟩, ⟨n1⟩, ⟨n2⟩ } {} ⊆ String ⊆ { ⟨s0⟩, ⟨s1⟩, ⟨s2⟩ } {} ⊆ head ⊆ { this } × { n0, n1, n2, null } {} ⊆ next ⊆ { n0, n1, n2 } × { n0, n1, n2, null } {} ⊆ data ⊆ { n0, n1, n2 } × { s0, s1, s2, null }

Bounded verification: step 3/4

10 this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ let near0 = this.head, mid0 = near0.next, far0 = mid0.next, next0 = next ++ (near0 × far0), guard = (far0 != null), next1 = next0 ++ (mid0 × near0), near1 = mid0, mid1 = far0, far1 = far0.next1, near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0, next3 = next2 ++ (mid2 × near2) head0 = head ++ (this × mid2) | far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ ¬ (Inv(next3) ∧ Post(this, head, head0, next, next3))

Finite universe of uninterpreted symbols. Upper bound

  • n each relation:

tuples it may contain. Lower bound

  • n each relation:

tuples it must contain.

slide-37
SLIDE 37

Bounded verification: step 4/4

11

Forward VCG Execution finitization (inlining, unrolling, SSA) Heap finitization (bounds for types, fields) Solver

this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ let near0 = this.head, mid0 = near0.next, far0 = mid0.next, next0 = next ++ (near0 × far0), guard = (far0 != null), next1 = next0 ++ (mid0 × near0), near1 = mid0, mid1 = far0, far1 = far0.next1, near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0, next3 = next2 ++ (mid2 × near2) head0 = head ++ (this × mid2) | far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ ¬ (Inv(next3) ∧ Post(this, head, head0, next, next3)) { this, n0, n1, n2, s0, s1, s2, null } { ⟨null⟩ } ⊆ null ⊆ { ⟨null⟩ } {} ⊆ this ⊆ { ⟨ this ⟩ } {} ⊆ List ⊆ { ⟨ this ⟩ } {} ⊆ Node ⊆ { ⟨n0⟩, ⟨n1⟩, ⟨n2⟩ } {} ⊆ String ⊆ { ⟨s0⟩, ⟨s1⟩, ⟨s2⟩ } {} ⊆ head ⊆ { this } × { n0, n1, n2, null } {} ⊆ next ⊆ { n0, n1, n2 } × { n0, n1, n2, null } {} ⊆ data ⊆ { n0, n1, n2 } × { s0, s1, s2, null }
slide-38
SLIDE 38

Bounded verification: counterexample

12 this n0 data: null head n1 data: s2 next n2 data: s1 next next this n2 data: s1 head null n1 data: s2 next n0 data: null next next
slide-39
SLIDE 39

Bounded verification: optimization

13

Forward VCG Execution finitization (inlining, unrolling, SSA) Heap finitization (bounds for types, fields) Solver

slide-40
SLIDE 40

Bounded verification: optimization

13

Forward VCG Execution finitization (inlining, unrolling, SSA) Heap finitization (bounds for types, fields) Solver Finitized program after inlining may be huge.

slide-41
SLIDE 41

Bounded verification: optimization

13

Forward VCG Execution finitization (inlining, unrolling, SSA) Heap finitization (bounds for types, fields) Solver Finitized program after inlining may be huge. Full inlining is rarely needed to check partial correctness.

slide-42
SLIDE 42

Bounded verification: optimization

13

Forward VCG Execution finitization (inlining, unrolling, SSA) Heap finitization (bounds for types, fields) Solver Finitized program after inlining may be huge. Full inlining is rarely needed to check partial correctness. Optimization: Counterexample- Guided Abstraction Refinement with Unsatisfiable Cores [Taghdiri, 2004]

slide-43
SLIDE 43

From bounded verification to fault localization

14 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near0 = this.head; Node mid0 = near0.next; Node far0 = mid0.next; next0 = update(next, near0, far0); boolean guard = (far0 != null); next1 = update(next0, mid0, near0); near1 = mid0; mid1 = far0; far1 = far0.next1; near2 = phi(guard, near1, near0); mid2 = phi(guard, mid1, mid0); far2 = phi(guard, far1, far0); next2 = phi(guard, next1, next0); assume far2 == null; next3 = update(next2, mid2, near2); head0 = update(head, this, mid2); }
slide-44
SLIDE 44

From bounded verification to fault localization

14 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near0 = this.head; Node mid0 = near0.next; Node far0 = mid0.next; next0 = update(next, near0, far0); boolean guard = (far0 != null); next1 = update(next0, mid0, near0); near1 = mid0; mid1 = far0; far1 = far0.next1; near2 = phi(guard, near1, near0); mid2 = phi(guard, mid1, mid0); far2 = phi(guard, far1, far0); next2 = phi(guard, next1, next0); assume far2 == null; next3 = update(next2, mid2, near2); head0 = update(head, this, mid2); }

Given a buggy program and a valid input and the expected output, find a minimal subset of program statements that prevents the execution on the given input from reaching a valid

  • utput state.
slide-45
SLIDE 45

From bounded verification to fault localization

14 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near0 = this.head; Node mid0 = near0.next; Node far0 = mid0.next; next0 = update(next, near0, far0); boolean guard = (far0 != null); next1 = update(next0, mid0, near0); near1 = mid0; mid1 = far0; far1 = far0.next1; near2 = phi(guard, near1, near0); mid2 = phi(guard, mid1, mid0); far2 = phi(guard, far1, far0); next2 = phi(guard, next1, next0); assume far2 == null; next3 = update(next2, mid2, near2); head0 = update(head, this, mid2); }

Given a buggy program and a valid input and the expected output, find a minimal subset of program statements that prevents the execution on the given input from reaching a valid

  • utput state.

Introduce additional “indicator” relations into the encoding.

slide-46
SLIDE 46

From bounded verification to fault localization

14 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near0 = this.head; Node mid0 = near0.next; Node far0 = mid0.next; next0 = update(next, near0, far0); boolean guard = (far0 != null); next1 = update(next0, mid0, near0); near1 = mid0; mid1 = far0; far1 = far0.next1; near2 = phi(guard, near1, near0); mid2 = phi(guard, mid1, mid0); far2 = phi(guard, far1, far0); next2 = phi(guard, next1, next0); assume far2 == null; next3 = update(next2, mid2, near2); head0 = update(head, this, mid2); }

Given a buggy program and a valid input and the expected output, find a minimal subset of program statements that prevents the execution on the given input from reaching a valid

  • utput state.

Introduce additional “indicator” relations into the encoding. The resulting formula, together with the input partial model, is unsatisfiable.

slide-47
SLIDE 47

From bounded verification to fault localization

14 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near0 = this.head; Node mid0 = near0.next; Node far0 = mid0.next; next0 = update(next, near0, far0); boolean guard = (far0 != null); next1 = update(next0, mid0, near0); near1 = mid0; mid1 = far0; far1 = far0.next1; near2 = phi(guard, near1, near0); mid2 = phi(guard, mid1, mid0); far2 = phi(guard, far1, far0); next2 = phi(guard, next1, next0); assume far2 == null; next3 = update(next2, mid2, near2); head0 = update(head, this, mid2); }

Given a buggy program and a valid input and the expected output, find a minimal subset of program statements that prevents the execution on the given input from reaching a valid

  • utput state.

Introduce additional “indicator” relations into the encoding. The resulting formula, together with the input partial model, is unsatisfiable. A minimal unsatisfiable core of this formula represents an irreducible cause of the program’s failure to meet the specification.

slide-48
SLIDE 48 this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ let near0 = this.head, mid0 = near0.next, far0 = mid0.next, next0 = next ++ (near0 × far0), guard = (far0 != null), next1 = next0 ++ (mid0 × near0), near1 = mid0, mid1 = far0, far1 = far0.next1, near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0, next3 = next2 ++ (mid2 × near2) head0 = head ++ (this × mid2) | far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ ¬ (Inv(next3) ∧ Post(this, head, head0, next, next3))

Fault localization: encoding

15 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near0 = this.head; Node mid0 = near0.next; Node far0 = mid0.next; next0 = update(next, near0, far0); boolean guard = (far0 != null); next1 = update(next0, mid0, near0); near1 = mid0; mid1 = far0; far1 = far0.next1; near2 = phi(guard, near1, near0); mid2 = phi(guard, mid1, mid0); far2 = phi(guard, far1, far0); next2 = phi(guard, next1, next0); assume far2 == null; next3 = update(next2, mid2, near2); head0 = update(head, this, mid2); }

Start with the encoding for bounded verification.

slide-49
SLIDE 49 this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ let near0 = this.head, mid0 = near0.next, far0 = mid0.next, next0 = next ++ (near0 × far0), guard = (far0 != null), next1 = next0 ++ (mid0 × near0), near1 = mid0, mid1 = far0, far1 = far0.next1, near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0, next3 = next2 ++ (mid2 × near2) head0 = head ++ (this × mid2) | far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ ¬ (Inv(next3) ∧ Post(this, head, head0, next, next3)) this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ near0 = this.head ∧ mid0 = near0.next ∧ far0 = mid0.next ∧ next0 = next ++ (near0 × far0) ∧ next1 = next0 ++ (mid0 × near0) ∧ near1 = mid0 ∧ mid1 = far0 ∧ far1 = far0.next1 ∧ let guard = (far0 != null), near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0 | next3 = next2 ++ (mid2 × near2) ∧ head0 = head ++ (this × mid2) ∧ far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ Inv(next3) ∧ Post(this, head, head0, next, next3)

Fault localization: encoding

15 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near0 = this.head; Node mid0 = near0.next; Node far0 = mid0.next; next0 = update(next, near0, far0); boolean guard = (far0 != null); next1 = update(next0, mid0, near0); near1 = mid0; mid1 = far0; far1 = far0.next1; near2 = phi(guard, near1, near0); mid2 = phi(guard, mid1, mid0); far2 = phi(guard, far1, far0); next2 = phi(guard, next1, next0); assume far2 == null; next3 = update(next2, mid2, near2); head0 = update(head, this, mid2); }

Introduce fresh relations for source- level expressions.

slide-50
SLIDE 50 this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ near0 = this.head ∧ mid0 = near0.next ∧ far0 = mid0.next ∧ next0 = next ++ (near0 × far0) ∧ next1 = next0 ++ (mid0 × near0) ∧ near1 = mid0 ∧ mid1 = far0 ∧ far1 = far0.next1 ∧ let guard = (far0 != null), near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0 | next3 = next2 ++ (mid2 × near2) ∧ head0 = head ++ (this × mid2) ∧ far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ Inv(next3) ∧ Post(this, head, head0, next, next3)

Fault localization: bounds

16 { this, n0, n1, n2, s0, s1, s2, null } null = { <null> } this = { <this> } List = { <this> } Node = { <n0>, <n1>, <n2> } String = { <s1>, <s2> } head = { <this, n2> } next = { <n2, n1>, <n1, n0>, <n0, null> } data = { <n2, s1>, <n1, s2>, <n0, null> } {} ⊆ head0 ⊆ { this } × { n0, n1, n2, null } {} ⊆ next0 ⊆ { n0, n1, n2 } × { n0, n1, n2, null } {} ⊆ next1 ⊆ { n0, n1, n2 } × { n0, n1, n2, null } {} ⊆ next3 ⊆ { n0, n1, n2 } × { n0, n1, n2, null } {} ⊆ near0 ⊆ { n0, n1, n2, null } {} ⊆ near1 ⊆ { n0, n1, n2, null } {} ⊆ mid0 ⊆ { n0, n1, n2, null } {} ⊆ mid1 ⊆ { n0, n1, n2, null } {} ⊆ far0 ⊆ { n0, n1, n2, null } {} ⊆ far1 ⊆ { n0, n1, n2, null }

Input expressed as a partial model.

slide-51
SLIDE 51 this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ near0 = this.head ∧ mid0 = near0.next ∧ far0 = mid0.next ∧ next0 = next ++ (near0 × far0) ∧ next1 = next0 ++ (mid0 × near0) ∧ near1 = mid0 ∧ mid1 = far0 ∧ far1 = far0.next1 ∧ let guard = (far0 != null), near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0 | next3 = next2 ++ (mid2 × near2) ∧ head0 = head ++ (this × mid2) ∧ far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ Inv(next3) ∧ Post(this, head, head0, next, next3)

Fault localization: bounds

16
slide-52
SLIDE 52 this ⊆ List ∧ one this ∧ head ⊆ List ↦ (Node ∪ null) ∧ next ⊆ Node ↦ (Node ∪ null) ∧ data ⊆ Node ↦ (String ∪ null) ∧ near0 = this.head ∧ mid0 = near0.next ∧ far0 = mid0.next ∧ next0 = next ++ (near0 × far0) ∧ next1 = next0 ++ (mid0 × near0) ∧ near1 = mid0 ∧ mid1 = far0 ∧ far1 = far0.next1 ∧ let guard = (far0 != null), near2 = if guard then near1 else near0, mid2 = if guard then mid1 else mid0, far2 = if guard then far1 else far0, next2 = if guard then next1 else next0 | next3 = next2 ++ (mid2 × near2) ∧ head0 = head ++ (this × mid2) ∧ far2 = null ∧ Inv(next) ∧ Pre(this, head, next) ∧ Inv(next3) ∧ Post(this, head, head0, next, next3)

Fault localization: minimal unsat core

17 { this, n0, n1, n2, s0, s1, s2, null } null = { <null> } this = { <this> } List = { <this> } Node = { <n0>, <n1>, <n2> } String = { <s1>, <s2> } head = { <this, n2> } next = { <n2, n1>, <n1, n0>, <n0, null> } data = { <n2, s1>, <n1, s2>, <n0, null> } {} ⊆ head0 ⊆ { this } × { n0, n1, n2, null } {} ⊆ next0 ⊆ { n0, n1, n2 } × { n0, n1, n2, null } {} ⊆ next1 ⊆ { n0, n1, n2 } × { n0, n1, n2, null } {} ⊆ next3 ⊆ { n0, n1, n2 } × { n0, n1, n2, null } {} ⊆ near0 ⊆ { n0, n1, n2, null } {} ⊆ near1 ⊆ { n0, n1, n2, null } {} ⊆ mid0 ⊆ { n0, n1, n2, null } {} ⊆ mid1 ⊆ { n0, n1, n2, null } {} ⊆ far0 ⊆ { n0, n1, n2, null } {} ⊆ far1 ⊆ { n0, n1, n2, null }
slide-53
SLIDE 53

Fault localization: minimal unsat core

18 @invariant Inv(next) @requires Pre(this, head, next) @ensures Post(this, old(head), head, old(next), next) void reverse() { Node near0 = this.head; Node mid0 = near0.next; Node far0 = mid0.next; next0 = update(next, near0, far0); boolean guard = (far0 != null); next1 = update(next0, mid0, near0); near1 = mid0; mid1 = far0; far1 = far0.next1; near2 = phi(guard, near1, near0); mid2 = phi(guard, mid1, mid0); far2 = phi(guard, far1, far0); next2 = phi(guard, next1, next0); assume far2 == null; next3 = update(next2, mid2, near2); head0 = update(head, this, mid2); }
slide-54
SLIDE 54

Summary

19

Today

  • Bounded verification
  • A relational model of the heap
  • CEGAR with unsat cores
  • Fault localization

Next lecture

  • Symbolic execution and concolic testing