off Go: Liveness and Safety for Channel-Based Programming

Julien Lange Nicholas Ng Bernardo Toninho Nobuko Yoshida Imperial College London, UK tifact r Comp { } * let * j.lange, nickng, b.toninho, n.yoshida @imperial.ac.uk A t e n * te A is W s E * e n l l C o

L D

C

o

P *

* c

u

e

m s

O

E

u

e

e

P

n

R

t

v e

o

d t

* y

* s E a a

l d u e a t

Abstract goroutines (i.e. lightweight threads), drawing heavily from Go is a production-level statically typed programming language calculi such as Communicating Sequential Processes (CSP) [23]. whose design features explicit message-passing primitives and Concurrent programming in Go is mostly guided towards channel- lightweight threads, enabling (and encouraging) programmers to based communication as a way to exchange data between gorou- develop concurrent systems where components interact through tines, rather than more classical mechanisms communication more so than by -based con- (e.g. locks). Channel-based concurrency in Go is lightweight, of- currency. Go can only detect global deadlocks at runtime, but pro- fering logically structured flows of messages in large systems pro- vides no compile-time protection against all too common commu- gramming [3, 54], instead of “messy chains of dozens of asyn- nication mismatches or partial deadlocks. chronous callbacks spread over tens of source files” [4, 39]. This work develops a static verification framework for bounded On the other hand, Go inherits most problems commonly found liveness and safety in Go programs, able to detect communication in concurrent message-passing programming such as communi- errors and partial deadlocks in a general class of realistic concur- cation mismatches and deadlocks, offering very little in terms of rent programs, including those with dynamic channel creation and compile-time assurances of correct structuring of communication. infinite recursion. Our approach infers from a Go program a faith- While the Go runtime includes a (sound) global deadlock detector, ful representation of its communication patterns as a behavioural it is ultimately inadequate for complex, large scale applications that type. By checking a syntactic restriction on channel usage, dubbed may easily be undermined by trivial mistakes or benign changes to fencing, we ensure that programs are made up of finitely many dif- the program structure [20, 40], nor can it detect deadlocks involving ferent communication patterns that may be repeated infinitely many only a strict subset of a program’s goroutines (partial deadlocks). times. This restriction allows us to implement bounded verification Liveness and Safety of Communication While Go’s type system procedures (akin to bounded ) to check for liveness does ensure that channels are used to communicate values of the and safety in types which in turn approximates liveness and safety appropriate type, it makes no static guarantees about the liveness in Go programs. We have implemented a type inference and live- or channel safety (i.e. channels may be closed in Go, and sending ness and safety checks in a tool-chain and tested it against publicly on a closed channel raises a runtime error) of communication in available Go programs. well-typed code. Our work provides a framework for the static Categories and Subject Descriptors D.1.3 [Programming Tech- verification of liveness and absence of communication errors (i.e. niques]: Concurrent Programming; D.2.4 [Software Engineering]: safety) of Go programs by extracting concurrent behavioural types Software/Program Verification; D.3.1 [Programming Languages]: from Go source code (related type systems have been pioneered Formal Definitions and Theory; F.3.2 [Semantics of Programming by [48] and [28], among others). Our types can be seen as an Languages]: Program analysis abstract representation of communication behaviours in the given program. We then perform an analysis on behavioural types, which Keywords Channel-based programming, Message-passing pro- checks for a bounded form of liveness and communication safety gramming, , Types, Safety and Liveness, Compile- on types and study the conditions under which our verification time (static) deadlock detection entails liveness and communication safety of programs.

1. Introduction 1.1 Overview Do not communicate by sharing memory; We present a general overview of the steps needed to perform our instead, share memory by communicating analysis of concurrent Go programs. Go language proverb [5, 49] Prime Sieve in Go To illustrate the challenges in analysing Go Go is a statically typed programming language designed with ex- programs, we begin by considering a rather concise implementa- plicit concurrency primitives at the forefront, namely channels and tion of a concurrent prime sieve (Listing 1, adapting the example from [43] to output infinite primes). This seemingly simple Go pro- gram includes intricate communication patterns and concurrent be- haviours that are hard to reason about in general due to the combi- nation of (1) unbounded iterative behaviour, (2) dynamic channel creation and (3) spawning of concurrent threads. The program is made up of three functions: Generate, that given a channel ch continuously sends along the channel an in- creasing sequence of integers starting from 2 (encoded as a for loop without an exit condition); Filter, that given a channel that a program consists of finitely many different communication 1 package main 2 func Generate(ch chan<- int){ patterns (that may themselves be repeated infinitely many times). 3 for i := 2; ;i++ { ch <-i} // Send sequence 2,3... For instance, the recursive call to rhbi in the equation r(x) is 4 } fenced, since the recursive instances of r will not know the channel 5 func Filter(in <-chan int, out chan<- int, prime int){ parameter x hence they cannot spawn threads which share x. This 6 for {i := <-in // Receive value from’in’. 7 if i%prime != 0 { out <-i} // Fwd’i’ if factor. restriction enforces a “finite memory” property wrt. channel names, 8 } insofar as given enough recursive unfoldings all the parameters of 9 } the recursive call will be names local to the recursive definition. 10 func main() { 11 ch := make(chan int) // Create new channel. Symbolic Semantics and Bounded Verification Fenced types can 12 go Generate(ch) // Spawn generator. 13 for i := 0; ;i++ { be symbolically executed as CCS processes in a representative 14 prime := <-ch finite-state labelled transition system that consists of a bounded 15 ch1 := make(chan int) version of the generally unrestricted type semantics. This enables 16 go Filter(ch, ch1, prime) // Chain filter. us to produce decision procedures for bounded liveness and safety 17 ch= ch1 18 } of types, which deems the type t0 as bounded live and safe. In the 19 } finite control case, the bounded correctness properties and their Listing 1. Concurrent Prime Sieve. unbounded ones coincide. The approach is similar to bounded model checking, using a bound on the number of channels in a type to limit its execution. for inputs in, one for outputs out, and a prime, continuously From Type Liveness to Program Liveness The final step is to forwards a number from in to out unless it is divisible by formally relate liveness and safety of types to their analogues prime; and main, which assembles the sieve by creating a new in Go programs. For the case of safety, safety of types implies synchronous channel ch, spawning the Generator (i.e. safety for programs. For the case of liveness, programs typically go f(x) spawns a parallel instance of f) with the channel ch, rely on data values to guide their control flow (e.g. in conditional and then iteratively setting up a chain of Filter threads, where branches) which are abstracted away at the type level. For instance, the first filter is connected to the generator and the next Filter, the Filter function only outputs if the received value i is not and so on. We note that with each iteration, a new synchronous divisible by the given prime, but the type for the corresponding channel ch1 is created that is then used to link each filter instance. process is given by f (x, y) which just indicates an internal choice The program spawns an infinite parallel composition of Filter between two behaviours. The justification for the liveness of t0 is threads, each pair connected by a dynamically created channel; and that the internal choice always has the potential to enable the output the execution of Generator and the Filter processes in the on y (assuming a fairness condition on scheduling). However, it is sieve is non-terminating. not necessarily the case that a conditional branch is “equally likely” Types of Prime Sieve Our framework infers from the prime sieve to proceed to the then branch or the else branch. In § 5, we de- fine the three classes of programs in which liveness of programs program the type t0 given by: and their types coincide. g(x) , x; ghxi f (x, y) , x;(y; f hx, yi ⊕ f hx, yi) 1.2 Contributions r(x) , x;(new b)(f hx, bi | rhbi) We list the main contributions of our work: To define and then t0() , (new a)(ghai | rhai) show the bounded liveness and safety properties entailed by our The type is described as a system of mutually recursive equations, analysis, we formalise the message-passing concurrent fragment of the Go language as a process calculus (dubbed MiGo). The MiGo where the distinguished name t0 types the program entry point. This language is equivalent to a subset of CCS [36] with recursion, calculus mirrors very closely the intended semantics of the channel- parallel and name creation, for which deciding liveness or safety based Go constructs and allows us to express highly dynamical and potentially complex behaviours (§ 2); We introduce a typing is in general undecidable [7, 8]. The type t0 specifies that main consists of the creation of a new channel a and the two parallel system for MiGo which abstracts behaviours of MiGo as a subset of behaviours ghai and rhai. The type given by equation g(x), which CCS process behaviours (§ 3); We define a verification framework types the generator, identifies the infinite output behaviour along for our type language based on the notion of fences and symbolic the given channel x. The type equation f (x, y) specifies the filter execution, showing that for fenced types our symbolic semantics behaviour: input along x and then either output on y and recurse or is finite control, entailing the decidability of bounded liveness and just recurse. Finally, we decompose the topology set-up as r(x) channel safety (i.e. the notions of liveness and channel safety wrt. which inputs along x and then, through the creation of a new the symbolic semantics – § 4); We characterise the MiGo programs channel b, spawns a filter on x and b and recurses on b, creating whose liveness and safety are derived from our analysis on types an infinite parallel composition of filters throughout the execution (§ 5); We show that our results are systematically extended to of the program. asynchronous communication semantics (§ 6); we describe the Our type-level analysis relies on the fact that types are able to implementation of our analysis in a tool that we use to evaluate accurately model a program’s communication behaviour. The anal- our approach against open-source, publicly available Go programs ysis proceeds in two steps: a simple syntactic check on channel us- (§ 7). age in types, dubbed fencing, and a symbolic finite-state execution Our implementation and benchmark examples are available on- of fenced types. line [2]. Fenced Types Intuitively, if types are fenced, then during their ex- ecution there can only be a finite set of channels, or a fence, shared 2. MiGo: A Core Language for Go by finitely many types (threads). Moreover, fencing ensures that re- This section introduces a core calculus that models the message cursive calls under parallel composition eventually involve only lo- passing concurrency features of the Go programming language, cal names, shared between finitely many threads. This guarantees dubbed MiGo (mini-go). Beyond sending and receiving data values a new channel with payload type σ, binding it to y in the contin- u := a | x P,Q := π; P uation P ; process call Xhe,˜ u˜i denotes an instance of the process | close u; P π := u!hei | u?(y) | τ definition bound to X, with formal parameters instantiated to e˜ and | select{πi; Pi}i∈I v := n | true | false | x u˜. Both restriction (νc)P and buffers at channel c denote runtime | if e then P else Q e := v | not(e) | succ(e) constructs (i.e. not written explicitly by the programmer), where | newchan(y:σ); P the former denotes the runtime handle c for a channel bound in P | P | Q | 0 D := X(˜x) = P and a buffer for an open channel chσi::˜v, containing messages v˜ of ? | Xhe,˜ u˜i P := {Di}i∈I in P type σ, or a buffer for a closed channel c hσi::˜v. A closed chan- | (νc)P nel cannot be used to send messages, but may be used for receive ? σ := bool | int | ... | chσi::˜v | c hσi::˜v operations an unbounded number of times. Our representation of a Go program as a program P in MiGo, Figure 1. Syntax of MiGo. written {Di}i∈I in P , consists of a set of mutually recursive pro- cess definitions which encode all the goroutines and functions used in the program, together with a process P that encodes the program along channels, the Go language supports three key concurrency entry point (i.e. the main). features: 2.1.1 Example – Prime Sieve in MiGo FIFO Queues. Message-passing in Go is achieved via an ab- stract notion of a lossless, order-preserving communication chan- To showcase the MiGo calculus, we present a concurrent imple- nel, implemented as a (bounded) FIFO-queue. When the bound on mentation of the sieve of Eratosthenes that produces the infinite the queue size is 0, communication is fully synchronous, whereas sequence of all prime numbers. with strictly positive bounds the communication is asynchronous The implementation relies on a generator process G(n, c) that (i.e. sending is non- if a queue is not full and, dually, re- outputs natural numbers and a filter process F (n, i, o) that filters ceiving is non-blocking if a queue is not empty). The bound is de- out divisible naturals. The code for the generator and filter pro- fined upon channel creation and cannot be changed dynamically. cesses are given below as definitions: Goroutines. Go supports lightweight threads, dubbed gorou- G(n, c) , c!hni; Ghn+1, ci tines, which denote the spawning of a thread to execute a function F (n, i, o) , i?(x); if (x%n 6= 0) then o!hxi; F hn, i, oi concurrently with the main control flow of a program. This fea- else F hn, i, oi ture can be modelled by a combination of parallel composition and process definitions. Definition G stands for the generator process: given the natural Select. The select construct in Go encodes a form of guarded number n and channel c, G(n, c) sends the number n along c and choice, where each branch is guarded by an input or an output on recurses on n+1. Definition F stands for the filter: given a natural some channel. When multiple branches can be chosen simultane- n and a pair of channels i and o, F (n, i, o) inputs a number x along ously, one is chosen at random (through pseudo-random number i and sends it on o if x is not divisible by n, followed by a recursive generation). It is also possible to encode a “timeout” branch in such call. We then need a way to chain filters together, implementing the a choice construct. sieve: Our fencing-based analysis is oblivious to buffer sizes, hence 0 0 0 R(c) , c?(x); newchan(c :int); (F hx, c, c i | Rhc i) we first focus on fully synchronous communication for ease of presentation, addressing bounded asynchrony in § 6. The process defined above inputs from the previous element in the chain (either a generator or a filter), creates a new channel which is 2.1 Syntax of MiGo then used to spawn a new filter process in parallel with a recursive call to R on the new channel. Putting all the components together The syntax of the calculus is given in Figure 1, where P,Q range we obtain the program: over processes, π over communication prefixes, e, e0 over expres- sions and x, y over variables. We write v˜ and x˜ for a list of ex- {G(n, c),F (n, i, o),R(c)} in newchan(c:int); (G(2, c) | R(c)) pressions and variables, respectively (we use · as a concatenation As we make precise in § 4.2, the execution of the processes operator). Programs are ranged over by P, consisting of a collec- is fenced insofar it is always the case that channels are shared tion of mutually recursive process definitions (ranged over by D), (finitely) by a finite number of processes. For instance, name c parameterised by a list of (expression and channel) variables. We above is only known to G(k, c) and F (2, c, c0). This point is crucial omit a detailed enumeration of types such as booleans, floating- to ensure the feasibility of our approach. point numbers, etc., which are typed with payload types σ. fn(P ) and fv(P ) denote the sets of free names and variables. Process vari- 2.1.2 Example – Fibonacci in MiGo X definitions D X(˜x) = P ables are bound by of the form of We implement a parallel Fibonacci number generator that computes where fv(P ) ⊆ {x˜} and fn(D) = ∅. We use u to range over chan- the nth number of the Fibonacci sequence. nel names a or variables x. 0 The language constructs are as follows: a prefixed (or guarded) Fib(n, c) , if (n ≤ 1) then c!hni else newchan(c :int); process π; P denotes the behaviour π (a send action of e on u, (Fibhn−1, c0i | Fibhn−2, c0i | c0?(x); c0?(y); c!hx+yi) u!hei, a receive action on u, bound to y, u?(y), or an inter- The definition F ib(n, c) above tests if the given number n is less nal action τ) followed by process P ; a close process close u; P than or equal to 1. If so, it sends n on c and terminates. Otherwise, closes the channel u and continues as P ; a selection process the process creates a new channel c0, which is then used to run two select{π ; P } denotes a choice between the several P pro- i i i∈I i parallel copies of Fib for the two predecessors of n. The parallel cesses, where each P is guarded by a prefix π . Thus, the choice i i instances are composed with inputs on c0 twice which are then construct non-deterministically selects between any process P i added and sent along c. whose guarding action can be executed (note that a τ action prefix A sample program that produces the 10th element of the Fi- can always be executed, cf. § 2.2); the standard conditional process bonacci sequence is given below: if e then P else Q, parallel process P | Q, and the inactive process 0 (often omitted); a new channel process newchan(y:σ); P creates {Fib(n, c)} in newchan(c:int); (Fibh10, ci | c?(u); 0) 2.3 Liveness and Channel Safety e ↓ v [SCOM] We define a notion of liveness and channel safety for programs c!hei; P | c?(y); Q | chσi::∅ −→ P | Q {v/y} | chσi::∅ through barbs in processes (Definition 2.1). Liveness identifies the ? σ ? [SCLOSE] c?(y); P | c hσi::∅ −→ P {v /y} | c hσi::∅ ability of communication actions to always eventually fire. Channel ? safety pertains to the semantics of channels in Go, where closing a [CLOSE] close c; P | chσi::˜v −→ P | c hσi::˜v channel more than once or sending a message on a closed channel [TAU] τ; P −→ P raises a runtime error. c∈ / fn(P ) A common pattern in the usage of select in Go is to introduce [NEWC] newchan(y:σ); P −→ (νc)(P {c/y} | chσi::∅) a timeout (or default) branch, which we model as a τ-guarded branch. This notion of timeout makes the definition of liveness P −→ P 0 P −→ P 0 slightly challenging. Consider the following: [PAR] 0 [RES] 0 P | Q −→ P | Q (νc)P −→ (νc)P P1 , select{a!hvi, b?(x); 0 , τ; Pt} R1 , a?(y); 0 0 0 P2 , select{a!hvi, b?(x); 0} R2 , c?(y); 0 P ≡ Q −→ Q ≡ P πj ; Pj | P −→ R j ∈ I [STR] 0 [SEL] A select with a branch guarded by τ contains a branch that is always P −→ P select{πi; Pi}i∈I | P −→ R enabled by default (since τ actions can always fire silently). Hence e ↓ true e ↓ false if the continuation of the τ prefix Pt is live, then P1 is live. On the [IFT] [IFF] other hand, P by itself cannot be live. For P to be live, it must be if e then P else Q −→ P if e then P else Q −→ Q 2 2 composed with a process that can offer an input on a or an output v,˜ c˜ on b, with respective live continuations. Hence P2 | R1 is live. P { /x˜} | Q → R ei ↓ vi X(˜x) = P ∈ {Di}i∈I [DEF] However, P1 | Ri is not live unless Pt | Ri is live (i ∈ {1, 2}). X(˜e, c˜) | Q−→ R Accounting for these features, we formalise safety and liveness Structural Congruence properties, extending the notion of barbed process predicates [38]. Most of the definitions are given in a standard way, with some P | Q ≡ Q | PP | (Q | R) ≡ (P | Q) | RP | 0 ≡ P specifics due to the ability to close channels: input barbs P ↓ , (νc)(νd)P ≡ (νd)(νc)P (νc)0 ≡ 0 (νc)chσi::˜v ≡ 0 a ? denoting that process P is ready to perform an input action on the P | (νc)Q ≡ (νc)(P | Q) (c 6∈ fn(P )) (νc)c hσi::˜v ≡ 0 free channel name a; output barbs P ↓a are dual; a synchronisation barb P ↓[a], indicating that P can perform a synchronisation on a; Figure 2. Reduction Semantics. a channel close barb P ↓end[a], denoting that P can close channel a; and P ↓a? , denoting that P may send from closed channel a. We highlight the predicate P ↓o˜, where o˜ is a set of barbs, On the other hand, the following program should be deemed not which applies only for the select construct, stating that the barbs live since the outputs from the recursive calls are never sent to the of select{πi; Pi}i∈I are those of all the processes that make up the initial Fibbad call and so the answer is never returned to the main external choice, provided that all of them can exhibit a barb. process (i.e. c?(u); 0 can never fire). Definition 2.1 (Barbs). We define the predicates π ↓o, P ↓o and 0 P ↓ with o, o ∈ {a, a, [a], end[a], a?}. Fibbad (n, c) , newchan(c :int); o˜ i 0 0 0 0 (Fibbad hn−1, c i | Fibbad hn−2, c i | c ?(x); c ?(y); c!hx+yi) π ↓o ? c?(x) ↓ c!hei ↓ close c; Q ↓ c hσi::~v ↓ ? c c π; Q ↓ end[c] c 2.2 Operational Semantics o

The semantics of MiGo, written P −→ Q, is defined by the reduc- P ↓o P ↓o a 6∈ fn(o) P ↓o P ≡ Q tion rules of Figure 2, together with the standard structural congru- P | Q ↓o (νa)P ↓o Q ↓o ence P ≡ Q (which includes ≡α). For process definitions, we e,˜ a˜ implicitly assume the existence of an ambient set of definitions Q { /x˜} ↓o X(˜x) = Q {Di}i∈I . Our semantics follows closely the semantics of the Go Xhe,˜ a˜i ↓o language: a channel is implemented at runtime by a buffer that is ∀i ∈ {1, .., n} : π ↓ P ↓ Q ↓ or Q ↓ ? open or closed. Once a channel is closed, it may not be closed again i oi a a a nor can it be used for output. However, a closed channel can always select{πi; Pi}i∈{1,..,n} ↓{o1...on} P | Q ↓[a] be the subject of an input action, where the received value is a bot- P ↓ π ↓ P ↓ or P ↓ ? π ↓ tom element of the corresponding payload data type. For now, we a i a a a i a impose a synchronous semantics (represented in Go with channel P | select{πi; Qi}i∈I ↓[a] P | select{πi; Qi}i∈I ↓[a] ∗ 0 0 ? of size 0), see § 6 for the asynchronous semantics. P ⇓o if P −→ P and P ↓o with o ∈ {c, c, [c], end[c], c }. Rule [SCOM] specifies a synchronisation between a send and a For example, we have that ¬(P1 ↓a) for any a, whereas P2 ↓a,b. receive. Rule [SCLOSE] defines inputs from closed channels, which Note that (P | R ) ↓ and (P | R ) ↓ ; and if P ↓ then according to the semantics of Go are always enabled, entailing the 1 1 [a] 2 1 [a] t o σ P1 ⇓o and if Pt ↓c then P1 | R2 ⇓[c]. reception of a base value v of type σ. Rule [CLOSE] changes the We may now define liveness and channel safety: a program state of a buffer from open (chσi::˜v) to closed (c?hσi::˜v). Rule is live if, for all the reachable process states, (a) if the state can [NEWC] creates a fresh channel c, instantiating it accordingly in the perform an input or output action on a channel, the state can also continuation process P and creating the buffer for the channel. eventually perform a synchronisation on that channel; and, (b) if a Rule [SEL] encodes a mixed non-deterministic choice, insofar as any state can perform a set of actions (i.e. a select where all its guards subprocess P that can exhibit a reduction may trigger the choice. j are non-τ), the state can also eventually synchronise on one of the The rule [DEF] replaces X by the corresponding process definition action prefixes. (according to the underlying definition environment), instantiating the parameters accordingly. The remaining rules are standard from Definition 2.2 (Liveness). The program P satisfies liveness if for process calculus literature [46]. all Q such that P −→∗ (νc˜)Q: The recursive process defined by X(c) receives a potentially un- T,S := κ; T | ⊕{T } | {κ ; T } | (T | S) | 0 i i∈I i i i∈I bounded number of positive integers, stopping when the received | (new a)T | end[u];NT | t hu˜i | (νa)T | bac | a? X value x is less than 0. To type such a commonplace programming T := {tXi (˜yi) = Ti}i in S κ := u | u | τ pattern, we allow the branches in the conditional to hold different types (which are also incompatible by the usual branch subtyping). Figure 3. Syntax of Types. Process Typing The judgement (Γ ` P I T ) for processes is defined in Figure 4 where Γ is a typing environment that maintains (a) If Q ↓a or Q ↓a then Q ⇓[a]. information about channel payload types, types of bound commu-

(b) If Q ↓a˜ then Q ⇓[ai] for some ai ∈ {a˜}. nication variables and recursion variables, P is a process and T a behavioural type. Channel safety states that in all reachable program states, chan- We write Γ ` J for J ∈ Γ and Γ ` e : σ to state that the nels are closed at most once and no process performs outputs on expression e is well-typed according to the types of variables in closed channels, as specified by the semantics of the Go language. Γ. We write u:ch(σ) to denote that u stands for a channel with Definition 2.3 (Channel Safety). The program P is channel safe payload type σ. We omit the typing rules of expressions e, given ∗ if for all Q such that P −→ (νc˜)Q, if Q ↓a? then ¬(Q ⇓end[a]) and that expressions only include basic data types. We write dom(Γ) ¬(Q ⇓a). for the set of channel bindings in Γ. The rules implement a very close correspondence between pro- 3. A Behavioural Typing System for MiGo cesses and their respective types: Rule hOUTi types output processes with the output prefix type u; T , checking that the type of the ob- Go’s channel types are related to those of the π-calculus, where the ject to be sent matches the payload type σ of channel u, and that type of a channel carries the type of the objects that threads can the continuation P has type T . The rule hINi for inputs is dual. send and receive along the channel. Our typing system augments Rule hSELi types the select construct with the external choice type, Go’s channel types by also serving as a behavioural abstraction of whereas rule hIFi types the conditional as a binary internal choice a valid MiGo program, where types take the form of CCS processes between the type S corresponding to P and the type T correspond- with name creation. ing to Q. The typing rules for close, zero, parallel and τ are straightfor- 3.1 Syntax of Types ward. Rule hNEWi allocates a fresh type-level channel name with The syntax of types T,S is given in Figure 3, mirroring closely payload type σ. Rule hVARi matches a process variable with its that of MiGo processes: The type κ; T denotes an output u, input corresponding type variable, checking that the specified arguments u along channel u, or an explicit τ action (often used to encode have the appropriate types. timeouts in external choices), followed by the behaviour denoted by type T . The type ⊕{Ti}i∈I represents an internal choice be- Program Typing The judgement (Γ ` P I T ) is defined in 0 tween the behaviours Ti, whereas {κi; Ti}i∈I denotes an exter- Figure 4. A process declaration X(˜x:˜σ, y˜:ch(˜σ )) = P is matched nal choice between behaviours Ti, respectivelyN guarded by prefixes with tX (˜y) = T , connecting the process level variable X with the κi which drive the choice. Types include parallel composition of type variable t, where P may use any of the parameters specified in behaviours T | S, inaction 0 and channel creation (new a)T (bind- the recursion variable. The typing rule for programs hDEFi assigns ing a in T ). The type end[u]; T denotes the closing of channel u a program the type {tXi (˜yi) = Ti}i∈I in S, checking that each followed by the behaviour T . The type variable tX hu˜i associated to definition is typed with tXi (˜yi) = Ti and that the main process Q a process variable X, denotes the behaviour bound to variable tX has type S. in the definition environment, with formal parameters instantiated Runtime Process Typing The judgement (Γ ` P T ) types to u˜. The type {tXi (˜yi) = Ti}i in S codifies a set of (parame- B I a process created after execution of a program (called runtime terised) mutually recursive type definitions tXi (˜yi) = Ti, bound in S. This set of equations denoted by T is the type assigned to process). B is a set of channels with associated runtime buffers top-level programs P. to ensure their uniqueness. Runtime channel bindings are typed by The type constructs (νa)T , bac and a? denote the type rep- rule hRESi, where given a process of type T that can use the buffered resentations of runtime channel bindings, open and closed buffers, channel c, we type (νc)P with (νc)T removing c from the set s respectively. We write fn(T ) and fv(T ) for the free names and vari- since it is local to P (and T ). Closed and open buffers are typed by ables of type T , respectively; and n(T ) denotes the set of bound and rules hCBUFFi and hBUFFi, respectively, noting that the set of active free names. buffers is a singleton containing the appropriate buffer reference. The parallel rule hPARRi ensures that the buffers of both processes 3.2 Typing System do not overlap (hence only a single buffer for each name exists in We first explain the two essential differences from (linear or the context). session-based) type systems of the π-calculus [27, 28, 48]: Notation 3.1. In the remainder of this paper, we refer to the type of Sharing of Channels. We do not enforce linear (disjoint) chan- a program as a system of type equations T which is obtained from nel usages, allowing processes to have races. For instance, the pro- the program by collecting all the types for definitions and adding cess below (with shared y) is typable: a distinguished unique definition t0() = S for the program entry newchan(y:bool); (y!htruei; 0 | y!hfalsei; 0 | y?(x); 0) point. We often write X0 to stand for the process variable of the program entry point. Conditionals. We do not enforce the same types of both branches of the conditional. This design choice stems from the fact that most real programs make use of conditionals precisely to 4. Bounded Verification of Behavioural Types identify points where behaviours need to be different. For instance, This section introduces our main definition, fencing, and a bounded consider the following definition: verification of liveness and channel safety for types. Our develop- X(c) = c?(x); if x ≥ 0 then Xhci else 0 ment consists of the following steps: Γ ` P I T a a τ |SND| a; T −→ T |RCV| a; T −→ T |TAU| τ; T −→ T Γ ` u:ch(σ)Γ ` e : σ Γ ` P I T hOUTi α Γ ` u!hei; P I u; T j ∈ I κj ; Tj −→ Tj |SEL| τ |BRA| α ⊕{Ti}i∈I −→ Tj {κi; Ti}i∈I −→ Tj Γ ` u:ch(σ)Γ, x:σ ` P I T Γ ` P I T N hINi hTAUi α β a Γ ` u?(x); P I u; T Γ ` τ; P I τ; T T −→ T 0 T −→ T 0 S −→ S0 β = a, a? |PAR| |COM| α 0 [a] Γ ` P I T T | S −→ T | S T | S −→ T 0 | S0 hCLOSEi hZEROi Γ ` close u; P end[u]; T Γ ` 0 0 τ end[a] I I |NEW| (new a)T −→ (νa)(T | bac) |END| end[a]; T −−−→ T

Γ ` π ; P κ ; T end[a] end[a] i i I i i end[a] ? 0 0 hSELi |BUF| bac −−−→ a T −−−→ T S −−−→ S Γ ` select{πi; Pi}i∈I {κi; Ti}i∈I ? |CLOSE| τ I ? a ? 0 0 N |CLD| a −→ a T | S −→ T | S Γ ` e : bool Γ ` P I S Γ ` Q I T α 0 [a] 0 hIFi T −→ T fn(α) 6= {a} T −→ T Γ ` if e then P else Q I ⊕{S,T } |RES-1| |RES-2| (νa)T −→α (νa)T 0 (νa)T −→τ (νa)T 0 Γ, y:ch(σ) ` P I T c 6∈ dom(Γ) ∪ fn(T ) α α hNEWi 0 00 a˜ 0 c T ≡α T T −→ T T { /x˜} −→ T t(˜x) = T Γ ` newchan(y:σ); P I (new c)T { /y} |EQ| |DEF| T 0 −→α T 00 tha˜i −→α T 0 Γ ` P I T Γ ` Q I S hPARi Γ ` P | Q I (T | S) Figure 5. LTS Semantics of Types. Γ ` e˜:˜σ Γ ` u˜:ch(˜σ0) hVARi Γ,X(˜σ, ch(˜σ0)) ` Xhe,˜ u˜i hu˜i I tX of channel closure (end[a] and end[a]), and send actions from a closed channel a?. We write t(˜x) = T if t(˜x) = T is in T . Γ ` P I T Rule |SND| (resp. |RCV|) allows a type to emit a send (resp. 0 0 a |SEL| |BRA| Γ,Xi(˜σi, ch(˜σi)), x˜i:˜σi, y˜i:ch(˜σi) ` Pi I Ti receive) action on a channel . Rules and model internal 0 0 and (mixed) external choices, respectively. Rule |COM| allows two Γ,X1(˜σ1, ch(˜σ1)), ..., Xn(˜σn, ch(˜σn)) ` Q I S hDEFi types to synchronise on a dual action (send with receive, or closed Γ ` {Xi(x ˜i, y˜i) = Pi}i∈I in Q {tXi (˜yi) = Ti}i∈I in S I channel with receive). Rule |NEW| creates a new (open) channel for a. Rule |END|, together with rule |CLOSE|, allows a type to request Γ `B P I T the closure of channel a. Rule |BUF| models a transition from an Γ ` P T Γ, c:ch(σ) ` P T I B I |CLD| hINTi hRESi open channel to a closed one, and rule models the perpetual Γ `∅ P I T Γ `B\c (νc)P I (νc)T ability of a closed channel to emit send actions. The other rules are standard from CCS. In Figure 5, we omit the symmetric rules for Γ ` a:ch(σ) Γ ` a:ch(σ) |CLOSE|, |PAR|, and |COM|. We define structural congruence rules over hCBUFFi ? ? hBUFFi Γ `{a} a hσi::˜v I a Γ `{a} ahσi::˜v I bac types as follows: 0 T | S ≡ S | TT | (T 0 | S) ≡ (T | T 0) | ST | 0 ≡ T Γ `B P I T Γ `B0 Q I SB ∩ B = ∅ hPARRi (νa)(νb)T ≡ (νb)(νa)T (νa)0 ≡ 0 (νa)a? ≡ 0 (νa)bac ≡ 0 Γ `B∪B0 P | Q I (T | S) 0 0 T | (νa)S ≡ (νa)(T | S) (a∈ / fn(T )) T ≡α T ⇒ T ≡ T Figure 4. Typing Rules (Processes and Programs). We write −→ for −→∪τ ≡ and T −→∗−→α if there exist T 0 and T 00 such that T −→∗ T 0 −→α T 00. Step 1. Define a syntactic restriction on types, dubbed a fence, guaranteeing that whenever fenced types model a program that 4.2 Fenced Types and Fencing Predicate spawns infinitely many processes, the program actually con- This section develops Step 1 by defining the fencing predicate. We sists of finitely many communication patterns (which may be illustrate the key intuitions with an example. repeated infinitely many times). Recall the prime sieve program (§ 2.1.1) given in § 1.1, as Step 2. Define a symbolic semantics for types, which generates inferred by the type system of § 3. We represent the execution wrt. a representative finite-state labelled transition system (LTS) the semantics of § 4.1, via the diagram below.

whenever types validate the fencing predicate...... t0hi gha0i f ha0,a 1i f ha1, a2i f hak−1,a ki f hak, ak+1i Step 3. Prove that liveness and channel safety are decidable for the bounded symbolic executions of fenced types. rha0i rha1i rha2i ... rhaki rhak+1i ...

4.1 Types as Processes: Semantics In the diagram, a node represents an instance of a concurrently executing type (or thread) and the arrows represent the parent-child The semantics of our types is given by the labelled transition system relation. For instance, t0hi is the parent of gha0i and rha0i. (LTS), extending that of CCS, defined in Figure 5. The labels, We observe that there are only finitely many kinds of concurrent ranged over by α and β, have the form: threads (i.e. t0hi, ghai, f ha, bi, and rhai). Also given any name in α, β := a | a | τ | [a] | end[a] | end[a] | a? the diagram, e.g. a1, it is shared only by finitely many threads. For instance, a1 is “known” only to f ha0, a1i, f ha1, a2i, and rha1i. Labels denote send and receive actions (a and a), silent transitions Note that this situation does not change during the execution of the τ, synchronisation over a channel [a], the request and acceptance program because (1) types cannot exchange names in communi- G ;y ˜;z ˜ `t T is a parallel composition within T ), hence for each occurrence of a t-recursion, at least one of the parameters must be forgotten. In the y˜ 6= ε ∨ u˜ ≺ z˜ shu˜i ∈ G prime sieve example, the types g(x) and f (x, y) always recurse on bAXIOMc bDEF-∈c G ;y ˜;z ˜ `t thu˜i G ;y ˜;z ˜ `t shu˜i the same parameters, but they do not include parallel composition. While the type r(x) has a parallel composition, its parameter x is G ; ε ;z ˜ · y˜ `t T1 G ; ε ;z ˜ · y˜ `t T2 “forgotten” at the rhbi recursive call (i.e. b ≺ x). bENDc bPARc ∆ `t 0 G ;y ˜;z ˜ `t T1 | T2 4.2.2 Examples on Fencing u˜ t 6= s shu˜i ∈/ GG · shu˜i ;y ˜;z ˜ `t T { /x˜} s(˜x) = T bDEF-∈c/ No Fence We now give an example that does not validate the G ;y ˜;z ˜ `t shu˜i fencing predicate. The types below model a program that spawns a reader and a writer on a channel a infinitely many times. ∀i ∈ I : ∆ `t Ti ∀i ∈ I : ∆ `t Ti bSELc bBRAc w(x) x; whxi = T t (x) whxi | rhxi | t hxi = T ∆ `t ⊕{Ti}i∈I ∆ `t {Ti}i∈I , w 1 , 1 1 N r(x) , x; rhxi = Tr t0() , (new a)(t1hai) = T0 ∆ `t T ∆ `t T ∆ `t T bENDc bRESc bPREFc We have: ε ; x ; ε `w Tw, ε ; x ; ε `r Tr, and ε ; ε ; ε `t0 T0 ∆ `t end[u]; T ∆ `t (new a)T ∆ `t κ; T hold (since they do not feature any parallel composition). However,

ε ; x ; ε `t1 T1 does not hold. This is due to the fact that the axiom Figure 6. Rules for Fencing, where ∆ stands for G ;y ˜;z ˜. bAXIOMc cannot be applied (i.e. ¬(x ≺ x)). The topology induced by the type is given as: cation and (2) the grand-children of, e.g. rha1i, do not know a1, t0hi t1hai t1hai t1hai hence they cannot spawn further threads sharing that name. Intuitively, we can observe that despite the fact that the prime whai rhai whai rhai whai rhai sieve generates an unbounded number of fresh channels and threads, it consists of finitely many different communication pat- We can observe that there are infinitely many instances of types that terns (i.e. the coloured regions above). It is this general pattern that “know” the name a. we capture with fencing. Fibonacci Below, we give the types for the Fibonacci example, 4.2.1 Fencing Types cf. § 2.1.2. We introduce a judgement which ensures that a system of types is fib(x) , x ⊕ (new b)(fibhbi | b; b; x | fibhbi) = Tfib fenced: given a finite set of names called fence, the types execut- t () (new a)(fibhai | a) = T ing within that fence approximate a form of finite control. We use 0 , 0 judgements of the form G ;y ˜;z ˜ `t T to guarantee that a type defi- Observe that t0() is trivially fenced since it has no parameter (cf. nition t(˜x) = T is fenced; where G records previously encountered Definition 4.2). The judgement ε ; x ; ε `fib Tfib also holds since recursive calls, y˜ represents the names that t can use if T is single- we have b ≺ x. Essentially, the equation fib validates the fencing threaded, and z˜ represents the names that a sub-term of T can use predicate because each recursively spawned child does not have if T is a multi-threaded type. We write ε for the empty environ- access to the parameter x. We illustrate the behaviour of this type ment. The judgement G ;y ˜;z ˜ `t T holds if it can be inferred by in the diagram below. the rules of Figure 6, which use a relation on sequences of names that ensures a strictly decreasing usage of names, defined below. t0hi

Definition 4.1 (≺-relation). We write u˜ ≺ x˜ iff (1) x˜ = x1 ··· xn, fibhai a (2) u˜ = xk+1 ··· xn · a1 ··· ak, with k ≥ 1, and (3) ∀1 ≤ i ≤ n : ∀1 ≤ j ≤ k : xi 6= aj . fibhb1i b1; b1; a fibhb1i The relation ≺ enforces that types featuring parallel composition fibhb2i fibhb2i b2; b2; b1 b3; b3; b1 fibhb3i fibhb3i have a “finite memory” wrt. the names over which they can recurse. For instance, yza ≺ xyz, but xaz 6≺ xyz. We comment on the key rules of Figure 6. Rule bPARc identifies multi-threaded types by moving the y˜ environment to the z˜ envi- Two fences are highlighted in the diagram: the {a}-fence includes four parallel types (including the initial t ); while the {b }-fence ronment. The axiom bAXIOMc states that thu˜i is fenced if either (i) 0 1 y˜ 6= ε, i.e. the type corresponding to t does not contain any parallel includes five components: one instance of fib(x) as well as two composition; or (ii) u˜ ≺ z˜ holds, i.e. any recursive call over t uses of its recursive children and three instances of the non-recursive strictly fewer (non newly created) names. The second part of the component of fib. premise guarantees that after a certain number of recursive calls, the type t will have completely “forgotten” the names it started ex- 4.3 Symbolic Semantics ecuting with; hence moving outside of the fence. Rules bDEF-∈c and For Step 2, we introduce a symbolic semantics for types, which is bDEF-∈c/ deal with recursive calls to different type variables. parameterised by a bound on the number of free names that can be used when unfolding a recursive call, e.g. thu˜i, by its correspond- Definition 4.2 (Fenced types). We say T is fenced (denoted by ing definition. The overall purpose of the symbolic semantics is Fenced(T )) if for all t(˜x) = T in T , either x˜ = ε or ε ;x ˜ ; ε ` T t that for any T such that Fenced(T ), the symbolic LTS of T is finite holds. state. A consequence of fencing is that for each equation t(˜x) = T The symbolic semantics for types is given in Figure 7, where we such that x˜ 6= ε, either (1) T is single-threaded (i.e. there is no show only the interesting new rules. The other rules are essentially parallel composition within T ), hence there is no restriction as to those of Figure 5, with the additional parameters k and N as the parameters t recurses on; or (2) T is multi-threaded (i.e. there expected. Rule |DEF| replaces its counterpart from Figure 5, while 0 0 α ∀i ∈ {1, . . . , n} : κi ↓o T ↓a T ↓a or T ↓a? a˜ 0 i N C T { /x˜} −→k N C T t(˜x) = T a˜ ∩ N 6= ∅ 0 |DEF| {κi; T }i∈{1,...,n} ↓{o ...o } T | T ↓ α 0 1 n [a] N C tha˜i −→k N C T N T ↓a κi ↓a T ↓a or T ↓a? κi ↓a α 0 N ]{a} C T −→k N ]{a} C T fn(α) 6= {a} |N| < k T | {κ ; S } ↓ T | {κ ; S } ↓ |R1<| i i i∈I [a] i i i∈I [a] α 0 N N N C (νa)T −→k N C (νa)T k ∗ 0 0 Given k ∈ N, we write T ⇓o if N C T −→n N C T and T ↓o, ? [a] 0 with N = fn(T ), n = k + |N| and o ∈ {a, a, [a], end[a], a }. N ]{a} C T −→k N ]{a} C T |N| < k k |R2<| Observe that the predicate T ⇓o is defined wrt. the symbolic τ 0 ∞ N C (νa)T −→k N C (νa)T semantics. We write T ⇓o if T ⇓o . α 0 N C T −→k N C T fn(α) 6= {a} |N| ≥ k Definition 4.4 (Liveness). The system T satisfies k-liveness if for |R1≥| ∗ α 0 all T such that ∅ C t0hi −→k ∅ C (νa˜)T , N C (νa)T −→k N C (νa)T T ↓ T ↓ T ⇓k [a] 0 (a) If a or a then [a]. N C T −→k N C T |N| ≥ k k |R2≥| (b) If T ↓a˜ then T ⇓ for some ai ∈ a˜. τ 0 [ai] N C (νa)T −→k N C (νa)T If T is ∞-live, then we say that T is live.

Figure 7. Symbolic Semantics for Types. Consider the type equations below, where the reduction of t0hi leads to terms that are not k-live, for any k ≥ 0. rules [R1<] and |R1≥| replace rule |RES-1|, and rules |R2<| and |R2≥| t1(x) , (new b)(t1hbi | b; x) t0() , (new a)(t1hai | a) replace |RES-2|. Intuitively, the system is not live since it is not possible to find a In a term N C T , N can be seen as a subset of the free names synchronisation for, e.g. the receive action on a, within bounded of T . Whenever a new name is encountered, e.g. through (νa)T , unfolding. a is recorded in N as long as N has less than k elements. Rule The definition of channel safety follows the same structure as |DEF| states that a recursive call can only be unfolded if some of its that of liveness. parameters are in N. Note that, in rule |DEF|, we assume that the unfolding of a type is such that there is no clash with the names in Definition 4.5 (Channel Safety). The system T satisfies k-channel ∗ N. safety if for all T such that ∅ C t0hi −→k ∅ C (νa˜)T , if T ↓a? then ∗ α 0 00 k k For k ≥ 0, we write N C T −→k−→k if there exist T and T ¬(T ⇓end[a]) and ¬(T ⇓a). ∗ 0 α 00 such that N C T −→k N C T −→k N C T . If T is ∞-safe, then we say that T is channel safe. We consider a fragment of the prime sieve example and show Example We illustrate the need of a sufficiently large bound to its behaviour according to the symbolic semantics, with k = 1. We detect liveness errors with an example. Consider a variation of the have: Prime Sieve example with a non-recursive filter: {a} C (νb)(ghai | f ha, bi | rhbi) ∗ [a] f (x, y) , x; y; x; y; x; y; x; y; 0 −→1−→1 {a} C (νb)(ghai | b; f ha, bi | rhbi) with g(x), r(x), and t0() as in § 1.1. The system above is 2-live, at this point the process is stuck. The sub-term b; f ha, bi awaits to but not 3-live. A bound of 2 is too small to allow the symbolic synchronise on b, however the dual action on b is “hidden” in the semantics to explore all the states of f (x, y); while a bound of 3 unfolding of rhbi, which cannot be unfolded by rule |DEF| since enables spawning another filter process, hence to explore all the b∈ / {a} and, since k = 1, b cannot be added to the set of names. If states of f (x, y). we set the bound to k = 2: 4.5 Decidability of the Bounded Verification {a, b} C ghai | b; f ha, bi | rhbi [b] −→∗−→ {a, b} ghai | f ha, bi | (new c)(f hb, ci | rhci) We show that k-liveness and k-channel safety are decidable, noting 2 2 C that these are defined wrt the symbolic semantics (Step 3). 4.4 Liveness and Channel Safety for Types The decidability result mainly follows from fencing, which en- sures finite control over the symbolic semantics. The key idea is to Following § 2.3, we define liveness and channel safety properties show that the set of non-equivalent terms reachable by the symbolic for types. The definitions of liveness and channel safety rely on semantics is finite. This is formalised below. barbs. The predicate T ↓a (resp. T ↓a) denotes a type ready to send (resp. receive) over channel a. Barb T ↓end[a] denotes a type Lemma 4.1 (Finite Control). If Fenced(T ), then the set ∗ ready to close channel a and barb T ↓a? denotes a closed channel. {[T ]≡ | ∅ C t0hi −→k ∅ C T } is finite, for any finite k. Barb T ↓ denotes a synchronisation over channel a. Barb T ↓ [a] o˜ The crux of the proof is to show that the number of occurrences denotes a type that is waiting to synchronise over the actions in o˜. of a type variable t is bounded in the maximal unfolding of a term Definition 4.3 (Type Barbs). We define the predicates κ ↓o, T ↓o up-to a given set of names N. That is, an unfolding which unfolds ? and T ↓o˜ with o, oi ∈ {a, a, [a], end[a], a }. a term tha˜i only if a˜ ∩ N 6= ∅.

κ ↓o ? Theorem 4.1 (Decidability). For all T s.t. Fenced(T ), it is decid- a ↓a a ↓a end[a]; T ↓end[a] a ↓a? κ; T ↓o able whether or not T is k-live (resp. channel safe), for any k ≥ 0.

0 Theorem 4.1 follows from the fact that checking k-liveness T ↓o T ↓o a∈ / fn(o) T ↓o T ≡ T (resp. channel safety) is decidable over any finite LTS (finiteness 0 T | T ↓o (νa)T ↓o T ↓o is guaranteed by Lemma 4.1). a˜ T { /x˜} ↓o t(˜x) = T Remark 4.1. It is not always possible to compute a finite k such

tha˜i ↓o that k-liveness (resp. k-channel safety) implies general liveness (resp. channel safety) of fenced types. However, if the types are respectively. Despite this program executing forever, both program finite control (i.e., parallel composition does not appear under re- and type liveness hold. This class of programs is made precise in cursion), then liveness and channel safety are indeed decidable, see Proposition 5.2, along with the issue of infinitely occurring condi- e.g. [15]. tionals, which are explained below.

5. Properties of MiGo 5.3 Liveness of Infinitely Occurring Conditionals We now make precise the properties our behavioural type analy- As the third class, we consider infinitely running programs that sis ensures on MiGo programs. We show that if a program P is contain recursive variables in conditional branches. The behaviours typed by a safe type, then P is safe according to Definition 2.3. of conditionals in a program rely on data to decide which branch For liveness, we identify the classes of programs for which live- is taken. On the other hand, at the type level, this information is ness of types implies program liveness. We note that type liveness abstracted as an internal choice. This causes a mismatch between and safety refers to ∞-liveness and ∞-safety, respectively. More- program and type behaviours. § over, we recall that our bounded analysis on types implies its ∞- Revisiting the prime sieve example of 2.1.1, consider the counterpart only in the finite control fragment. definition of the filter process: F (n, i, o) , i?(x); if (x%n 6= 0) then o!hxi; F hn, i, oi 5.1 Type and Channel Safety in MiGo else F hn, i, oi The typing system of § 3 ensures that channel payloads always have whose type is given as: t (i, o) = i; ⊕{o; t hi, oi, t hi, oi}. the expected type. This property is made precise by a standard sub- F F F Our analysis on types does indeed determine the types of the ject reduction result, stating that the semantics of types simulates prime sieve as live, even in the absence of terminating reduction se- the semantics of processes. quences. In tF (i, o), we have an internal choice between a branch Theorem 5.1 (Subject Reduction). Let Γ `B P I T and P −→ that recurses back to tF and another that outputs along o and re- 0 0 0 0 0 P . Then there exists T such that Γ `B0 P I T with T −→ T . curses back to tF . Thus, if we compose a call tF with a type that denotes an infinite sequence of inputs along o, we deem such a We prove that type safety implies program safety, using a corre- composition as live since all the inputs can eventually be synchro- spondence between barbs. Hereafter we write P ⇓ including the o nised with an output from t , given that the semantics of internal case o =a ˜. F choice state that we may indeed move to either branch. Lemma 5.1. Suppose Γ `B P I T . If P ⇓o then T ⇓o. However, the type T of the prime sieve program is an abstract approximation of the actual prime sieve implementation, where Theorem 5.2 (Process Channel Safety). Suppose Γ ` P I T and the test x%n 6= 0 is not obviously guaranteed to ever succeed T is safe. Then P is safe. given that it depends on received data (which is sent by either the generator process G or a previous filter process). Thus, the 5.2 Liveness of Limited Programs interplay of conditional branching and infinite recursion may in The development in § 4 performs an analysis on our abstract rep- general cause a disconnect between the semantics of the types and resentation of processes (i.e. the types), verifying liveness (Defini- those of the concrete processes. For instance, if the test x%n 6= 0 tion 4.4) for fenced types. Our goal is to ensure liveness of a general is replaced by false in the prime sieve example, its type is live class of typable programs. We divide programs into three classes to while the program is not. In the remainder of this section, we discuss the issue of liveness. make precise the conditions under which the semantics of infinite The first class is a set of programs which have a path to termi- processes and types simulate one another, thus implying liveness nate. In this class, a program that is typable with a live type can (even in the presence of infinite branching). always satisfy liveness. To achieve our liveness results, we proceed as follows: Step 1. infinite conditional Inf Definition 5.1 (May Converging Program). Let Γ ` P I T . We Define the notion of ( ), identifying ∗ ∗ write P ∈ May⇓ if for all X0hi −→ P , P −→ 0. a class of programs where conditional branches are executed infinitely often. Proposition 5.1. Assume Γ ` P I T and T is live. (1) Suppose Step 2. Fill the gap between internal choices of types and condi- ∗ there exists P such that X0hi −→ P 6−→. Then P ≡ 0; and (2) If tionals by defining a ∗-conditional (if ∗ then P else Q) which P ∈ May⇓, then P is live. non-deterministically reduces to either P or Q (as the internal We note that the above statement does not restrict the programs to choice ⊕{T,S}), allowing us to identify the subclass of Inf, be finite. A program with infinite reduction sequences can satisfy dubbed alternating conditionals (AC), where programs simulate liveness by Proposition 5.1. For instance: their non-deterministic conditional counterparts. Step 3. {D ,D } in newchan(b); newchan(c); (X hb, ci | X hb, ci) Prove that liveness of types implies liveness of programs in 1 2 1 2 AC. with D1 = X1(b, c) , select{c!hvi; X1hb, ci , b!hwi; 0} Alternating and Non-deterministic Conditionals For Step 1, we D2 = X2(b, c) , select{c?(x); X2hb, ci , b?(y); 0} begin by defining a notion of infinite conditional (Definition 5.5) in is live, as is its corresponding type. programs. Intuitively, we identify programs that reduce forever and The second class is a set of programs which do not contain in- where conditional branches appearing under recursion have their finitely occurring conditional branches (we discuss at length the branches taken infinitely often. issues raised by the interplay of conditional branching and recur- Definition 5.2 (Marked Programs). Given a program P we define § sion in 5.3). If such a process is assigned a live type, then it is its marking, written mark(P), as the program obtained by deter- itself live. For example, any program which does not include con- ministically labelling every occurrence of a conditional of the form ditionals or one with conditionals containing only finite processes if e then Q else R in P, as ifn e then Q else R, such that n is dis- in both branches belong to this class. Consider a program obtained tinct natural number for all conditionals in P. from the one above by replacing 0 in D1 and D2 by X1 and X2, Definition 5.3 (Marked Reduction Semantics). We define a marked Proposition 5.3 (∗-properties). Suppose Γ `B P I T . Then (1) if l ∗ ∗ ∗ ∗ reduction semantics, written P −→ Q, stating that program P re- P ∈ Inf then P ∈ AC; (2) If P ⇓o, then P ⇓o; (3) if P ⇓o duces to Q in a single step, performing action l. The grammar of then T ⇓o. action labels is defined as: Liveness for Infinite Conditionals We now have defined the con- l :=  | n·L | n·R ditions under which programs simulate the behaviour of their types. More precisely, when a program P is well-typed with some live where  denotes an unmarked action, n·L denotes a conditional type T and P ∈ AC holds, then P must itself be live. branch marked with the natural number n in which the then branch is chosen, and n·R denotes a conditional branch in which the else Theorem 5.3 (Liveness). Suppose Γ ` P I T and T is live and branch is chosen. We write P −→ Q for P −→ Q. The marked P ∈ AC. Then P is live. reduction semantics replace rules [IFT] and [IFF] with: To summarise, we identified three significant classes of pro- e ↓ true e ↓ false grams for which type liveness implies liveness: those with at least [IFTM] [IFFM] n n·L n n·R one terminating path (Definition 5.1 and Proposition 5.1) such as if e then P else Q −−→ P if e then P else Q −−→ Q Fibonacci, cf. § 2.1.2; those for which their infinite traces do not Definition 5.4 (Trace). We define an execution trace T of a process contain infinite occurrences of a given conditional (Proposition 5.2) P as the potentially infinite sequence of action labels ~l such that such as Dining Philosophers, cf. § 7.1; and, those with infinite l l traces containing infinite occurrences of conditional branches (Def- P −→1 P −→2 ... ~l = {l l ... } 1 , with 1 2 . We write TP for the set of inition 5.7 and Theorem 5.3) such as Prime Sieve, cf. § 2.1.1. P all possible traces of a process . While a reasonable percentage of real-world programs are in the A trace of the marked reduction semantics identifies exactly first two classes, our empirical observations show that a substantial which branches were selected during the potentially infinite exe- amount of infinitely running programs (with infinitely occurring cution of a program. conditionals) that are not in AC have redundant or erroneous con- We now define infinitely recurring conditionals. We use a re- ditionals. duction context Cr given by: 6. Bounded Asynchrony in Cr := [ ] | (P | Cr) | (Cr | P ) | (νa)Cr MiGo Our framework extends with relative ease to the asynchronous We write Cr[P ] for the process obtained by replacing P for the hole [] in . communication variant of the Go language. As mentioned in § 2, Cr communication channels in Go are implemented as bounded FIFO Definition 5.5 (Infinite Conditional). We say that P has infi- queues, where by default the buffer bound is 0 – synchronous nite conditional branches, written P ∈ Inf, iff mark(P ) −→∗ communication. For bounds greater than 0, communication is then n Cr[if e then Q1 else Q2] = R, for some n, and R has an infinite potentially asynchronous – sends do not block if the buffer is not trace where n·L or n·R appears infinitely often. We say that such full and inputs do not block if the buffer is not empty. an n is an infinite conditional mark and write InfCond(P ) for the Asynchrony significantly affects a program’s liveness. Consider set of all such marks. the following example:

The following statement implies that even programs which con- P (x, y) , x!h1i; y?(z) | y!h2i; x?(z) tain only infinite executions can be live if none of its conditionals appear in traces infinitely often (i.e. our second class of programs). A program that instantiates P (x, y) with synchronous communica- tion channels will necessarily not be live since the output and input Proposition 5.2 (Liveness for Finite Branching). Suppose Γ ` actions in P are mismatched. However, with asynchronous chan- P I T and T is live and P 6∈ Inf. Then P is live. nels, the output actions become non-blocking and the program is indeed live – the output on x on the left-hand side can fire asyn- The main purpose of Definition 5.7 is to identify infinitely running chronously, exposing the input on y which may then fire. Similarly processes where the behaviour of conditional branching approxi- for the output on y and input on x on the right-hand side. mates that of non-deterministic internal choice (i.e. the type-level semantics of internal choice). To make this relationship precise, we Processes and Typing To account for the buffer bounds in define a mapping from MiGo programs to programs where condi- the syntax of MiGo we add a bound n to channel creation, tional branching is replaced by a form of non-deterministic branch- newchan(y:σ, n); P . This number must be equal or greater to ing. This step corresponds to Step 2. zero and must be a literal. We also carry this information in run- ? ∗ time buffers: chσ, ni::˜v and c hσ, ni::˜v (also replacing chσi::∅ and Definition 5.6. The mapping (P ) replaces all occurrences of ? ? ifn e then Q else R, such that n ∈ InfCond(P ), with if ∗ then Q else R. c hσi::˜v by chσ, 0i::∅ and c hσ, ni::˜v for synchronous channels). The reduction semantics of if ∗ then Q else R is defined as follows: We add the reduction rules for asynchronous communication: |v˜| < n e ↓ v [IFT∗] if ∗ then P else Q −→ P [IFF∗] if ∗ then P else Q −→ Q [OUT] c!hei; P | chσ, ni::˜v −→ P | chσ, ni::v · v˜ Definition 5.7 (Alternating Conditionals). We say that P has alternating conditional branches, written P ∈ AC, iff P ∈ Inf [INA] c?(y); P | chσ, ni::˜v · v −→ P {v/y} | chσ, ni::˜v and if P −→∗ (νc˜)Q then Q∗ ⇓ implies Q ⇓ . o o In all other rules that use buffers we add the buffer bound straight- Recall that o ranges over any barbs, including a˜. Moreover, ob- forwardly. The type system is fundamentally unchanged, now ac- serve that the mapping P ∗ only affects conditionals that are ex- counting for buffer bounds: ecuted infinitely often (i.e. those whose behaviour may fail to be Γ, y:ch(σ, n) `s P I T c 6∈ dom(Γ) ∪ s ∪ fn(T ) captured by the type-level analysis). We do not require condition- hNEWi n c als that are not in InfCond(P ) to necessarily match the barbs of Γ `s newchan(y:σ, n); P I (new c)T { /y} their non-deterministic counterpart, since their behaviour is already |v˜| = k over-approximated by the corresponding types. hBUFFi n Γ, a:ch(σ, n) `{a} ahσ, ni::˜v I back In contrast with the types and rules in Figure 4, (new a)T and bac verification outlined in § 4, checking bounded liveness and channel n n are replaced by (new a)T and back , respectively, where k stands safety of the types. An outline of our verification tool chain is for the number of elements in the buffer. shown in Figure 8. Liveness and safety of types are defined as in § 4.4, with two extra rules for the definition of type barbs, pertaining to buffers. In Gong Gong written in Haskell particular we need barbs for writing to a non-full buffer (P ↓•a) The tool checks input behavioural types for k-liveness Behavioural types and reading from a non-empty buffer (P ↓a• ), combined with the and k-channel safety. following additional rules: GoInfer GoInfer written in Go |v˜| < n |v˜| ≥ 1 go/ssa The tool loads source code, type-checks and builds package SSA IR using go/ssa package, then extracts com- ahσ, ni::˜v ↓•a ahσ, ni::˜v ↓a• munication from the SSA IR as behavioural types. Load main() P ↓a Q ↓•a P ↓a• πi ↓a Go source code P | Q ↓[a] P | select{πi; Qi}i∈I ↓[a]

The barbs for asynchronous types, T ↓o, are given below: Figure 8. Workflow of our verification tool chain. 0 k < n k ≤ 1 T ↓a T ↓•a T ↓a• κi ↓a n n 0 • • Type Inference Our type inference tool GoInfer is written in back ↓ a back ↓a T | T ↓[a] T | {κi; Si}i∈I ↓[a] 1 N Go, using the go/ssa package from Go project’s extra tools. The Verification of Types The changes to the semantics of types are package builds Go source code in Static Single Assignment (SSA) straightforward. It is based on the LTS of § 4.1, where rule |NEW| and representation, and provides an API to access the resulting SSA IR |BUF| are replaced by their counterparts below, and four additional programmatically. Starting from the program entry point, i.e. the rules |IN-B|, |OUT-B|, |PUSH| and |POP|. main() function in the main package, we transform the SSA IR into a system of type equations T by converting each SSA block n τ n n end[a] ? |NEW| (new a)T −→ (νa)(T | bac0 ) |BUF| back −−−→ a into an individual type equation. The analysis and conversions are context-sensitive, for example, channels created in different k < n k ≥ 1 instances of a function are different, and loops are unrolled if it |IN-B| • |OUT-B| • bacn −→ba acn bacn −→ba acn is possible to determine the bounds statically. We note that our k k+1 k k−1 analysis is agnostic wrt. aliasing since we do not rely on linearity a •a a• a of channels. In addition to inference, our tool can also check for T −→ T 0 S −→ S0 T −→ T 0 S −→ S0 |PUSH| |POP| trivial conditionals that do not belong to any of the three classes of [a] [a] T | S −→ T 0 | S0 T | S −→ T 0 | S0 programs defined in § 5. Observe that since types abstract away from values and channels Verification Our proof-of-concept verification tool Gong, written are attributed a unique payload type, the semantics does not model in Haskell, inputs a system of type equations T representing a Go message ordering. program’s concurrent behaviour and performs liveness and channel With all the technical machinery in place for the bounded asyn- safety checks on the bounded symbolic semantics. Our represen- chronous setting, we replicate our main results. The proofs are tation of T makes use of the unbound package [53] to deal with essentially identical to those in the synchronous setting. Indeed, the binding structure of types. First, it checks if T is fenced. If asynchrony affects our analysis only in the size of the models to so, we generate all −→k-reachable terms, where k is heuristically- be checked (larger buffer sizes give larger LTSs). The symbolic computed. Finally, each of these terms are checked for k-liveness semantics executes the types up-to a limited number of channels, and k-channel safety by identifying their barbs and successors. which is orthogonal to the number of message a buffer can store, cf. Figure 7. 7.1 Evaluation Theorem 6.1 (Decidability – Asynchrony). For all T s.t. Fenced(T ), We tested our tool-chain on the examples from the paper, from it is decidable whether or not T is k-live (resp. channel safe), for works on static deadlock detection in Go [40], on open-source any k ≥ 0. Go programs from developer guides [43, 45] and GitHub [1], on classic concurrency examples [18, 35], and on concurrent programs Theorem 6.2 (Process Channel Safety and Liveness – Asyn- translated to idiomatic Go from from [34]. chrony). Suppose Γ ` P I T . Table 1 summarises the experimental results. The column “Go 1. If T is channel safe, then P is channel safe. programs” shows the names of the programs. In the columns 2. If T is live and either P ∈ May⇓, P 6∈ Inf or P ∈ AC, then P “Number of channels”, the number of channels given for programs is live. with bounded loops is precise since bounded loops are unrolled and we can statically count the number of channels; for programs with With the revised semantics, the program recursion, we count the channels that appear in the source code. {P (x, y)} in newchan(x:int, 1); newchan(y:int, 1); P hx, yi The columns “Time” show the inference and verification times in seconds. We include a comparison with the tool from [40] to is correctly deemed as live, with the typing given by: demonstrate the extra expressiveness of our approach. If a program 1 1 is “Static”, it has no dynamic spawning of goroutines (a require- {tP (x, y) = (x; y | y; x)} in (new x)(new y)tP hx, yi ment for the usage of the tool of [40]). forselect is a pattern described in [44] where an infinite 7. Implementation for loop and a select statement with two cases are combined to We have implemented our static analysis as a verification tool-chain repeatedly receive (or send). In our example we spawn two gorou- consisting of two parts: First, we analyse Go source code and infer tines, where each goroutine has a for-select loop with compatible behavioural types (§ 3) based on a program’s usage of concurrency primitives. The types are passed to a tool that implements the 1 http://golang.org/x/tools/go/ssa Table 1. Go programs verified by our tool chain. their approach (implemented as cond-recur in Table 1): # chans Analysis [40] Examples LoC unbuf. buf. Live Safe Time (ms) Static Safe {X(a, b) = if e1 then a!he2i.Xha, bi else b?(z); 0, Y (a, b) = select{a?(z); Y ha, bi, b!hi; 0}} in sieve † 19 2 0 XX 209.55 × newchan(a); newchan(b); (Xha, bi | Y ha, bi) fib † 23 2 0 XX 14638.4 × fib-async † 23 1 1 XX 32173.8 × Finally, (3) it is unclear how their tool can deal with the ambiguity fact † 19 2 0 XX 206.63 × dinephil [18, 35] 56 3 0 646921.76 × of context sensitive inter-procedural analysis given their use of the XX oracle tool and the syntactic approach taken in the implementa- jobsched 41 0 1 XX 48.12 × concsys [1] 112 2 0 × X 323.75 × tion. fanin [40, 45] 36 3 0 XX 89.14 XX 1 fanin-alt [40] 37 3 0 × X 209.02 XX Behavioural Types Behavioural type-based techniques (see [27] mismatch [40] 26 2 0 × X 26.59 X × for a broad survey) have been developed for general concurrent fixed [40] 25 2 0 XX 24.58 XX alt-bit [37] 74 0 2 405.78 program analyses [28], such as deadlock-freedom [22, 30], lock- XX XX freedom [32, 42], resource usages [33] and information flow anal- forselect 40 3 0 XX 31.01 XX cond-recur 32 2 0 XX 34.08 XX ysis [29]. All of the type-based techniques above differ from ours in 1 : testing for channel close state is not supported in this version that we perform an analysis on types akin to bounded model- †: examples that are not finite control checking, whereas their works take a type-checking based approach The benchmarks were compiled with ghc 7.10.3 and go1.6.2 executed on Intel Core i5 to deadlock (or lock) freedom. Their techniques are sound against @ 3.20GHz with 8GB RAM. all possible inputs of processes, but often too conservative. Our approach is sound only for some subsets of possible inputs, but channel communication. In the for-select loop, one of the select less conservative. A potential limitation of their techniques is that cases receives (or sends) a message then continues to the next it- subtle changes in channel usage (that may not have a significant eration of the infinite loop; the other case breaks out of the loop effect on a program’s outcome) can produce significantly different upon sending (or receiving) a message from the other goroutine so analysis outcomes (see discussion in [22] and [42]). Moreover, the that both goroutines exit the loop together. The exit condition is dependency tracking can be quite intricate and hard to implement non-deterministic (because of select), but the program is both live in a real language setting. Our fencing-based approach is more cond-recur forselect and safe. is similar to , where one of easily implemented as a post-hoc analysis, covering a wide range the two goroutines contains a for-select loop, but the other has an of Go programs, since it only limits names in recursive call sites ordinary for-loop so that the exit condition of the for-loop is deter- and does not explicitly depend on the ordering of communications ministic. or on computing circularities of channels (provided the programs are in one of the classes of § 5). The work [22, 31] develops a deadlock detection analysis of 8. Related Work and Conclusion asynchronous CCS processes with recursion and new name cre- Static Deadlock Detection in Synchronous Go There are two ation. The analysis is able to reason about infinite-state systems recent works on static deadlock detection for synchronous Go [40, that create networks with an arbitrary number of processes, going 47]. The work [40] extracts Communicating Finite State Ma- beyond those of [30] and [42]. Their approach uses an extension chines [6] whose representation corresponds to session types [24, of the typing system of [30] as a way to extract a so-called lam 48] from Go source code, and synthesises from them a global term from a (typed) process. Lam terms track dependencies be- choreography using the tool developed in [34]. If the choreogra- tween channel usages as pairs of level names. Given a lam term, the phy is well-formed, a program does not have a (partial) deadlock. authors develop a sound and complete decision procedure for cir- This approach is seriously limited due to the lack of expressiveness cularities in dependencies. By separating this decision procedure of (multiparty) session types [26] and its synthesis theory. The ap- from the type system, their system is able to accurately analyse proach expects all goroutines to be spawned before any communi- deadlock-free processes that are not possible in [30] and [42]. cation happens at runtime. This is due to the fact that the synthesis We first point out that the deadlock-freedom property of [22, 31] technique requires all session participants to be present from the does not match with our notion of liveness (which is closer to lock start of the global interaction, meaning that their work cannot han- freedom in [32, 42]). For instance, their analysis accepts program dle most programs with dynamic patterns, such as spawning new Fibbad in § 2.1.2 as a deadlock-free process since a program that threads after communication started. The analysis is also limited to loops non-productively is deadlock-free (but not lock-free). unbuffered channels and does not support asynchrony. For instance, While their analysis can soundly verify unfenced types, which our prime sieve example cannot be verified by their tool, and is in is by construction outside the scope of our work, we note that fact used to clarify the limitations of their approach. Moreover, the the reduction of deadlock-freedom to circularity of lams in their work is limited to the tool implementation, no theoretical property work excludes some natural communication patterns that are finite- nor formalisation is studied in [40]. control, which can be soundly checked by our type-level analysis. The work of [47] uses the notion of forkable behaviours (i.e. Consider the following finite control program (described in [31]), a regular expression extended with a fork construct) to capture which can be directly interpreted as a MiGo type: spawning behaviours of synchronous Go programs, developing a {A(x, y) = x?(); y!hi; Ahx, yi, tool based on this approach to directly analyse Go programs. Their B(x, y) = x!hi; y?(); Bhx, yi} in technique is sound but has some significant theoretical and prac- newchan(a); newchan(b); (Aha, bi | Bha, bi) tical limitations: (1) their analysis does not support asynchrony (buffered channels), closing of channels or usages of the select con- The program above consists of two threads that continuously send struct with non-trivial case bodies; (2) while their liveness analysis and receive along the two channels a and b. This program, despite (when restricted to synchrony) targets the sound fragment of our being finite control (and deadlock-free) is excluded by their anal- analysis, they are more conservative in their approach. For instance, ysis. As described in [31], this happens due to their current for- the following program which is verified as live in our approach (this mulation of the type system assigning a finite number of levels in program belongs to May⇓ in our theory) is judged as a deadlock in recursive channel usages, which entails that finite-control systems that use channels infinitely often (such as the one above) can be us to verify all forms of concurrency present in Go. The results of assigned circular lams, despite being deadlock-free. Our approach, § 4 suggest that it should be possible to encode our analysis as by not relying on such notions of circularity can tackle these finite- a model checking problem, allowing us to: (1) exploit the perfor- control cases in a sound manner. mance enhancements of state of the art model checking techniques; The work of [42] studies a variation of [30, 32] that ensures (2) study more fine-grained variants of liveness; (3) integrate model deadlock freedom and lock freedom of the linear π-calculus, with a checking into the analysis of conditionals to, in some scenarios, de- form of channel polymorphism. By relying on linearity, the system cide the program class (viz. § 5.3). Another interesting avenue of in [42] rules out many examples that are captured by our work future work is to explore the integration of type-checking based ap- (although it can in principle analyse unfenced types). The fib and proaches into our framework, including those aimed at liveness and diningphilosopher examples denote patterns that are untypable termination-checking (such as [17, 25, 29, 32]). These techniques in [42], but can be verified in our tool. Our tool can also verify eliminate false positives arising due to issues on divergence of pro- programs morally equivalent to most examples discussed in [42], cesses, which are related to our classification of § 5, hence would see Table 1 in § 7.1. be useful to identify a set of processes which conform, e.g. Propo- sition 5.1. This would enable a more fine-grained analysis, taking Session Types The work on session types is another class of be- advantage of the strong soundness properties of this line of work. havioural typing systems that rely crucially on linearity in chan- Moreover, the latter mentioned works could be applied in order to nel types to ensure certain compatibility properties of structured soundly approximate the program classification studied in § 5.3. communication between two (binary [24]) or more (multiparty [26]) participants. Progress (deadlock-freedom on linear channels) is guaranteed within a single session, but not for multiple inter- Acknowledgments leaved sessions. Several extensions to ensure progress on multiple We gratefuly acknowledge Naoki Kobayashi for finding flaws in sessions have been proposed, e.g. [11, 13, 14]. Our main examples Sections 4 and 5 in earlier versions of this work; as well as for de- are not typable in these systems for the same reasons described tailed comments on Sections 7 and 8. The present version aims at in the above paragraph. Their systems do not ensure progress of correcting these errors. In particular, we have revised the defini- shared names, which are key in our examples. tion of liveness and safety for types (Definitions 4.4 and 4.5) and A different notion of liveness called request-response is pro- removed theorems related to soundness of our analysis of general posed in [16] based on binary session types. Their liveness means liveness and safety for types. We have revised several remarks in that when a particular label of a branching type (a request) is se- the comparison between our work and [22, 29, 30, 32] (§ 7 and 8). lected, a set of other labels (its responses) is eventually selected. We also would like to thank Elena Giachino, Raymond Hu and The system requires a priori assumptions that a process must sat- Luca Padovani for fruitful discussions on this work, as well as the isfy lock-freedom and annotations of response labels in types. anonymous referees for their comments. This work is partially sup- The works of [9, 10, 52] based on linear logic ensure progress ported by EPSRC EP/K034413/1, EP/K011715/1, EP/L00058X/1 in the presence of multiple session channels, but the typing dis- and EP/N027833/1; and by EU FP7 612985 (UPSCALE) and cipline disallows modelling of process networks with cyclic pat- COST Action IC1405 (RC). terns (such as prime sieve). In these works, progress denotes both deadlock and lock-freedom in the sense of [42]. However, to ensure logical consistency general recursion is disallowed. In the presence References of general recursion [50], progress is weakened; ensuring all ty- [1] Collection of Golang concurrency patterns. https://github. pable processes are deadlock-free but not necessarily lock-free. The com/stillwater-sc/concurrency. work of [51] studies a restricted form of corecursion that ensures [2] Tool chain. http://mrg.doc.ic.ac.uk/tools/gong. both deadlock- and lock-freedom in the context of logic-based pro- [3] V. Agababov, M. Buettner, V. Chudnovsky, M. Cogan, B. Greenstein, cesses. However, since the typing discipline ensures termination of S. McDaniel, M. Piatek, C. Scott, M. Welsh, and B. Yin. Flywheel: all computations it is too restrictive for a more practical setting such Google’s Data Compression Proxy for the Mobile Web. In NSDI 2015, as ours. 2015. [4] D. G. Anderson. Experience with ePaxos: Systems Research using Effect Systems The work [41] introduces a type and effect sys- Go. 2013. https://da-data.blogspot.co.uk/2013/10/ tem for a fragment of concurrent ML (including dynamic channel experience-with-epaxos-systems-research.html. creations and process spawning) with a predicate on types which [5] Andrew Gerrand. Share Memory By Communicating. https:// guarantees that typed programs are limited to a finite communica- blog.golang.org/share-memory-by-communicating. tion topology. Their types are only used to check whether a pro- gram has a finite communication topology, that is, whether a pro- [6] D. Brand and P. Zafiropulo. On communicating finite-state machines. J. ACM, 30:323–342, April 1983. gram uses a bounded number of channels and a bounded number of processes. No analysis wrt. safe or live communication is given, [7] N. Busi, M. Gabbrielli, and G. Zavattaro. Replication vs. recursive which is the ultimate goal of our work. definitions in channel based calculi. In ICALP’03, pages 133–144, 2003. Conclusion and Future Work Since the early 1990s, behavioural [8] N. Busi, M. Gabbrielli, and G. Zavattaro. Comparing recursion, type theories which formalise “types as concurrent processes” [28] replication, and iteration in process calculi. In ICALP’04, pages 307– have been studied actively in models for concurrency [27]. Up to 319, 2004. this point, there have been few opportunities to apply these tech- [9] L. Caires and F. Pfenning. Session types as intuitionistic linear propo- niques directly to a real production-level language. The Go lan- sitions. In CONCUR, volume 6269 of LNCS, pages 222–236. Springer, guage opens up such a possibility. This work proposes a static ver- 2010. ification framework for liveness and safety in Go programs based [10] L. Caires, F. Pfenning, and B. Toninho. Linear logic propositions on a bounded execution of fenced behavioural types. We develop a as session types. Mathematical Structures in Computer Science, tool that analyses Go code by directly inferring behavioural types 26(3):367–423, 2016. with no need for additional annotations. [11] M. Carbone, O. Dardha, and F. Montesi. Progress as compositional In future work we plan to extend our approach to account for lock-freedom. In COORDINATION, volume 8459 of LNCS, pages channel passing, and also lock-based concurrency control, enabling 49–64. Springer, 2014. [12] S. Chaki, S. K. Rajamani, and J. Rehof. Types as models: model [34] J. Lange, E. Tuosto, and N. Yoshida. From Communicating Machines checking message-passing programs. In POPL’02, pages 45–57, 2002. to Graphical Choreographies. In S. K. Rajamani and D. Walker, [13] M. Coppo, M. Dezani-Ciancaglini, and N. Yoshida. Asynchronous editors, POPL’15, pages 221–232. ACM Press, 2015. Session Types and Progress for Object-Oriented Languages. In [35] J. Magee and J. Kramer. Concurrency: State Models &Amp; Java FMOODS’07, volume 4468 of LNCS, pages 1–31, 2007. Programs. John Wiley & Sons, Inc., New York, NY, USA, 1999. [14] M. Coppo, M. Dezani-Ciancaglini, N. Yoshida, and L. Padovani. [36] R. Milner. A Calculus of Communicating Systems, volume 92 of Global Progress for Dynamically Interleaved Multiparty Sessions. Lecture Notes in Computer Science. Springer, Berlin, 1980. MSCS, 26(2):238–302, 2016. [37] R. Milner. Communication and Concurrency. Prentice-Hall, Inc., [15] M. Dam. Model checking mobile processes. Inf. Comput., 129(1):35– Upper Saddle River, NJ, USA, 1989. 51, Aug. 1996. [38] R. Milner and D. Sangiorgi. Barbed bisimulation. In W. Kuich, editor, [16] S. Debois, T. T. Hildebrandt, T. Slaats, and N. Yoshida. Type-checking ICALP, volume 623 of LNCS, pages 685–695. Springer-Verlag, 1992. liveness for collaborative processes with bounded and unbounded re- [39] I. Moraru, D. G. Andersen, and M. Kaminsky. There is More Con- cursion. Logical Methods in Computer Science, 12(1), 2016. sensus in Egalitarian Parliaments. In SOSP’13, pages 358–372, New [17] R. Demangeon, D. Hirschkoff, and D. Sangiorgi. Termination in York, NY, USA, 2013. ACM. impure concurrent languages. In CONCUR’10, volume 6269 of LNCS, [40] N. Ng and N. Yoshida. Static Deadlock Detection for Concurrent Go pages 328–342. Springer, 2010. by Global Session Graph Synthesis. In CC 2016, pages 174–184. [18] E. W. Dijkstra. Cooperating sequential process. Programming Lan- ACM, 2016. guages, pages 43–112, 1965. [41] H. R. Nielson and F. Nielson. Higher-order concurrent programs with [19] E. D’Osualdo, J. Kochems, and C. L. Ong. Automatic verification of finite communication topology (extended abstract). In POPL ’94, erlang-style concurrency. In SAS, volume 7935 of LNCS, pages 454– pages 84–97. ACM, 1994. 476. Springer, 2013. [42] L. Padovani. Deadlock and Lock Freedom in the Linear π-Calculus. [20] B. Fitzpatrick. go 1.5.1 linux/amd64 deadlock detection failed, In T. A. Henzinger and D. Miller, editors, CSL-LICS’14, pages 72:1– 2015. https://github.com/golang/go/issues/12734# 72:10. ACM Press, 2014. issuecomment-142859447. [43] Rob Pike. Go Concurrency Patterns, 2012. https://talks. [21] S. Gay and M. Hole. Subtyping for Session Types in the Pi-Calculus. golang.org/2012/concurrency.slide. Acta Informatica, 42(2/3):191–225, 2005. [44] Sameer Ajamni. Advanced Go Concurrency Patterns, 2013. https: [22] E. Giachino, N. Kobayashi, and C. Laneve. Deadlock analysis of //talk.golang.org/2013/advconc.slide. unbounded process networks. In CONCUR, volume 8704 of LNCS, [45] Sameer Ajmani. Go Concurrency Patterns: Pipelines and cancellation, pages 63–77. Springer, 2014. 2014. https://blog.golang.org/pipelines. [23] C. Hoare. Communicating Sequential Processes. Prentice Hall, 1985. [46] D. Sangiorgi and D. Walker. The π-Calculus: a Theory of Mobile [24] K. Honda, V. T. Vasconcelos, and M. Kubo. Language primitives and Processes. Cambridge University Press, 2001. type disciplines for structured communication-based programming. [47] K. Stadmuller,¨ M. Sulzmann, and P. Thiemann. Static Trace-Based In ESOP’98, volume 1381 of LNCS, pages 22–138. Springer-Verlag, Deadlock Analysis for Synchronous Mini-Go. In APLAS, volume 1998. 10017 of LNCS, 2016. [25] K. Honda and N. Yoshida. A uniform type structure for secure [48] K. Takeuchi, K. Honda, and M. Kubo. An Interaction-based Language information flow. ACM Trans. Program. Lang. Syst., 29(6), 2007. and its Typing System. In PARLE’94, volume 817 of LNCS, pages [26] K. Honda, N. Yoshida, and M. Carbone. Multiparty Asynchronous 398–413. Springer-Verlag, 1994. Session Types. In POPL’08, pages 273–284. ACM, 2008. A full [49] The Go Authors. Effective Go. https://golang.org/doc/ version in JACM: 63(1-9):1–67, 2016. effective_go.html. [27]H.H uttel,¨ I. Lanese, V. T. Vasconcelos, L. Caires, M. Carbone, P.- [50] B. Toninho, L. Caires, and F. Pfenning. Higher-order processes, M. Denielou,´ D. Mostrous, L. Padovani, A. Ravara, E. Tuosto, H. T. functions, and sessions: A monadic integration. In ESOP’13, pages Vieira, and G. Zavattaro. Foundations of session types and behavioural 350–369, 2013. contracts. ACM Comput. Surv., 49(1):3:1–3:36, Apr. 2016. [51] B. Toninho, L. Caires, and F. Pfenning. Corecursion and non- [28] A. Igarashi and N. Kobayashi. A generic type system for the pi- divergence in session-typed processes. In TGC’14, pages 159–175, calculus. Theor. Comput. Sci., 311(1-3):121–163, 2004. 2014. [29] N. Kobayashi. Type-based information flow analysis for the pi- [52] P. Wadler. Proposition as Sessions. In ICFP’12, pages 273–286, 2012. calculus. Acta Inf., 42(4-5):291–347, 2005. [53] S. Weirich and B. Yorgey. Unbound library. https://hackage. [30] N. Kobayashi. A new type system for deadlock-free processes. In haskell.org/package/unbound. CONCUR’06, volume 4137 of LNCS, pages 233–247, 2006. [54] M. Welsh. Rewriting a large production system in Go. [31] N. Kobayashi and C. Laneve. Deadlock analysis of unbounded process 2013. http://matt-welsh.blogspot.co.uk/2013/08/ networks. Inf. Comput., 252:48–70, 2017. rewriting-large-production-system-in-go.html. [32] N. Kobayashi and D. Sangiorgi. A hybrid type system for lock- [55] K. Yasukata, N. Kobayashi, and K. Matsuda. Pairwise reachability freedom of mobile processes. TOPLAS, 32(5):16:1–16:49, May 2008. analysis for higher order concurrent programs by higher-order model [33] N. Kobayashi, K. Suenaga, and L. Wischik. Resource usage analysis checking. In CONCUR, volume 8704 of LNCS, pages 312–326. for the p-calculus. Logical Methods in Computer Science, 2(3), 2006. Springer, 2014. A. Appendix for Section 4 Definition A.3 (Number of Occurrences of t in T ). A.1 Decidability 0 if T = 0  def  0 0 Definition A.1 (Size of T ). Define |T | = |T |∅, where |T |t if T = α; T and α ∈ {κ, end[a]}  0 0 0 |T |t if T = (νa)T or (new a)T 0 if T = 0   |T |t = max({|Ti|t }i∈I ) if T = ⊕{Ti}i∈I or {Ti}i∈I 1 + |T 0|G if T = α; T 0, α ∈ {κ, end[a]}  |T | + |T | if T = T | T N  0 G 0 0  1 t 2 t 1 2 1 + |T | if T = (νa)T or (new a)T  def  1 if T = tha˜i |T |G = P |T |G if T = ⊕{T } or {T }  i∈I i i i∈I i i∈I 0 if T = sha˜i and s 6= t |T |G + |T |G if T = T | T N  1 2 1 2  0 a˜ G∪{t} 0 Lemma A.2. If Fenced(T ), then for all t(˜x) = T ∈ T , |T { /x˜}| if T = tha˜i, t ∈/ G, t(˜x) = T  k |x˜| 0 if T = tha˜i, t ∈ G ∀k ≥ 0 : |Ux˜ (T )|t ≤ |T |

Definition A.2 (Limited Unfolding). Let Gk be the function from th Proof. To increase the number of occurrences of a variable, we dom(T ) to N that always returns k. The k unfolding of T wrt. a˜, must have parallel processes, which implies that we must strictly k Gk written Ua˜ (T ) is given by Ua˜ (T ), defined below. decrease the number of free names hence eventually reach process  G 0 0 that do no use any variables in x˜. α; Ua˜ (T ) if T = α; T and  It is a simple observation that  α ∈ {τ, a, a, end[a]}  k k 0 if T = 0 ∀k ≥ 0 : |Ux˜ (T )|t ≤ |T |   G 0 0 (νa)Ua˜ (T ) if T = (νa)T Hence, the results holds trivially for any k < |x˜|.  G 0 0 Assume that the result does not hold by contradiction and take (new a)Ua˜ (T ) if T = (new a)T G  G k ≥ |x˜| such that Ua˜ (T ) = ⊕{Ua˜ (Ti)}i∈I if T = ⊕{Ti}i∈I  G k |x˜| k+1 |x˜|  {Ua˜ (Ti)}i∈I if T = {Ti}i∈I |U (T )| ≤ |T | |U (T )| > |T |  x˜ t and x˜ t NU G(T ) | U G(T ) T = NT | T  a˜ 1 a˜ 2 if 1 2 For the number of occurrences of t to increase strictly, we must  G[t7→i−1] 0 ˜b ˜ ˜ Ua˜ (T /x˜ ) if T = thbi, b ∩ a˜ 6= ∅, have  0  t(˜x) = T ∈ T ,G(t) = i > 0 h ˜ i  T = C C1[thbi] | C2[thc˜i] th˜bi if G(t) = 0 or ˜b ∩ a˜ = ∅ since ◦ ;x ˜ ; ◦ `t T , we must have ˜b ≺ x˜ and c˜≺ x˜, which implies, Lemma A.1 shows that there cannot be an infinitely decreasing by Lemma A.1, that the unfolding will terminate. sequence a˜1 · · · a˜k · · · , when unfolding a type. We note that In particular, we have a strictly decreasing number of names due to our convention that in t(˜x) = T , we have x˜ ⊆ fv(T ), a type from x˜ further down in the tree. Thus after |x˜| unfoldings, no names T can only remember the names it received as parameters. from x˜ will be left in each occurrences of thd˜i (i.e., d˜∩ x˜ = ∅) Lemma A.1. Given a chain and t cannot be unfolded further.

a˜1 a˜2 · · · a˜k k > |a˜1| Lemma A.3. For all types T , if T ≡ T 0 or T −→ T 0, then 0 such that ∀1 ≤ i < k : |a˜i| = |a˜i+1|, and ∀1 ≤ i < k : a ∈ fn(T ) ⊇ fn(T ). a˜ ∧ a∈ / a˜ =⇒ ∀i < j ≤ k : a∈ / a˜ . There exists i i+1 j Proof. Straightforward from the rules in Figure 5. n ≤ |a˜1| such that ∀j ≥ n :a ˜1 ∩ a˜j = ∅. Lemma 4.1 (Finite Control). If Fenced(T ), then the set Proof. Relation a˜ ≺ ˜b implies that we must have ∗ {[T ]≡ | ∅ C t0hi −→k ∅ C T } is finite, for any finite k. a˜ = aj+1 ··· ak · b1 ··· bj ≺ a1 ··· ak = ˜b Proof. Under the symbolic semantics, by Lemma A.2, there can be with k ≥ 1 and j ≥ 1. only a limited number of occurrences of each variable. Hence the Hence, if we remove one element at each step of the chain (the processes cannot unfold infinitely wide, therefore, by Lemma A.3, worst case), we obtain a chain of the form: only finitely many names are required. If infinitely many names are created, they can be garbage collected by structural congruence, in a˜1 = a1 ··· ak a˜2 = a2 ··· ak · b1 · · · particular any type (νa˜) th˜bi can be replaced by (ν˜b) th˜bi if a˜ ⊇ ˜b. and, in general: a˜i = ai ··· ak · b1 ··· bi−1 if i ≤ k; thus we have Finally, processes that differ only from their bound names can be a˜i ∩a˜1 = ∅ when i > k, as expected, since by assumption we have equated by alpha-equivalence. ∀1 ≤ i < k : a ∈ a˜i ∧ a∈ / a˜i+1 =⇒ ∀i < j ≤ k : a∈ / a˜j (i.e., once a has been removed from a˜i it cannot appear again Theorem 4.1 (Decidability). For all T s.t. Fenced(T ), it is decid- further in the chain). able whether or not T is k-live (resp. channel safe), for any k ≥ 0. Lemma A.2 shows that the number of occurrences of a type Proof. By Lemma 4.1, the LTS generated by the k-symbolic se- variable in the kth unfolding of T is bounded by a function of the mantics is finite state. Hence, checking k-liveness and k-safety is size of the syntactical tree of T . decidable since the k-symbolic LTS is finite and thus each term is using only finitely many names (after garbage collection). B. Appendix for Section 5 Theorem 5.3 (Liveness). Suppose Γ ` P I T and T is live and We use the following lemma for the proofs. P ∈ AC. Then P is live. 0 Lemma B.1 (Inversion). 1. If Γ `B P I T and P ≡ (νc)P Proof. If all conditionals in P are infinite, then the result follows 0 0 0 0 0 0 then T ≡ (νc)T , with Γ `B0 P I T , for some Γ and B , by Lemma B.3. Otherwise, by Lemma B.4 we can execute P until with Γ ⊆ Γ0 and B ⊆ B0. there are no more finite conditionals, from which then the result follows by Subject Reduction (Theorem 5.1) and Lemma B.3. 2. If Γ `s P I T and P ≡ P1 | P2 then T ≡ T1 | T2, with Γ `B1 P1 I T1 and Γ `B2 P2 I T2, with B = B1 ∪ B2. C. Appendix for Section 6 (Asynchrony) Proof. Straightforward from the typing rules. Definition C.1 (Asynchronous type barbs). Theorem 5.1 (Subject Reduction). Let Γ `B P I T and P −→ 0 0 0 0 0 0 k < n k ≤ 1 T ↓a T ↓•a T ↓a• κi ↓a 0 P . Then there exists T such that Γ `B P I T with T −→ T . n n 0 back ↓•a back ↓a• T | T ↓[a] T | {κi; Si}i∈I ↓[a] Proof. We use Lemma B.1 and similar inversion lemmas for other N Proofs of Theorems 6.1 and 6.2 Since the definitions of liveness constructs. Then the rest is straightforward by induction on typing and safety stay the same, and since the semantics are essentially and case analysis on the semantics of processes. isomorphic (the symbolic semantics is unaffected, notably) the proofs are essentially identical with the synchronous cases. Theorem 5.2 (Process Channel Safety). Suppose Γ ` P I T and T is safe. Then P is safe.

∗ D. Appendix for Section 7 Proof. Suppose X0hi −→ (νc˜)Q and Q ↓a? . Then by Lemma B.1, there exists T such that Γ0 ` Q T . By Lemma 5.1, we have Below we present source code of the examples we created for our I evaluation. T ↓a? . Since T is safe, ¬(T ⇓end[a]) and ¬(T ⇓a). This implies, by applying Lemma 5.1 again, ¬(Q ⇓end[a]) and ¬(Q ⇓a). D.1 forselect Proposition 5.1. Assume Γ ` P I T and T is live. (1) Suppose The forselect program spawns two communicating for-select ∗ there exists P such that X0hi −→ P 6−→. Then P ≡ 0; and (2) If loop and both goroutines terminate when channel term is used. P ∈ May⇓, then P is live. The choice of sending or receiving from the channels inside the for-select loop is non-deterministically decided by the select Proof. Assume Γ ` P I T and T is live. primitive in Go. ∗ (1) Suppose by contradiction that X0hi −→ P 6−→ but P 6≡ 0. Then 1 package main there exists Q such that P ≡ (νa˜)(Q) and Q ↓b or Q ↓˜b for some 2 b or ˜b. Then by Lemma 5.1, it contradicts T is live. 3 import "fmt" 4 (2) By definition of liveness (there is always a path (τ-actions) to 5 func sel1(term, ch chan int, done chan struct{}) { reach the term 0, which is live). 6 for { 7 select { Proposition 5.2 (Liveness for Finite Branching). Suppose Γ ` 8 case <-term: // Receive termating message. 9 fmt.Println("sel1: recv") P I T and T is live and P 6∈ Inf. Then P is live. 10 done <- struct{}{} 11 return ∗ Proof. Suppose P 6∈ Inf and X0hi −→ (ν~a)P . Then by Inversion 12 case ch <- 1: 0 fmt.Println("sel1: send") Lemma (Lemma B.1), there exists Γ `B P I T . Then P ⇓b iff 13 14 } T ⇓ and P ⇓˜ iff T ⇓ since the reduction of T coincides with b b ˜b 15 } the reduction of P . Hence P is live. 16 } 17 Proof of Theorem 5.3 18 func sel2(term, ch chan int, done chan struct{}) { 19 for { Lemma B.2. Suppose Γ ` P I T , T is live and P is conditional- 20 select { free, then P is live. 21 case <-ch: 22 fmt.Println("sel2: recv") Proof. Straightforward from the fact that all process moves are 23 case term <- 2: // Send terminating message. 24 fmt.Println("sel2: send") strongly matched by type moves and T is live. 25 done <- struct{}{} 26 return Lemma B.3. Suppose Γ ` P I T , T is live, P ∈ AC and P and 27 } all marked conditionals in mark(P) are in InfCond, then P is live. 28 } 29 } Proof. Since all conditionals in P are infinite, then by the definition 30 31 func main() { of AC and T being live gives us that P ⇓o iff T ⇓o, from which 32 done := make(chan struct{}) the result follows. 33 term := make(chan int) // Terminating channel. 34 data := make(chan int) Lemma B.4. Suppose Γ ` P I T , P ∈ AC, T is live and 35 go sel1(term, data, done) mark(P) has a marked branch n that is not in InfCond(P). Then 36 go sel2(term, data, done) 37 P has an infinite trace in which n no longer appears. 38 <-done 39 <-done Proof. By P ∈ AC it follows that P cannot have a finite trace, thus 40 } the result follows by n 6∈ InfCond(P). D.2 cond-recur 21 22 func morejob() bool { The cond-recur program is similar to forselect but the 23 i++ decision of when to terminate the for-select loop is determined by 24 return i < 20 a conditional in the x goroutine, rather than a terminating channel. 25 } 26 1 package main 27 func producer(q chan int, done chan struct{}) { 2 28 for morejob() { 3 import "fmt" 29 q <-i 4 30 } 5 func x(ch chan int, done chan struct{}) { 31 close(done) 6 i := 0 32 } 7 for { 33 8 if i<3{ // This condition decides when to 34 func main() { terminate. 35 jobQueue := make(chan int) 9 ch <-i 36 done := make(chan struct{}) 10 fmt.Println("Sent",i) 37 go worker(1, jobQueue, done) 11 i++ 38 go worker(2, jobQueue, done) 12 } else { 39 producer(jobQueue, done) 13 done <- struct{}{} // Send terminate message 40 time.Sleep(1 * time.Second) . 41 } 14 return // Break out of loop. 15 } 16 } E. Additional Discussion of Related Work 17 } 18 We discuss some additional related work. 19 func main() { Session types. Another key limitation of session typing is that 20 done := make(chan struct{}) 21 ch := make(chan int) conditional branching is usually typed with the following rule: 22 go x(ch, done) // Spawna decision making Γ ` e:bool Γ ` P ∆ Γ ` Q ∆ goroutine. I I 23 FINISH: Γ ` if e then P else Q I ∆ 24 for { The rule above (even in the presence of subtyping, e.g. [21]) en- 25 select { 26 case x := <-ch: forces that the communication behaviour of the two branches must 27 fmt.Println(x) be fundamentally the same. This turns out to be too restrictive in 28 case <-done: // Terminate message. practice, where branching is mostly used to define the conditions 29 break FINISH // Break out of loop. under which behaviour should indeed be morally different. 30 } 31 } Model Checking. The classical work [12] verifies progress 32 } properties (ϕ) of the π-calculus applying a LTL model checking tool to types which take the form of CCS terms. To limit state-space D.3 jobsched explosion, the work relies on an assume-guarantee reasoning tech- nique. The work requires “user input” type signatures (annotations) The jobsched program sets up a shared job queue between two on processes. Our framework does not require such annotations. workers to process the incoming jobs. The work [19] studies a verification of Erlang programs where 1 package main processes communicate via unbounded mailboxes and can be 2 spawned dynamically and potentially infinitely. Erlang programs 3 import ( 4 "fmt" are modelled as vector addition systems (VAS) and a VAS-based 5 "time" tool is used to check simple reachability properties. The model 6 ) is limited to a bounded number of channels, hence the topologies 7 represented in their work are more limited than those induced by 8 var i int 9 fenced types. 10 func worker(id int, jobQueue <-chan int, done <-chan The work [55] uses higher-order model checking to verify a struct{}) { concurrent calculus which features dynamic process creation. They for 11 { transform processes into higher-order recursion schemes, which in 12 select { 13 case jobID := <-jobQueue: turn generates action trees on which they can check whether two 14 fmt.Println(id,"Executing job", jobID) threads enter the same critical section. Because the action trees 15 case <-done: model executions only at the single process level (i.e. ignoring how 16 fmt.Println(id,"Quits") processes are interleaved and communicate with each other), it is 17 return 18 } not straightforward to adapt their approach to verify our liveness 19 } and safety properties. 20 }