Lectures onType Theory
Chapter 52
Chapter 52Optional

Typestate and State-Transition Protocols

Prerequisites. Direct starred prerequisites: none. No later core chapter depends on this route.

A program may own a file handle exactly once and still execute read after close. Ownership controls aliases; it does not say which operations are available in the handle’s current state. The missing datum is a state that changes when an operation succeeds.

The file protocol

Let the protocol states be Q={open,eof,closed}. A protocol transition is a labeled edge qaq between states. The file automaton has openread(more)open,openread(eof)eof,opencloseclosed,eofcloseclosed. There is no outgoing read edge from eof or closed, and there is no outgoing close edge from closed. These absences, rather than run-time tests inserted by the type system, reject the two protocol errors.

Definition 52.1 — State-indexed handle

For qQ, the affine type File[q] contains one file handle whose run-time state is q. A protocol context Δ is a finite map from handle variables to such types. It has exchange but no contraction; weakening is permitted only by an explicit finalizer that consumes the handle.

The distinction between a state and a type index is exact. The run-time map H stores H()=q for a concrete handle identity . The static context stores f:File[q]. A value environment η maps f to . We write H,ηtsΔ if and only if every f:File[q]Δ has a distinct η(f)= and H()=q. Distinctness is the alias restriction. This is the sole-handle pattern of chapter 19 instantiated with a file: ownership supplies the non-aliasing fact, while the index q adds the operation-availability invariant that ownership alone lacks.

Commands and flow-sensitive typing

Programs are in administrative form: P::=returnopen f;Pclose f;Pread f as{more(f)Pmeof(f)Pe}X(f), where X ranges over declared protocol procedures. The flow judgment ΔPΔ means that P consumes the handles in Δ according to their states and, if it returns, leaves exactly Δ. The principal rules are

ΔreturnΔ
TS-Return
Δ,f:File[open]PΔfdom(Δ)
Δopen f;PΔ
TS-Open
q{open,eof}ΔPΔ
Δ,f:File[q]close f;PΔ
TS-Close
Δ,f:File[open]PmΔΔ,f:File[eof]PeΔ
Δ,f:File[open]read f as{more(f)Pmeof(f)Pe}Δ
TS-Read

The two read branches start with different state indices but must finish in the same context Δ. This is the join condition. Taking the union of the two contexts would be unsound: a handle closed on one branch and open on the other would then appear available after the join.

Fix a procedure table Ω. Its entries have the form X:(x:File[q])ΔX=PX, where x may occur in the output context. To keep this first-order interface closed, dom(ΔX){x}; a handle opened inside PX must be closed before the procedure returns. The table is suppressed in the flow judgment. Its call rule is X:(x:File[q])ΔX=PXΩf:File[q]X(f)ΔX[f/x]TSCall. A recursive table is admitted only after every declared body checks from its declared input to its declared output while calls use the assumed table. The rule therefore unfolds a previously checked body; it does not infer a recursive invariant.

The following program closes the file on every returning path: drain(f)=read f as{more(f)drain(f)eof(f)close f;return},main=open f;drain(f). Its checked signatures are drain:File[open],main. The recursive branch re-establishes the exact input state of drain. The eof branch consumes the handle. Thus both branches join at the empty context.

Exercise 52.1

★☆☆ Write the TS-Read derivation for drain. Mark the recursive call signature in the more branch and the TS-Close premise in the eof branch.

Operational transitions

A configuration is H,η,P. Opening chooses a handle dom(H). Closing removes the handle from H and its variable from η. A read step consults an external reply b{more,eof}:

dom(H)
H,η,open f;PtsH[open],η[f],P
TSO-Open
η(f)=H(){open,eof}
H,η,close f;PtsH,ηf,P
TSO-Close
η(f)=H()=open
H,η,read f as{PmPe}tsH,η,Pm
TSO-Read-More
η(f)=H()=open
H,η,read f as{PmPe}tsH[eof],η,Pe
TSO-Read-Eof

The abbreviated branch syntax in the dynamics keeps the same binder f in each continuation. Procedure calls use X:(x:File[q])ΔX=PXΩH,η,X(f)tsH,η,PX[f/x]TSOCall Before this rule is used, every procedure-local handle binder is alpha-renamed fresh for H, η, and the caller; the displayed substitution then renames the formal handle to f.

Let Df abbreviate the displayed body of drain(f). For an input consisting of one chunk followed by eof, the complete trace is ,,maintsTSOOpenopen,f,drain(f)tsTSOCallopen,f,DftsTSOReadMoreopen,f,drain(f)tsTSOCallopen,f,DftsTSOReadEofeof,f,close f;returntsTSOClose,,return. Every relation is one step. The final configuration has no live handle.

Exercise 52.2

★☆☆ Attempt to type open f;close f;close f;return. Write the context after the first close and identify the absent premise of the second TS-Close step.

The state invariant

Lemma 52.2 — One-step state preservation

Assume H,ηtsΔ, ΔPΔ, and H,η,PtsH1,η1,P1. Then some Δ1 satisfies H1,η1tsΔ1andΔ1P1Δ.

Proof of Lemma 52.2 — One-step state preservation

Proof. Induct on the dynamic step. For TSO-Open, the fresh identity extends both maps and TS-Open’s premise supplies the continuation derivation; take Δ1=Δ,f:File[open]. For TSO-Close, agreement gives the state q required by TS-Close; removing , f, and their unique correspondence leaves agreement with the rule’s premise context Δ.

For TSO-Read-More, take Δ1=Δ,f:File[open] and use the first TS-Read premise. For TSO-Read-Eof, update the one agreeing heap entry to eof, take Δ1=Δ,f:File[eof], and use the second premise. Procedure unfolding uses the checked declaration and renames its formal handle to f; the injectivity clause of agreement is unchanged. These are all dynamic rules. ◻

Lemma 52.3 — Protocol progress

If H,ηtsΔ and ΔPΔ, then P=return or some H1,η1,P1 satisfy H,η,PtsH1,η1,P1, provided a read command receives either the reply more or eof.

Proof of Lemma 52.3 — Protocol progress

Proof. Invert the last typing rule. TS-Return gives the first alternative. For TS-Open, choose an identity outside the finite domain of H. For TS-Close, agreement provides the concrete identity and exact state, so TSO-Close applies. For TS-Read, agreement gives an open heap entry; the external reply selects one of the two read rules. A procedure call unfolds because every admitted signature has a checked body. ◻

Theorem 52.4 — Protocol safety

Assume H0,η0tsΔ0 and Δ0P0Δf. Along every finite reduction from H0,η0,P0, written H0,η0,P0tsc, no command reads or closes a handle whose run-time state lacks the corresponding protocol edge. If execution returns, the final heap and value environment agree with Δf.

Proof of Theorem 52.4 — Protocol safety

Proof. Induct on the length of the reduction. The zero-step case is the initial agreement. For a successor length, apply lemma 52.2 to obtain the typed agreeing successor configuration, then apply the induction hypothesis. Every read or close step uses a dynamic premise matching an edge printed in section 52.1; progress shows that a well-typed command is never blocked by a missing edge. At return, the flow judgment is TS-Return, so its input context is Δf, and the maintained agreement gives the final claim. ◻

The theorem depends on affine handle identity. If agreement allowed η(f)=η(g)=, one alias could close while the other retained type File[open]; the next read would refute preservation.

Encodings and theorem boundaries

Sources.

Strom and Yemini’s archived PDF page 3 (printed page 159) defines typestates, operation preconditions, and operation-dependent poststates [SY86]. Modern modular systems combine protocol states with access permissions so that aliases cannot invalidate a state guarantee; the archived modular-typestate PDF pages 8 and 10 print the input stream automaton, permissions, and branching read postcondition [BA07]. Featherweight Typestate makes state change a primitive language operation: its archived PDF pages 4–5 print the file automaton and aliasing counterexample, and pages 21 and 33 state progress and preservation for the static and gradual calculi [GTWA14]. The finite kernel in this chapter is book-owned; its preceding proof does not claim to be a translation of any of those richer object calculi.

A sum-type encoding returns File[open]File[eof] from read. It accounts for branching but needs the same affine elimination discipline to prevent duplication. A binary session type coordinates dual communicating endpoints; the file kernel has one local resource and therefore proves no duality, communication fidelity, or deadlock freedom. Affine typestate combines the two independent obligations visible here: a state index selects the available edge, and affinity prevents a stale alias.

Protocol safety is a safety property. The external source may return more forever, so drain may diverge. Nothing in theorem 52.4 proves termination, eventual close, or temporal liveness.

Exercise 52.3

★★☆ Delete injectivity from H,ηtsΔ. Construct the two-alias close/read execution described above and identify the first false conclusion of lemma 52.2.

Suggested first pass.

Do exercise 52.4, exercise 52.6 before implementing the checker.

Exercise 52.4

★★☆ Add a suspended state and the transitions opensuspendsuspendedsuspendedresumeopen. Give both typing and dynamic rules and add their two cases to lemma 52.2.

Exercise 52.5

★☆☆ Prove by inversion that the following judgment has no derivation: f:File[eof]read f as{PmPe}Δ.

Exercise 52.6

★★☆ Encode the read result by a binary sum and write the eliminator that reproduces TS-Read’s common-output condition. State the additional affine premise. Then explain why the encoding introduces neither a peer endpoint nor a deadlock-freedom theorem.

Exercise 52.7

★★★ Practical project.typestate-trace-checker Implement the file automaton and trace checker in Kappa. Maintain agreement between each accepted command and the current state. Accept open–read-more–read-eof–close; reject read after eof, close twice, read after close, and a branch join whose final states differ. Mutate the read-eof case so that it leaves the state open: the mutant must typecheck and audit cleanly but fail the read-after-eof oracle. Use artifacts/ch52-typestate-protocol/corpus.kp; the accepted run must end with All 5 typestate corpus cases passed. The checker illustrates theorem 52.4; it does not prove the theorem.

Search the book

Type to search the local edition.