Separation Logic for Non-local Control Flow and Block Scope - - PowerPoint PPT Presentation
Separation Logic for Non-local Control Flow and Block Scope - - PowerPoint PPT Presentation
Separation Logic for Non-local Control Flow and Block Scope Variables Robbert Krebbers Joint work with Freek Wiedijk Radboud University Nijmegen February 4, 2013 @ Gallium, INRIA Rocquencourt, France What is this program supposed to do? int
What is this program supposed to do?
int *p = NULL; l: if (p) { return (*p); } else { int j = 10; p = &j; goto l; }
What is this program supposed to do?
int *p = NULL; l: if (p) { return (*p); } else { int j = 10; p = &j; goto l; } memory: p NULL
What is this program supposed to do?
int *p = NULL; l: if (p) { return (*p); } else { int j = 10; p = &j; goto l; } memory: p NULL
What is this program supposed to do?
int *p = NULL; l: if (p) { return (*p); } else { int j = 10; p = &j; goto l; } memory: p NULL j 10
What is this program supposed to do?
int *p = NULL; l: if (p) { return (*p); } else { int j = 10; p = &j; goto l; } memory: p
- j
10
What is this program supposed to do?
int *p = NULL; l: if (p) { return (*p); } else { int j = 10; p = &j; goto l; } memory: p
- j
10
What is this program supposed to do?
int *p = NULL; l: if (p) { return (*p); } else { int j = 10; p = &j; goto l; } memory: p
What is this program supposed to do?
int *p = NULL; l: if (p) { return (*p); } else { int j = 10; p = &j; goto l; } memory: p
What is this program supposed to do?
int *p = NULL; l: if (p) { return (*p); } else { int j = 10; p = &j; goto l; } memory: p
- It exhibits undefined behavior, thus it may do anything
Why is catching undefined behavior important
◮ C allows a program to do anything on undefined behavior
Why is catching undefined behavior important
◮ C allows a program to do anything on undefined behavior ◮ It cannot be checked statically
Why is catching undefined behavior important
◮ C allows a program to do anything on undefined behavior ◮ It cannot be checked statically ◮ Not catching it means that
◮ programs can be proven to be correct with respect to the
formal semantics . . .
Why is catching undefined behavior important
◮ C allows a program to do anything on undefined behavior ◮ It cannot be checked statically ◮ Not catching it means that
◮ programs can be proven to be correct with respect to the
formal semantics . . .
◮ whereas they may crash when compiled with an actual compiler
Goto considered harmful?
http://xkcd.com/292/
Goto considered harmful?
http://xkcd.com/292/ Not necessarily: ⊢ {P} . . . goto main_sub3; . . . {Q}
Why goto
Goto can be useful
◮ Breaking from multiple nested loops for (int i = 0; i++; i < n) { // do something here for (int j = i; j++; j < m) { // do some work here goto outer; } }
- uter:;
Why goto
Goto can be useful
◮ Breaking from multiple nested loops ◮ Systematically cleaning up resources after errors if (!openDataFile()) goto quit; if (!getDataFromFile()) goto closeFileAndQuit; if (!allocateSomeResources) goto freeResourcesAndQuit; // do actual work here freeResourcesAndQuit: // free resources closeFileAndQuit: // close file quit: // quit!
Why goto
Goto can be useful
◮ Breaking from multiple nested loops ◮ Systematically cleaning up resources after errors ◮ To increase performance
Why goto
Goto can be useful
◮ Breaking from multiple nested loops ◮ Systematically cleaning up resources after errors ◮ To increase performance
Goto is used in practice
◮ The Linux kernel contains about ∼ 100.000 uses of goto $ grep -w -r goto ~/src/linux-3.5.5 --include \*.c | wc -l 101026 $ find ./ -name \*.c -exec cat ’{}’ \; | wc -l 11152601
This talk
An elegant small step semantics, and axiomatic semantics for goto, supporting:
◮ local variables (and pointers to those), ◮ mutual recursion, ◮ separation logic, ◮ soundness proof fully checked by Coq
Approach
◮ Small step semantics by traversal through the program
Approach
◮ Small step semantics by traversal through the program ◮ Traversal in four directions:
◮ ց downwards to the next statement ◮ ր upwards to the next statement
Approach
◮ Small step semantics by traversal through the program ◮ Traversal in four directions:
◮ ց downwards to the next statement ◮ ր upwards to the next statement ◮ l to a label l: after a goto l
Approach
◮ Small step semantics by traversal through the program ◮ Traversal in four directions:
◮ ց downwards to the next statement ◮ ր upwards to the next statement ◮ l to a label l: after a goto l ◮ ↑
↑ to the top after a return
Approach
◮ Small step semantics by traversal through the program ◮ Traversal in four directions:
◮ ց downwards to the next statement ◮ ր upwards to the next statement ◮ l to a label l: after a goto l ◮ ↑
↑ to the top after a return
◮ Gotos and returns are also executed in small steps
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ց
memory: p NULL
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ց
memory: p NULL
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ց
memory: p NULL
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ց
memory: p NULL j 10
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ց
memory: p NULL j 10
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ց
memory: p
- j
10
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ր
memory: p
- j
10
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ր
memory: p
- j
10
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ց
memory: p
- j
10
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ց
memory: p
- j
10
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
l
memory: p
- j
10
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
l
memory: p
- j
10
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
l
memory: p
- j
10
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
l
memory: p
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
l
memory: p
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ց
memory: p
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ց
memory: p
Example
int *p = NULL l: if (p) return (*p) int j = 10 ; p = &j goto l direction:
ց
memory: p
How to model the current location in the program
Huet’s zipper Purely functional way to store a pointer into a data structure
Statement contexts
◮ Statements:
s ::= block s | el := er | f ( e) | skip | goto l | l : s | s1 ; s2 | if (e) s1 s2 | return
Statement contexts
◮ Statements:
s ::= block s | el := er | f ( e) | skip | goto l | l : s | s1 ; s2 | if (e) s1 s2 | return
◮ Singular statement contexts:
ES ::= ; s2 | s1 ; | if (e) s2 | if (e) s1 | l :
Statement contexts
◮ Statements:
s ::= block s | el := er | f ( e) | skip | goto l | l : s | s1 ; s2 | if (e) s1 s2 | return
◮ Singular statement contexts:
ES ::= ; s2 | s1 ; | if (e) s2 | if (e) s1 | l :
◮ A pair (
ES, s) forms a zipper for statements, where
◮
- ES is a statement turned inside-out
◮ s is the focused substatement
Stacks
◮ A stack is a list of memory indexes ◮ A variable xi refers to the ith element on the stack
Stack x0
- x1
- x2
- Memory
10
- 12
Stacks
◮ A stack is a list of memory indexes ◮ A variable xi refers to the ith element on the stack
Stack x0
- x1
- x2
- Memory
10
- 12
◮ Uniform way of dealing with pointers to local variables
Program contexts (1)
Singular program contexts: E ::= ES | blockb | call f e | params b where:
Program contexts (1)
Singular program contexts: E ::= ES | blockb | call f e | params b where:
◮ blockb associates a block scope variable with its
corresponding memory index b
Program contexts (1)
Singular program contexts: E ::= ES | blockb | call f e | params b where:
◮ blockb associates a block scope variable with its
corresponding memory index b
◮ call f
e contains the location of the caller so that it can be restored when f returns
Program contexts (1)
Singular program contexts: E ::= ES | blockb | call f e | params b where:
◮ blockb associates a block scope variable with its
corresponding memory index b
◮ call f
e contains the location of the caller so that it can be restored when f returns
◮ params
b contains the memory indexes of the function parameters
Program contexts (1)
Singular program contexts: E ::= ES | blockb | call f e | params b where:
◮ blockb associates a block scope variable with its
corresponding memory index b
◮ call f
e contains the location of the caller so that it can be restored when f returns
◮ params
b contains the memory indexes of the function parameters Program contexts k are lists of singular program contexts
Program contexts (2)
◮ Program contexts contain the stack:
getstack (ES :: k) := getstack k getstack (blockb :: k) := b :: getstack k getstack (call f e :: k) := [ ] getstack (params b :: k) := b + + getstack k
Program contexts (2)
◮ Program contexts contain the stack:
getstack (ES :: k) := getstack k getstack (blockb :: k) := b :: getstack k getstack (call f e :: k) := [ ] getstack (params b :: k) := b + + getstack k
◮ Remark: not getstack (call f
e :: k) := getstack k
States
A state S(k, φ, m) consists of a program context k, focus φ, and memory m
States
A state S(k, φ, m) consists of a program context k, focus φ, and memory m We consider the following focuses:
◮ (d, s) execution of a statement s in direction d
States
A state S(k, φ, m) consists of a program context k, focus φ, and memory m We consider the following focuses:
◮ (d, s) execution of a statement s in direction d ◮ call f
v calling a function f ( v)
States
A state S(k, φ, m) consists of a program context k, focus φ, and memory m We consider the following focuses:
◮ (d, s) execution of a statement s in direction d ◮ call f
v calling a function f ( v)
◮ return returning from a function
Example
int *p = NULL l: if (p) return int j = 10 ; p = &j goto l
The corresponding state is S(k, φ, m), where:
◮ k = [
; goto l, x0 := int 10 ; , blockbj , if (load x0) return , l : , x0 := NULL ; , blockbp ]
◮ φ = (ր, x1 := x0) ◮ m = {bp → ptr bj, bj → 10}
The small step semantics
Some rules:
◮ S(k, (ց, e1 := e2), m) S(k, (ր, e1 := e2), m[a := v])
for a and v s.t. [ [ e1 ] ]k,m = ptr a, [ [ e2 ] ]k,m = v and m a = ⊥.
The small step semantics
Some rules:
◮ S(k, (ց, e1 := e2), m) S(k, (ր, e1 := e2), m[a := v])
for a and v s.t. [ [ e1 ] ]k,m = ptr a, [ [ e2 ] ]k,m = v and m a = ⊥.
◮ S(k, (ց, s1 ; s2), m) S(( ; s2) :: k, (ց, s1), m)
The small step semantics
Some rules:
◮ S(k, (ց, e1 := e2), m) S(k, (ր, e1 := e2), m[a := v])
for a and v s.t. [ [ e1 ] ]k,m = ptr a, [ [ e2 ] ]k,m = v and m a = ⊥.
◮ S(k, (ց, s1 ; s2), m) S(( ; s2) :: k, (ց, s1), m) ◮ S(( ; s2) :: k, (ր, s1), m) S((s1 ; ) :: k, (ց, s2), m)
The small step semantics
Some rules:
◮ S(k, (ց, e1 := e2), m) S(k, (ր, e1 := e2), m[a := v])
for a and v s.t. [ [ e1 ] ]k,m = ptr a, [ [ e2 ] ]k,m = v and m a = ⊥.
◮ S(k, (ց, s1 ; s2), m) S(( ; s2) :: k, (ց, s1), m) ◮ S(( ; s2) :: k, (ր, s1), m) S((s1 ; ) :: k, (ց, s2), m) ◮ S((s1 ; ) :: k, (ր, s2), m) S(k, (ր, s1 ; s2), m)
The small step semantics
Some rules:
◮ S(k, (ց, e1 := e2), m) S(k, (ր, e1 := e2), m[a := v])
for a and v s.t. [ [ e1 ] ]k,m = ptr a, [ [ e2 ] ]k,m = v and m a = ⊥.
◮ S(k, (ց, s1 ; s2), m) S(( ; s2) :: k, (ց, s1), m) ◮ S(( ; s2) :: k, (ր, s1), m) S((s1 ; ) :: k, (ց, s2), m) ◮ S((s1 ; ) :: k, (ր, s2), m) S(k, (ր, s1 ; s2), m) ◮ S(k, (ց, goto l), m) S(k, ( l, goto l), m)
The small step semantics
Some rules:
◮ S(k, (ց, e1 := e2), m) S(k, (ր, e1 := e2), m[a := v])
for a and v s.t. [ [ e1 ] ]k,m = ptr a, [ [ e2 ] ]k,m = v and m a = ⊥.
◮ S(k, (ց, s1 ; s2), m) S(( ; s2) :: k, (ց, s1), m) ◮ S(( ; s2) :: k, (ր, s1), m) S((s1 ; ) :: k, (ց, s2), m) ◮ S((s1 ; ) :: k, (ր, s2), m) S(k, (ր, s1 ; s2), m) ◮ S(k, (ց, goto l), m) S(k, ( l, goto l), m) ◮ S(k, ( l, l : s), m) S((l : ) :: k, (ց, s), m)
The small step semantics
Some rules:
◮ S(k, (ց, e1 := e2), m) S(k, (ր, e1 := e2), m[a := v])
for a and v s.t. [ [ e1 ] ]k,m = ptr a, [ [ e2 ] ]k,m = v and m a = ⊥.
◮ S(k, (ց, s1 ; s2), m) S(( ; s2) :: k, (ց, s1), m) ◮ S(( ; s2) :: k, (ր, s1), m) S((s1 ; ) :: k, (ց, s2), m) ◮ S((s1 ; ) :: k, (ր, s2), m) S(k, (ր, s1 ; s2), m) ◮ S(k, (ց, goto l), m) S(k, ( l, goto l), m) ◮ S(k, ( l, l : s), m) S((l : ) :: k, (ց, s), m) ◮ S(k, (ց, f (
e)), m) S(call f e :: k, call f v, m) provided that [ [ ei ] ]k,m = vi for each i
The small step semantics
Lemma
The small step semantics behaves as traversing through a zipper. That is, if S(k, (d, s), m) ∗
k S(k, (d′, s′), m′)
then s = s′.
Hoare triples
Traditional Hoare triples are of the shape {P} s {Q} Intuitive meaning:
◮ If P holds for the state before execution of s, ◮ and execution of s terminates, ◮ then Q will hold afterwards
Extended Hoare ‘triples’ (1)
Our Hoare sextuples are of the shape ∆; J; R ⊢ {P} s {Q}
Extended Hoare ‘triples’ (1)
Our Hoare sextuples are of the shape ∆; J; R ⊢ {P} s {Q} where:
◮ ∆ maps function names to their pre- and postconditions
Extended Hoare ‘triples’ (1)
Our Hoare sextuples are of the shape ∆; J; R ⊢ {P} s {Q} where:
◮ ∆ maps function names to their pre- and postconditions ◮ J maps labels to their jumping condition
When executing a goto l, the assertion J l has to hold
Extended Hoare ‘triples’ (1)
Our Hoare sextuples are of the shape ∆; J; R ⊢ {P} s {Q} where:
◮ ∆ maps function names to their pre- and postconditions ◮ J maps labels to their jumping condition
When executing a goto l, the assertion J l has to hold
◮ R has to hold to execute a return
Extended Hoare ‘triples’ (2)
Our Hoare sextuples are of the shape ∆; J; R ⊢ {P} s {Q} Observations:
◮ The assertions P, Q, J and R correspond to the four
directions ց, ր, and ↑
↑
Extended Hoare ‘triples’ (2)
Our Hoare sextuples are of the shape ∆; J; R ⊢ {P} s {Q} Observations:
◮ The assertions P, Q, J and R correspond to the four
directions ց, ր, and ↑
↑ ◮ We thus treat the sextuple as
∆; ¯ P ⊢ s where ¯ P ց = P, ¯ P ր = Q, ¯ P ( l) = J l and ¯ P ↑
↑ = R
Some Hoare rules
Weakening: (∀l ∈ labels s . J′l → Jl) (∀l / ∈ labels s . Jl → J′l) R → R′ P′ → P ∆; J; R ⊢ {P} s {Q} Q → Q′ ∆; J′; R′ ⊢ {P′} s {Q′}
Some Hoare rules
Weakening: (∀l ∈ labels s . J′l → Jl) (∀l / ∈ labels s . Jl → J′l) R → R′ P′ → P ∆; J; R ⊢ {P} s {Q} Q → Q′ ∆; J′; R′ ⊢ {P′} s {Q′} Composition: ∆; J; R ⊢ {P} s1 {P′} ∆; J; R ⊢ {P′} s2 {Q} ∆; J; R ⊢ {P} s1 ; s2 {Q}
Some Hoare rules
Weakening: (∀l ∈ labels s . J′l → Jl) (∀l / ∈ labels s . Jl → J′l) R → R′ P′ → P ∆; J; R ⊢ {P} s {Q} Q → Q′ ∆; J′; R′ ⊢ {P′} s {Q′} Composition: ∆; J; R ⊢ {P} s1 {P′} ∆; J; R ⊢ {P′} s2 {Q} ∆; J; R ⊢ {P} s1 ; s2 {Q} Non-local control: ∆; J; R ⊢ {R} return {Q}
Some Hoare rules
Weakening: (∀l ∈ labels s . J′l → Jl) (∀l / ∈ labels s . Jl → J′l) R → R′ P′ → P ∆; J; R ⊢ {P} s {Q} Q → Q′ ∆; J′; R′ ⊢ {P′} s {Q′} Composition: ∆; J; R ⊢ {P} s1 {P′} ∆; J; R ⊢ {P′} s2 {Q} ∆; J; R ⊢ {P} s1 ; s2 {Q} Non-local control: ∆; J; R ⊢ {R} return {Q} ∆; J; R ⊢ {J l} goto l {Q} ∆; J; R ⊢ {J l} s {Q} ∆; J; R ⊢ {J l} l : s {Q}
The frame rule
Used for local reasoning ∆; J; R ⊢ {P} s {Q} ∆; J ∗ A; R ∗ A ⊢ {P ∗ A} s {Q ∗ A}
The frame rule
Used for local reasoning ∆; J; R ⊢ {P} s {Q} ∆; J ∗ A; R ∗ A ⊢ {P ∗ A} s {Q ∗ A} Using our alternative notation: ∆; ¯ P ⊢ s ∆; ¯ P ∗ A ⊢ s
The block scope variable rule
The assertion A ↑ lifts the DeBruijn indexes in A ∆; J ↑ ∗ x0 → -; R ↑ ∗ x0 → - ⊢ {P ↑ ∗ x0 → -} s {Q ↑ ∗ x0 → -} ∆; J; R ⊢ {P} block s {Q}
The block scope variable rule
The assertion A ↑ lifts the DeBruijn indexes in A ∆; J ↑ ∗ x0 → -; R ↑ ∗ x0 → - ⊢ {P ↑ ∗ x0 → -} s {Q ↑ ∗ x0 → -} ∆; J; R ⊢ {P} block s {Q} Using our alternative notation ∆; ¯ P ↑ ∗ x0 → - ⊢ s ∆; ¯ P ⊢ block s
Example
void swap(int *p, int *q) { int z = *p; *p = *q; *q = z; }
Example
{x0 → p ∗ x1 → q ∗ p → y ∗ q → z} block ( x0 := load (load x1) ; load x1 := load (load x2) ; load x2 := load x0 ) {x0 → p ∗ x1 → q ∗ p → z ∗ q → y}
Example
{x0 → p ∗ x1 → q ∗ p → y ∗ q → z} block ( {x0 → - ∗ x1 → p ∗ x2 → q ∗ p → y ∗ q → z} x0 := load (load x1) ; load x1 := load (load x2) ; load x2 := load x0 ) {x0 → p ∗ x1 → q ∗ p → z ∗ q → y}
Example
{x0 → p ∗ x1 → q ∗ p → y ∗ q → z} block ( {x0 → - ∗ x1 → p ∗ x2 → q ∗ p → y ∗ q → z} x0 := load (load x1) ; {x0 → y ∗ x1 → p ∗ x2 → q ∗ p → y ∗ q → z} load x1 := load (load x2) ; load x2 := load x0 ) {x0 → p ∗ x1 → q ∗ p → z ∗ q → y}
Example
{x0 → p ∗ x1 → q ∗ p → y ∗ q → z} block ( {x0 → - ∗ x1 → p ∗ x2 → q ∗ p → y ∗ q → z} x0 := load (load x1) ; {x0 → y ∗ x1 → p ∗ x2 → q ∗ p → y ∗ q → z} load x1 := load (load x2) ; {x0 → y ∗ x1 → p ∗ x2 → q ∗ p → z ∗ q → z} load x2 := load x0 ) {x0 → p ∗ x1 → q ∗ p → z ∗ q → y}
Example
{x0 → p ∗ x1 → q ∗ p → y ∗ q → z} block ( {x0 → - ∗ x1 → p ∗ x2 → q ∗ p → y ∗ q → z} x0 := load (load x1) ; {x0 → y ∗ x1 → p ∗ x2 → q ∗ p → y ∗ q → z} load x1 := load (load x2) ; {x0 → y ∗ x1 → p ∗ x2 → q ∗ p → z ∗ q → z} load x2 := load x0 {x0 → y ∗ x1 → p ∗ x2 → q ∗ p → z ∗ q → y} ) {x0 → p ∗ x1 → q ∗ p → z ∗ q → y}
Soundness of the axiomatic semantics
◮ Define ∆; J; R {P} s {Q} in terms of operational semantics
Soundness of the axiomatic semantics
◮ Define ∆; J; R {P} s {Q} in terms of operational semantics ◮ Prove ∆; J; R ⊢ {P} s {Q} implies ∆; J; R {P} s {Q}
Soundness of the axiomatic semantics
◮ Define ∆; J; R {P} s {Q} in terms of operational semantics ◮ Prove ∆; J; R ⊢ {P} s {Q} implies ∆; J; R {P} s {Q} ◮ Tricky definition of ∆; J; R {P} s {Q} because
◮ The frame rule ◮ Undefined behavior ◮ Non-local control ◮ Mutual recursion