Presentation is loading. Please wait.

Presentation is loading. Please wait.

Modular Verification of Message Passing Programs

Similar presentations


Presentation on theme: "Modular Verification of Message Passing Programs"— Presentation transcript:

1 Modular Verification of Message Passing Programs
Actor Services Modular Verification of Message Passing Programs 20th May 2016 Dagstuhl Seminar 16201 Alexander J. Summers Peter Müller ETH Zurich, Switzerland

2 Passing messages for the next ESOP
! ESOP 2017 Prof. Alice

3 Passing messages for the next ESOP
alice.tell(conf) ↝ conf.submit(_) ESOP 2017 Prof. Alice idea idea + plan Dr. Bob (PhD student) this.exps(_) ↝ this.mentor.results(_) running experiments…

4 Passing messages for the next ESOP
alice.tell(conf) ↝ conf.submit(_) ESOP 2017 Prof. Alice idea idea + plan Dr. Bob (PhD student) bob.propose(idea) this.exps(_) ↝ this.mentor.results(_) running experiments…

5 Passing messages for the next ESOP
alice.tell(conf) ↝ conf.submit(_) ESOP 2017 Prof. Alice idea idea + plan Dr. Bob (PhD student) bob.propose(idea) ↝ alice.response(idea) this.exps(_) ↝ this.mentor.results(_) running experiments…

6 Passing messages for the next ESOP
alice.tell(conf) ↝ conf.submit(_) ESOP 2017 Prof. Alice idea idea + plan Dr. Bob (PhD student) bob.propose(idea) ↝ alice.response(idea) where idea.name == old(idea.name) && idea.plan != null this.exps(_) ↝ this.mentor.results(_) running experiments…

7 Actor Services Novel assertions specifying response properties
trigger message responsemessage bob.propose(idea) ↝ alice.response(idea) where idea.name == old(idea.name) … Novel assertions specifying response properties May depend on actor instance, program state Abstract over how property is guaranteed number of intervening messages / actors involved but, some finite sequence of messages Where-clauses express functional properties concerning message arguments and program heap two-state assertions relate to

8 Actor Services: Intuitive Semantics
trigger message responsemessage bob.propose(idea) ↝ alice.response(idea) where idea.name == old(idea.name) … Related to LTL property: ⃞(trigger ⇒ ⃟response) We incorporate stateful properties, incl. shared heap Our semantics avoids explicit traces (non-modular) trigger response where condition

9 Mnesia distributed database protocol (Ericsson)

10 Mnesia distributed database protocol (Ericsson)

11 Mnesia distributed database protocol (Ericsson)

12 Mnesia distributed database protocol (Ericsson)

13 Mnesia distributed database protocol (Ericsson)
query_setup

14 Mnesia distributed database protocol (Ericsson)
query_setup

15 Mnesia distributed database protocol (Ericsson)
query_setup next

16 Mnesia distributed database protocol (Ericsson)
query_setup next next

17 Mnesia distributed database protocol (Ericsson)
query_setup next next next

18 Mnesia distributed database protocol (Ericsson)
next next query_setup next next next

19 Mnesia distributed database protocol (Ericsson)
next next query_setup ready next next next

20 Mnesia distributed database protocol (Ericsson)
next next query_setup ready req next next next

21 Mnesia distributed database protocol (Ericsson)
sols next query_setup ready req next next next

22 Mnesia distributed database protocol (Ericsson)
sols sols query_setup ready req next next next

23 Mnesia distributed database protocol (Ericsson)
sols sols query_setup ready req sols sols sols

24 Mnesia distributed database protocol (Ericsson)
sols sols query_setup enough? ready req sols sols sols

25 Mnesia distributed database protocol (Ericsson)
sols query_setup ready req

26 Mnesia distributed database protocol (Ericsson)
sols sols query_setup ready req

27 Mnesia distributed database protocol (Ericsson)
sols sols query_setup ready req sols sols sols

28 Mnesia distributed database protocol (Ericsson)
sols sols query_setup enough? ready req sols sols sols

29 Mnesia distributed database protocol (Ericsson)
sols sols query_setup ready req sols response sols sols Want to prove: a request will eventually get a response (Ericsson verification case study [Arts & Dam ‘99])

30 Programming Language handler query_setup(Query query, User u) { val subqueries := // break down query - non-empty var n := this; for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } Actors have implicit mailboxes, and receive loops (active objects)

31 Programming Language handler query_setup(Query query, User u) { val subqueries := // break down query - non-empty var n := this; for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } Actors have implicit mailboxes, and receive loops (active objects) Actor classes declare handlers to execute on message receive

32 Programming Language handler query_setup(Query query, User u) { val subqueries := // break down query - non-empty var n := this; for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } Actors have implicit mailboxes, and receive loops (active objects) Actor classes declare handlers to execute on message receive Handler code may send messages, no blocking operations

33 Local Actor Services handler query_setup(Query query, User u) { val subqueries := // break down query - non-empty var n := this; for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } M.query_setup(Q,U) ↝ U.ready(M) Local actor service: provable using code of single message handler

34 Deriving actor services
Local services: response sent directly by message handler the base-cases for deriving actor services Derivation rules prescribe when an actor service holds e.g. actor services can be composed : Where-conditions must also be combined (three states) M.query_setup(Q,U) ↝ U.ready(M) (x.query_setup(Q,user) ↝ user.ready(x)) && (user.ready(x) ↝ m.req(…)) ⊨ x.query_setup(Q,user) ↝ m.req(…) see the paper!

35 Composition and heap expressions
e.g. query workers (another local actor service): we interpret W.next in state when response is sent Suppose we have instances x, y with x.next == y Similar composition? … Only sound if x.next will not be modified in future W.sols(S,P) ↝ W.next.sols(S,P) (x.sols(S,P) ↝ x.next.sols(S,P)) && (y.sols(S,P) ↝ y.next.sols(S,P)) ⊨ x.sols(S,P) ↝ y.next.sols(S,P)

36 Permissions, Immutability, Future States
Two kinds of permissions to heap locations Exclusive permission, denoted acc(e.f) ownership, sendable via messages (preconditions) Immutable permission, denoted immut(e.f) only allows reading, freely duplicable Can freeze locations: transmute acc into immut Define future states relation: Σ1 ≼ Σ2 iff any immut location in Σ1 is immut in Σ2 and the value of that location is the same in Σ1,Σ2 Assertions depending only on immutable locations can be projected to future states (x.next == y on prev. slide)

37 Mnesia distributed database protocol (Ericsson)
query_setup

38 Mnesia distributed database protocol (Ericsson)
query_setup next

39 Mnesia distributed database protocol (Ericsson)
query_setup next next

40 Mnesia distributed database protocol (Ericsson)
query_setup next next next

41 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); }

42 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n = this) && (i>0 ⇒

43 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n = this) && (i>0 ⇒ n.sols(S,P) ↝ this.sols(_,P))

44 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n = this) && (i>0 ⇒ n.sols(S,P) ↝ this.sols(_,P))

45 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n’.sols(S,P) ↝ this.sols(_,P))

46 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n’.sols(S,P) ↝ this.sols(_,P)) && (n.sols(S,P) ↝ n.next.sols(_,P))

47 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n’.sols(S,P) ↝ this.sols(_,P)) && (n.sols(S,P) ↝ n.next.sols(_,P)) && immut(n.next) && n.next = n’

48 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n’.sols(S,P) ↝ this.sols(_,P)) && (n.sols(S,P) ↝ n.next.sols(_,P)) && immut(n.next) && n.next = n’

49 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n’.sols(S,P) ↝ this.sols(_,P)) && (n.sols(S,P) ↝ n’.sols(_,P)) && immut(n.next) && n.next = n’

50 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n’.sols(S,P) ↝ this.sols(_,P)) && (n.sols(S,P) ↝ n’.sols(_,P))

51 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n.sols(S,P) ↝ this.sols(_,P))

52 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } n.sols(S,P) ↝ this.sols(_,P)

53 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } n.sols(S,P) ↝ this.sols(_,P)

54 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } n.sols(S,P) ↝ this.sols(_,P) this.next.sols(S,P) ↝ this.sols(_,P)

55 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } n.sols(S,P) ↝ this.sols(_,P) this.next.sols(S,P) ↝ this.sols(_,P)

56 Deriving Actor Services
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } n.sols(S,P) ↝ this.sols(_,P) immut(this.next) && this.next.sols(S,P) ↝ this.sols(_,P)

57 Alternative responses
sols sols query_setup ready req sols response sols sols

58 Alternative Response Messages
message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } }

59 Alternative Response Messages
message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } this.sols(S,P) ↝ this.user.response(_)

60 Alternative Response Messages
message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } this.sols(S,P) ↝ this.user.response(_)

61 Alternative Response Messages
message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } this.sols(S,P) ↝ this.user.response(_)

62 Alternative Response Messages
message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } this.sols(S,P) ↝ this.user.response(_) |

63 Alternative Response Messages
message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } this.sols(S,P) ↝ this.user.response(_) | this.next.sols(_,P)

64 Alternative Response Messages
message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } IDEA: introduce this if-condition this.sols(S,P) ↝ this.user.response(_) | this.next.sols(_,P)

65 Alternative Response Messages
message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } IDEA: introduce this if-condition this.sols(S,P) ↝ this.user.response(_) | this.next.sols(_,P) | ε

66 Alternative Response Messages
message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } IDEA: introduce this if-condition this.sols(S,P) ↝ this.user.response(_) | this.next.sols(_,P) | ε where old(this.next = null ∨ this.user = null)

67 Alternative Response Messages
message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } this.sols(S,P) ↝ this.user.response(_) | this.next.sols(_,P) | ε where old(this.next = null ∨ this.user = null) (another local actor service)

68 Recall: proof of query setup
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } n.sols(S,P) ↝ this.sols(_,P) immut(this.next) && this.next.sols(S,P) ↝ this.sols(_,P)

69 Eliminating alternative responses
sols query_setup ready req sols response this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) | ε where old(this.next = null ∨ this.user = null)

70 Eliminating alternative responses
sols query_setup ready req sols response this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) | ε where old(this.next = null ∨ this.user = null)

71 Eliminating alternative responses
sols query_setup ready req sols response this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) | ε where old(this.user = null)

72 Eliminating alternative responses
sols query_setup ready req sols response this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) | ε where old(this.user = null)

73 Eliminating alternative responses
sols query_setup enough? ready req sols response this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) | ε where old(this.user = null)

74 Eliminating alternative responses
sols query_setup enough? ready req sols response see the paper! this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) where localVariant(this) | ε where old(this.user = null)

75 Eliminating alternative responses
sols query_setup enough? ready req sols response see the paper! this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) where localVariant(this) | ε where old(this.user = null)

76 Eliminating alternative responses
sols query_setup ready req sols response this.sols(S,P) ↝ this.user.response(_) | ε where old(this.user = null)

77 Eliminating alternative responses
sols query_setup ready req sols response this.sols(S,P) ↝ this.user.response(_) | ε where old(this.user = null) ?

78 Eliminating alternative responses
sols query_setup ready req sols response this.req(U,P) ↝ this.sols(_,P) where immut(this.user) && this.user = U && U ≠ null this.sols(S,P) ↝ this.user.response(_) | ε where old(this.user = null)

79 Eliminating alternative responses
sols query_setup ready req sols response this.req(U,P) ↝ this.sols(_,P) where immut(this.user) && this.user = U && U ≠ null this.sols(S,P) ↝ this.user.response(_) | ε where old(this.user = null)

80 Eliminating alternative responses
sols query_setup ready req sols response this.req(U,P) ↝ this.sols(_,P) where immut(this.user) && this.user = U && U ≠ null this.sols(S,P) ↝ this.user.response(_) | ε where old(this.user = null)

81 Eliminating alternative responses
sols query_setup ready req sols response this.req(U,P) ↝ this.sols(_,P) where immut(this.user) && this.user = U && U ≠ null this.sols(S,P) ↝ this.user.response(_) | ε where old(this.user = null)

82 Eliminating alternative responses
sols query_setup ready req sols response this.req(U,P) ↝ U.response(_) | ε where old(U = null && U ≠ null)

83 Eliminating alternative responses
sols query_setup ready req sols response this.req(U,P) ↝ U.response(_) | ε where old(U = null && U ≠ null)

84 Eliminating alternative responses
sols query_setup ready req sols response this.req(U,P) ↝ U.response(_) | ε where false

85 Eliminating alternative responses
sols query_setup ready req sols response this.req(U,P) ↝ U.response(_) | ε where false

86 Eliminating alternative responses
sols query_setup ready req sols response this.req(U,P) ↝ U.response(_)

87 Eliminating alternative responses
sols query_setup ready req sols response this.req(U,P) ↝ U.response(_)

88 Back to the query setup…
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } this.req(U,P) ↝ U.response(_) derivable for this query manager, at this program point

89 Back to the query setup…
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } this.req(U,P) ↝ U.response(_) derivable for this query manager, at this program point In particular, by the time of the ready response message …

90 Back to the query setup…
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } this.req(U,P) ↝ U.response(_) derivable for this query manager, at this program point In particular, by the time of the ready response message … M.query_setup(Q,U’) ↝ U’.ready(M)

91 Back to the query setup…
handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } this.req(U,P) ↝ U.response(_) derivable for this query manager, at this program point In particular, by the time of the ready response message … M.query_setup(Q,U’) ↝ U’.ready(M) where (M.req(U,P) ↝ U.response(_))

92 So we’re done (almost…)
Derived an actor service characterising responsiveness: Abstracts over workings of protocol, worker actors Combined with user which promises ready… ↝ req… … the property that Ericsson wanted  We built our proof up modularly workers don’t need to know about managers clients don’t need to know about anything (more) M.query_setup(Q,U’) ↝ U’.ready(M) where (M.req(U,P) ↝ U.response(_)) M.query_setup(Q,user) ↝ user.response(_)

93 Conclusions Actor services: novel specification and proof technique
composition, where-clauses, alternatives, … semantics enabling modular proofs Local actor services: base-cases of actor services Lots more in the paper: Hoare Logic for proofs against implementation Obligation assertions as a tool for liveness properties Formal semantics for actor service assertionsInvariants and variants for actor-local state Hierarchical proofs (actor service assumptions) Soundness argument sketch Related and (lots of) Future Work – please ask 

94 Any questions? sols sols query_setup ready req sols response sols sols
M.query_setup(Q,U’) ↝ U’.ready(M) where (M.req(U,P) ↝ U.response(_))


Download ppt "Modular Verification of Message Passing Programs"

Similar presentations


Ads by Google