A HAT Trick: Automatically Verifying Representation Invariants using Symbolic Finite Automata

Functional programs typically interact with stateful libraries that hide state behind typed abstractions. One particularly important class of applications are data structure implementations that rely on such libraries to provide a level of efficiency and scalability that may be otherwise difficult to achieve. However, because the specifications of the methods provided by these libraries are necessarily general and rarely specialized to the needs of any specific client, any required application-level invariants must often be expressed in terms of additional constraints on the (often) opaque state maintained by the library. In this paper, we consider the specification and verification of such representation invariants using symbolic finite automata (SFA). We show that SFAs can be used to succinctly and precisely capture fine-grained temporal and data-dependent histories of interactions between functional clients and stateful libraries. To facilitate modular and compositional reasoning, we integrate SFAs into a refinement type system to qualify stateful computations resulting from such interactions. The particular instantiation we consider, Hoare Automata Types (HATs), allows us to both specify and automatically type-check the representation invariants of a datatype, even when its implementation depends on stateful library methods that operate over hidden state. We also develop a new bidirectional type checking algorithm that implements an efficient subtyping inclusion check over HATs, enabling their translation into a form amenable for SMT-based automated verification. We present extensive experimental results on an implementation of this algorithm that demonstrates the feasibility of type-checking complex and sophisticated HAT-specified OCaml data structure implementations layered on top of stateful library APIs.


INTRODUCTION
Functional programs often interact with stateful libraries that hide internal state behind their APIs.Such libraries are a vital component of a programming language's ecosystem, providing well-tested implementations of databases, key-value stores, logging and system services, and other stateful abstractions.A common use case for how these libraries are used is for programmers to build higher-level data structures on top of the abstractions they provide [Charguéraud et al. 2017].A set abstract data type (ADT) equipped with intersection and cardinality operations, for example, might use the aforementioned key-value library to maintain a persistent collection of its elements by associating those elements (represented as values) with unique ids (their associated keys).Since the internal state of the underlying stateful library is opaque to its clients, the implementations of these higher-level modules will necessarily be expressed in terms of the operations provided by the library: the methods of the set ADT might use methods like put and get to interact with the backing key-value store, for example.To maximize reusability, these low-level libraries are typically quite permissive in how client may use them, resulting in weak specifications that only describe simple algebraic or equational constraints.For example, the specification of the key-value store might state that two put operations over distinct keys commute or that a get operation on a key always returns the last value put in that key.
In contrast, the higher-level abstractions provided by clients built on top of these libraries are equipped with stronger guarantees: for example, the operations of our set ADT need to respect the semantics of mathematical sets, for example, so that ∀ : Verifying such semantically rich properties typically requires a strong guarantee that the internal state of the set library is consistent with the abstract states of the datatype, i.e., the ADT implementation should be equipped with a representation invariant [Malik et al. 2007;Miltner et al. 2020].Ensuring that distinct keys are always associated with distinct values allows us to prove the above relationship between the intersection and cardinality operations of the set ADT, for example.Establishing such an invariant is complicated by the fact that our set ADT is built on top of another library with an opaque internal state, and whose weak specifications do not directly address the notion of uniqueness central to the set ADT.This paper presents an automated type-based verification procedure that addresses these complications.
Our proposed solution augments a refinement type system for a call-by-value functional core language that supports calls to stateful libraries, with Hoare Automata Types (HATs).HATs are a new type abstraction that qualifies basic types with pre-and post-conditions expressed as symbolic finite-state automata [Veanes 2013;Veanes et al. 2010], a generalization of finite-state automata equipped with an unbounded alphabet, and whose transitions are augmented with predicates over the elements of this alphabet.HATs serve as a compact representation of the trace of stateful API calls and returns made prior to a function invocation and those performed during its execution.As a result, HATs serve as an expressive basis for a fine-grained effect system within a general refinement type framework, enabling compositional reasoning about invocations of stateful APIs in a form amenable to automated verification of a client module's representation invariant.For example, he representation invariant from our set ADT is captured by the following symbolic automaton, written using a variant of linear temporal logic on finite sequences [De Giacomo and Vardi 2013].The base predicates of this formula describe events, or invocations of the methods of a key-value store whose values are drawn from a setElem type: This representation invariant ( Set ) is expressed using standard temporal logic modalities (e.g., (always), (next), and ♦ (eventually)) to express the history of valid interactions between the set implementation and the underlying key-value store that ensure the expected element uniqueness property holds.Informally, the invariant establishes that, at every step of the execution ( (...)), once a value el has been inserted into the set ( put key val = | val = el ), it will never be reinserted ( ¬♦ put key val = | val = el ).An implementation that preserves this invariant guarantees that every value is always associated with at most one key.
To verify it, we can check the insert method of the set ADT against the following type The output type of insert ([ Set (el)] { :unit | ⊤} [ Set (el)]) is a Hoare Automata Type (HAT) over the representation invariant Set .This type captures the behavior required of any correct implementation of insert: assuming the invariant holds for any value el (encoded here as a ghost variable scoped using ) prior to the insertion of a new element x, it must also hold after the insertion as well.The precondition of the HAT captures the set of interactions that lead to a valid underlying representation state (namely, one in which every value in the store is unique), and the postcondition of the HAT specifies that this condition remains valid even after executing insert.
Our use of HATs differs in important ways from other type-based verification approaches for functional programs that allow computations to perform fine-grained effects, such as F* [Swamy et al. 2023] or Ynot [Nanevski et al. 2008b].These systems use the power of rich dependent types to specify fine-grained changes to the heap induced by an stateful operation.The Dijkstra monad [Maillard et al. 2019;Swamy et al. 2013] used by F*, for example, extends ideas first proposed by Nanevski et.al [Nanevski et al. 2008a] in their development of Hoare Type Theory to embed a weakest precondition predicate transformer semantics into a type system that can be used to generate verification conditions consistent with a provided post-condition on the program heap.In contrast, our setting assumes that such operations are executed by a library with a hidden state, requiring the client's representation invariant to be expressed solely in terms of observations on calls (and returns) to (and from) the methods of the underlying library.
Our approach also bears some similarity to recent (refinement) type and effect systems for verifying temporal properties of event traces produced during a computation [Nanjo et al. 2018;Sekiyama and Unno 2023].While the idea of using symbolic traces of previous observations as a handle to reason about the hidden state of a library is similar to these other efforts, incorporating return values of operations as part of such traces is a significant point of difference.This capability, critical for the validation of useful representation invariants, allows the precondition encoded in a HAT to describe a fine-grained context of events in which a program can run, expressed in terms of both library method arguments and return values.These important differences with prior work position HATs as a novel middle-ground in the design space between explicit state-based verification techniques and trace-based effect systems.Our formulation also leads to a number of significant technical differences with other refinement type systems [Jhala and Vazou 2021] and their associated typing algorithms, since we must now contend with type-level reasoning over symbolic automata qualifiers.
In summary, this paper makes the following contributions: (1) We formalize a new trace-based specification framework for expressing representation invariants of functional clients of stateful libraries that manage hidden state.(2) We show how this framework can be embedded within a compositional and fine-grained effect-tracking refinement type system, by encoding specifications as symbolic automata and instantiated as HATs in the type system.(3) We develop a bidirectional type-checking algorithm that translates the declarative type system into efficient subtype inclusion checks amenable to SMT-based automated verification.(4) Using a tool (Marple) that implements these ideas, we present a detailed evaluation study over a diverse set of non-trivial OCaml datatype implementations that interact with stateful libraries.To the best of our knowledge, Marple is the first system capable of automated verification of sophisticated representation invariants for realistic OCaml programs.
The remainder of this paper is organized as follows.In the next section, we provide further motivation for the problem by developing a running example, which is used in the rest of the paper.Sec. 3 introduces a core functional language equipped with operators that model stateful library methods.Sec. 4 presents a refinement type system that augments basic type qualifiers to also include  HATs; a brief background on symbolic automata is also given.An efficient bidirectional typing algorithm is then presented in Sec. 5. Implementation details, as well as experimental results on its effectiveness in automatically verifying the representation invariants of a number of realistic data structure client programs that interact with stateful libraries is provided in Sec. 6.The paper ends with a discussion of related work and conclusions.

MOTIVATION
To further motive our approach, consider the (highly-simplified) Unix-like file-system directory abstraction (FileSystem) shown in Fig. 1.This module is built on top of other libraries that maintain byte arrays (Bytes.t)which hold a file or a directory's contents and metadata, and which manipulate paths (Path.t),strings that represent fully-elaborated file and directory names.FileSystem's implementation also uses an underlying key-value store library (KVStore) to provide a persistent and scalable storage abstraction, where keys represent file paths and values are byte arrays.The interface to this store is defined by three stateful library methods: put persistently stores a key-value pair , , adding the pair to the store if does not already exist, and overwriting its old value with if it does; exists returns true if the given key has been previously put and false otherwise; and, get returns the value associated with its key argument, and fails (i.e., raises an exception) if the key does not exist in the store.
, Vol. 1, No. 1, Article .Publication date: September 2018.Fig. 1 shows three of the methods provided by FileSystem: init initializes the file system by storing a root path ("/"); add allows users to add regular files or directories to the filesystem; and, delete logically removes files or directories from the filesystem.The latter two operations return a Boolean value to indicate whether they were successful.The add operation succeeds (line 15) only if (a) the given path does not already exist in the file system (line 6) and (b) a path representing the parent directory for the file does exist (line 9).Besides adding the file (line 13), add also updates the contents of the parent directory to make it aware that a new child has been added (line 14).Similar to add, delete requires that the supplied path exists and is not the root (line 19).If the path corresponds to a directory, its children are marked deleted (line 22) using procedure deleteChildren (not shown).Additionally, the directory's metadata, and that of its parent, are updated to record the deletion (lines 25 and 26).Implicit in delete's implementation is the assumption that the parent path of the file to be deleted exists in the store: observe that there is no check to validate the existence of this path before the get of the parent's contents is performed on line 24.This assumption can be framed as a representation invariant that must hold prior to and after every call and return of add and delete.Intuitively, this invariant captures the following property: Invariant : Any path that is stored as a key in the key-value store (other than the root path) must also have its parent stored as a non-deleted directory in the store.
Representation Invariants.In a language that guarantees proper data abstraction, library developers can safely assume that any guarantees about an API established using a representation invariant are independent of a particular client's usage.However, as we noted earlier, numerous challenges arise when validating a representation invariant in our setting because we can only characterize the state of the underlying library in terms of its admissible sequence of interactions with the client.In our example, this characterization must be able to capture the structure of a valid filesystem, in terms of sequences of put, get, and exists calls and returns that are sufficient to ensure that the invariant is preserved.Verifying that Invariant holds requires us to prove that, except for the root, the path associated with a file or directory can only be recorded in the store when its parent path exists, i.e., its parent has previously been put and has not yet been deleted.Establishing that this property holds involves reasoning over both data dependencies among filesystem calls and temporal dependencies among store operations.
Events, Traces, and Symbolic Finite Automata.We address these challenges by encoding desired sets of stateful interactions between the client and library as symbolic finite automata (SFA).Besides constants and program variables, the alphabet of an automaton that captures these interactions includes a set of stateful operators op (e.g., put and get).The language recognized by an automaton specifies the sequences of permitted library method invocations.Each such invocation (called an event) op = records the operator invoked (op), a list of its arguments ( ), and a single result value .It is critical that we record both inputs and outputs of an operation, in order to enable our type system to distinguish among different hidden library states.Consider, for example, a stateful library initialization operator initialize : unit bool.The hidden state after observing "initialize () = true", representing a successful initialization action, is likely to be different from the hidden state that exists after observing the event "initialize () = false", which indicates an initialization error.The language accepted by an SFA is a potentially infinite set of finite sequences of events, or traces.
Example 2.1 (Traces).In contrast to the correct implementation of add shown in Fig. 1 This implementation simply records a path without considering whether its parent exists.We assume the file system initially contains only the root path, "/", which is put when the file system is created by the method init.The traces 1 and 2 , shown below, represent executions of these two implementations that reflect their differences: under context trace 0 [put "/" bytesDir = () ] add bad "/a/b.txt"bytesFile yields 1 [put "/" bytesDir = (); put "/a/b.txt"bytesFile = () ] add "/a/b.txt"bytesFile yields 2 [put "/" bytesDir = (); exists "/a/b.txt"= false; exists "/a" = false] where bytesDir and bytesFile represent the contents of a directory and a regular file, resp.The trace 1 violates Invariant since it registers the path "/a/b.txt" in the file system even though the parent path ("/a") does not exist.Thus, performing the operation delete "/a/b.txt"after executing the actions in this trace will cause a runtime error when it attempts to perform a get operation on the parent path "/a".On the other hand, executing this operation after executing the actions in trace 2 terminates normally and preserves Invariant .
Example 2.2 (Symbolic Finite Automata).We express symbolic automata as formulae in a symbolic version of linear temporal logic on finite sequences [De Giacomo and Vardi 2013].Invariant can be precisely expressed in this language as the following formula (denoted FS (p)) parameterized over a path p: where Here, isRoot, isDir, isFile, and parent are pure functions on paths and bytes.The invariant is comprised of two disjunctions involving p: (1) isRoot(p) asserts that p is the filesystem root and is always a valid path, regardless of any other actions that have been performed on the underlying key-value store.
(2) isFile (p) ∨ isDir (p) =⇒ isDir (parent(p)) asserts that if p corresponds to either a file or directory in the file system, then its parent path must also correspond to a directory in the file system.The predicate isDir (p) captures contexts in which p has been registered as a directory and then not subsequently deleted or replaced by a file (i.e., ).The predicate ( isFile (p)) is defined similarly except that it requires that an existing file not be deleted or modified to become a directory.Observe that atomic predicates (e.g., put key val = | key = p ∧ isFile(val) ) not only capture when an event has been induced by a library operator (e.g., put), but also qualify the arguments to that operator and the result it produced (e.g., the argument key must be equal to the path p). 1 This specification of the representation invariant FS (p) thus formalizes our informal characterization of a correct file system, Invariant .The two subformulas capture the property that any path recorded as a key in the key-value store, other than the root, must have a parent directory.Notably, this specification is defined purely by constraining the sequence of put operations allowed on the store and does not require any knowledge of the store's underlying representation.
Hoare Automata Types.While SFAs provide a convenient language in which to encode representation invariants, it is not immediately apparent how they can help with verification since, by themselves, they do not impose any safety constraints on expressions.To connect SFAs to computations, we embed them as refinements in a refinement type (and effect) system.In a standard refinement type system, the refinement type { : | } of an expression uses a qualifier to refine 1 Throughout this paper, we italicize event arguments.

Variables
, , , , , ... the value of .Similarly, a Hoare Automata Type (HAT), [ ] { : | } [ ], additionally refines a pure refinement type { : | } with two symbolic automata: a precondition automaton defining an effect context under which is allowed to evaluate, and a postcondition automata that augments this context with the effects induced by as it executes.A HAT thus allows us to track the sequence of stateful interactions between higher-level datatypes and the underlying stateful library, as well as to record the consequences of this interaction in terms of the return values of stateful calls.

Pure Operations
Our type system leverages this information to verify the preservation of stated representation invariants in terms of these interactions.For example, add and delete can be ascribed the following HAT-enriched types: (i.e., 2 = 0 new ).Note that both 0 and 2 satisfy the representation invariant FS .To admit this trace, our type system automatically infers a new automata that accepts the sequence of events in new by type-checking add.Verifying that the representation invariant holds now reduces to an inclusion check between two symbolic automata, requiring that the trace expected by the concatenated automata FS ; is also accepted by the representation invariant FS .

LANGUAGE
To formalize our approach, we first introduce E , a core calculus for effectful programs.
Fig. 3. Trace syntax and selected operational semantics.
Syntax.E is a call-by-value lambda calculus with built-in inductive datatypes, pattern matching, a set of pure and effectful operators, and recursive functions.Its syntax is given in Fig. 2. For simplicity, programs are expressed in monadic normal-form (MNF) [Hatcliff and Danvy 1994], a variant of A-Normal Form (ANF) [Flanagan et al. 1993] that permits nested let-bindings.The terms in E are syntactically divided into two classes: values and expressions , where the latter include both pure terms and those that have computational effects.Similar to the simply-typed lambda calculus, function parameters have explicit type annotations.E is parameterized over two sets of primitive operators: pure operators ( ) include basic arithmetic and logical operators, while examples of effectful operators (op) include state manipulating operators (e.g., put/exists/get for interacting with a key-value store, or insert/mem for manipulating a stateful set library).We express sequencing of effectful computations 1 ; 2 in terms of let-bindings: 1 ; 2 let = 1 in 2 , where does not occur free in 2 .The application 1 2 is syntactic sugar for let = 1 in let = 2 in let = in .
Operational Semantics.Our programming model does not have access to the underlying implementation of effectful operators, so the semantics of E constrains the behavior of impure operations in terms of the outputs they may produce.To do so, we structure its semantics in terms of traces that record the history of previous effectful operations and their results.The syntax of traces adopts standard list operations (i.e., cons :: and concatenation ), as shown in Fig. 3. Traces are lists of effect events op = that record the application of an effectful operator op to its arguments as well as the resulting value .The traces 2 and 1 from Example 2.1 record the events generated by correct and incorrect implementations of the add function of the FileSystem ADT, for example.
The operational semantics of E is defined via a small-step reduction relation, ′ ↩− − → ′ .This relation is read as "under an effect context (i.e., trace) , the term reduces to ′ in one step and performs the effect ′ ", where ′ is either empty [ ] or holds a single event.The semantics is parameterized over two auxiliary relations, ⇓ and ⇓ , used to evaluate pure and impure operations, respectively.Fig. 3 shows how the corresponding reduction rules use these relations. 3he reduction rule for pure operators (STP O ) holds in an arbitrary effect context (including the empty one), and produces no additional events.The rule for effectful operators (STE O ), in contrast, can depend on the current context , and records the result value of the effectful operator in the event it emits.The multi-step reduction relation ′ ↩− → * ′ defines the reflexive, transitive closure of the single-step relation; its trace records all events generated during evaluation.
Example 3.1.We can define the semantics for the put, exists, and get operators found in a keyvalue store library and used in FileSystem, via the following rules:

Basic Types
The rule P asserts that put operations always succeed.For a particular key, the E F and E T rules stipulate that exists returns false or true, based on the presence of a corresponding put event in the effect context.Finally, the rule G specifies that the result of a get event is the last value written by a put event to that key.Note that a program will get stuck if it attempts to get a key in a trace without an appropriate put event, since there is no rule covering this situation.
All the traces appearing in Sec. 2 can be derived from these rules.Under the effect context 0 , for example, the expression, add "/a/b.txt"bytesFile, induces the trace 2 as follows: the add function uses the exists operator on the key "/a/b.txt"(line 6) to check if this path is in the file system.Since the context trace 0 lacks a put event with the key "/a/b.txt",we have The trace used to evaluate subsequent expressions records this event: 0 [exists "/a/b.txt"= false].
The remaining evaluation of add "/a/b.txt"bytesFile is similar, yielding the following execution:

TYPE SYSTEM
The syntax of E 's types is shown in Fig. 4. Our type system uses pure refinement types to describe pure computations and Hoare Automata Types (HATs) to describe effectful computations.Our pure refinement types are similar to those of other refinement type systems [Jhala and Vazou 2021], and allow base types (e.g., int) to be further constrained by a logical formula or qualifier.
Qualifiers in E are universally quantified formulae over linear arithmetic operations ( ) as well as uninterpreted functions, or method predicates (mp), e.g., isRoot.Verification conditions generated by our type-checking algorithm can nonetheless be encoded as effectively propositional (EPR) sentences [Ramsey 1987], which can be efficiently handled by an off-the-shelf theorem prover such as Z3 [de Moura and Bjørner 2008].We also allow function types to be prefixed with a set of ghost variables with base types ( : ). Ghost variables are purely logical -they can only appear in other qualifiers and are implicitly instantiated when the function is applied.
Unique to E are the HATs ascribed to a stateful computation, which constrain the traces it may produce.HATs use Symbolic Finite Automata (SFAs) [D' Antoni and Veanes 2014;D'Antoni and Veanes 2017;Veanes 2013]  qualify the types of pure terms.We adopt the symbolic version of linear temporal logic on finite traces [De Giacomo and Vardi 2013] as the syntax of SFAs. 4s shown in Fig. 4, HATs support two kinds of atomic propositions, or symbolic effect events: and .A symbolic effect event op x = | describes an application of an effectful operator op to a set of argument variables that produces a result variable .The formula constrains the possible instantiation of and in a concrete event.The other symbolic effect event, , is used to constrain the relationship between pure values (e.g., ghost variables and function arguments).In addition to the temporal next ( ) and until operators ( U ), the syntax of HATs includes negation (¬ ), intersection ( ∧ ), union ( ∨ ), and concatenation ( ; ).Fig. 4 defines notations for other useful logical and temporal operators: implication ( =⇒ ¬ ∨ ), the eventually operator ♦, the globally operator , and importantly, the last modality LAST, which describes a singleton trace, thus prohibiting a trace from including any other effects [De Giacomo and Vardi 2013].Fig. 4 also provides notations for specifying the value of an argument op x ∼ x = | .As is standard, we abbreviate the qualified type { : | ⊤} as .
Example 4.1.This syntax can capture rich properties over effect contexts.For example, the following formulae concisely specify traces under which a key holds a certain value ( stored ) and when a particular key exists in the current store ( exists ):

HATs, By Example
Formally, a Hoare Automata Type [ ] [ ] qualifies a refinement type with two SFAs: a precondition SFA that captures the context traces in which a term can be executed and a postcondition SFA that describes the effect trace after the execution of the term.Our type system also includes intersection types on HATs ( ⊓ ), in order to precisely specify the behaviors of a program under different effect contexts.
Example 4.2 (Built-in Effectful Operators).Our type system is parameterized over a (global) typing context Δ containing the types of built-in operators.Intuitively, the signatures of effectful operators in Δ captures the semantics the library developer ascribes to their API.Using the SFAs from Example 4.1, for example, the signatures of put, exists, and get are: The postcondition SFAs of all three operators use LAST, to indicate that they append a single effect event to the end of the effect context ( op v = | ∧ LAST).The type of put permits it to be used under any context ( ⊤ ).More interestingly, the built-in type of get uses a ghost variable (a) to represent the current value of the key k; its precondition stipulates that it should only be applied to keys that have been previously stored.Finally, the intersection type of exists describes two possible behaviors, depending on whether the key k has been previously added to the store.Note that the pre-and post-condition SFAs in these signatures express different constraints on the allowed contexts in which the operator may execute, and the effects the operator performs.
Example 4.3 (MinSet).Consider a developer who wants to implement an API for a set augmented with an additional operation that returns the minimum element in the set.This implementation is defined in terms of two other stateful libraries: a Set ADT that provides effectful operators insert:int unit and mem:int bool, and a MemCell library that provides a persistent cell with effectful read:unit int and write:int unit operators.One implementation strategy is to track the minimum element in a persistent cell, and maintain the representation invariant that the contents of this cell are always less than or equal to every element in the underlying set.Using SFAs, we can express this invariant as: which specifies that an element el is written into the persistent cell only when el has been inserted into the backing Set and no element less than el has also been inserted.The HAT ascribed to the function minset_insert enforces this invariant Example 4.4 (LazySet).As our next example, consider a LazySet ADT that provides a lazy version of an insert operation, delaying when elements are added to an underlying Set ADT.The ADT does so by returning a thunk closed over a collection of elements that will be added to the set when it is forced.This ADT maintains the representation invariant that an element is never inserted twice: We can specify a "lazy" insert operator using the following HAT: This operation takes as input a (potentially new) element elem to be added to the set and a thunk holding any elements that have not yet been inserted into the backing set, and returns another thunk.This HAT stipulates that both input thunk and output thunk preserve the representation invariant of the ADT: Example 4.5 (DFA).Our final example is a library built on top of a stateful graph library that is used to maintain the states and transitions of a Deterministic Finite Automaton (DFA).The underlying graph library exports two methods to add (connect) and remove (disconnect) edges.We can specify the standard invariant that DFAs only have deterministic transitions: DFA stipulates that a node n can have at most one transition on a character c ( connect ∼n ∼c n end = | ⊤ ).Moreover, adding a new transition from n on c requires that any previous transitions on c have first been removed ( ).An add_transition method with the following signature is thus guaranteed to preserve determinism:

Typing Rules
Type contexts.A type context (shown in Fig. 4) is a sequence of bindings from variables to pure refinement types (i.e., ).Type contexts are not allowed to contain HATs, as doing so breaks several structural properties (e.g., weakening) that are used to prove type safety. 5ype Erasure Auxiliary typing relations.Fig. 5 shows selected rules from three sets of auxiliary relations used by our type system.The first set describes the type erasure functions ⌊ ⌋, ⌊ ⌋, and ⌊Γ⌋.The former two functions return basic types by erasing all qualifiers and automata from types, while the latter ⌊Γ⌋ is the type context derived by applying ⌊...⌋ to all of Γ's bindings.The second set describes well-formedness conditions on SFAs (Γ ⊢ WF ) and types (Γ ⊢ WF and Γ ⊢ WF ).These rules largely ensure that all the qualifiers appearing in a type are closed under the current typing context Γ.The exceptions are WFI , which stipulates that only HATs with the same basic type can be intersected, and WFH , which requires the language accepted by the precondition SFA to be a prefix of the postcondition automata (i.e., L( ) ⊆ L( ; ⊤ ), where ⊤ denotes the SFA that accepts an arbitrary trace) after performing a substitution consistent with the current typing context ( ∈ Γ ); the functions L(...) and ... are defined in the next subsection.
Our type system also uses a mostly-standard subtyping relation that aligns with the denotation of the types being related.Fig. 5 highlights key subtyping rules that do not have analogues in a standard refinement type system.The subtyping rule for symbolic automata (S A ) checks inclusion between the languages of the automata, after performing substitutions consistent with the current typing context.The subtyping rule for (non-intersected) HATs (S H ) checks that the inclusion is contravariant over the precondition automata and covariant over the postcondition automata under the same context (i.e., the conjunction of 1 and 2 with 2 ; ⊤ ).The subtyping rules for the intersection of HATs -S I LL, S I LR, and S I R -are standard.The S I M rule additionally allows the precondition automata of intersected types to be merged.Finally, the subtyping rules for ghost variables either bind a ghost variable in the type context (S G R), or instantiate it to some concrete value (S G L).
, Vol. 1, No. 1, Article .Publication date: September 2018.A subset of our typing rules6 is shown in Fig. 6.Note that an stateful computations can only be ascribed a HAT.As mentioned in Example 4.2, our type system is parameterized over Δ, a typing context for built-in operators that provides HATs for both pure (TPO ) and effectful operators (TEO ).This system features the standard subsumption and intersection rules (TS and TI ), which enables our type system to use fine-grained information about the effect context when typing different control flow paths.The rule TEP allows a pure term to be treated as a computation that does not perform any effects.
Example 4.6.The add function in Fig. 1 has four possible control flow paths, depending on the effect context under which it is called: (1) the input path exists in the file system (line 6); (2) neither the input path or its parent path exist (line 9); (3) its parent path exists and is a directory (line 13−15), or (4) it is not (line 16).The following four automata (recall that the SFA isDir was defined in Sec. 2) indicate the effect context corresponding to each of these scenarios: The union of these automata is exactly the representation invariant ( FS (p)), established from the following subtyping relation between their intersection and the return type of add : Using the TI and T rules, our type system is able to reduce checking add against add into checking add against each (i.e., [ ] bool [ FS (p)]).Note that add only adds the given path to the file system in the third case (line 13), whose precondition automata ( 2 ) indicates that the input path does not exist in the file system (¬ exists (path)), although its parent path does ( exists (parent(path))), and is a directory ( isDir (parent(path))).When coupled with the representation invariant that the parent of a path is always a directory in the file system, our type system is able to ensure that it is safe to perform the put operation.

Trace Language
, |= L( ) ∈ P ( ) Type Denotation ∈ P ( ) ∈ P ( ) Effectful operator application.TPO A is the standard rule for operator application in a typical refinement type system [Jhala and Vazou 2021].On the other hand, rule TEO A , the rule for effect operator application, with the help of the subsumption rule (TS ), allows operators to have function types whose ghost variables are instantiated properly; moreover, the return type of effect operators is a non-intersected HAT.After ensuring each argument types against the corresponding argument type and substituting all parameters ( ) with the supplied arguments ( ), the return type of an effectful operator must be in the form [ ] [ ′ ] and have exactly the same precondition SFA ( ) as the type of the surrounding term.Typing the let-body is similar to TPO A , where a new binding : is added to the type context.Moreover, the rule replaces the precondition SFA used to type the body of the let with the postcondition SFA from the return type of the effect operator ′ , so that the body is typed in a context reflecting the use of the effectful operator.

Type Soundness
The denotation of a SFA L( ), shown in Fig. 7, is the set of traces accepted or recognized by the automata; the denotation of refinement types and HATs are sets of terms; these functions are defined mutually recursively.
The language of traces ranges over the set of all well-formed traces Tr WF , i.e., those only containing events (op = ) that are well-typed according to the basic type system.As is standard [De Giacomo and Vard 2013], our denotation uses an auxiliary judgement , |= that defines when the suffix of a trace starting at the ℎ position is accepted by an automaton.The denotation of an SFA is the set of (well-formed) traces recognized by , starting at index 0.
Type denotations.Out type denotations use a basic typing judgement ∅ ⊢ s : that types an expression according to the standard typing rules of the simply-typed lambda-calculus (STLC), erasing all qualifiers in function argument types.The denotation of pure refinement types is standard [Jhala and Vazou 2021] parameters are restricted to base types.The denotation of an intersection type 1 ⊓ 2 is the intersection of the denotations 1 and 2 .An expression belongs to the denotation of a HAT [ ] [ ] iff every trace and value produced by is consistent with the SFA and refinement type , under any effect context accepted by the SFA .Intuitively, the denotation of a HAT is naturally derived from the language's operational semantics, as depicted by the following correspondence: Pure Language: In a pure language with the simple multi-step reduction relation ↩→ * , refinement types qualify the value that produces.In contrast, the multi-step reduction relation of E , ′ ↩− → * depends on the effect context and emits a trace ′ that records the sequence of effects performed by during evaluation.Thus, HATs use precondition and postcondition automata (i.e., and ) to qualify the trace before and after a term is evaluated (i.e., and ′ ).
Type context denotation.The denotation of a type context is a set of closing substitutions , i.e., a sequence of bindings [ ↦ → ] consistent with the type denotations of the corresponding variables in the type context.The denotation of the empty context is a singleton set containing only the identity substitution ∅.
Definition 4.7 (Well-formed built-in operator typing context).The built-in operator typing context Δ is well-formed iff the semantics of every built-in operator is consistent with its type: T 4.8.[Fundamental Theorem] Given a well-formed typing context for built-in operators Δ, the trace of effects produced by a well-typed term are captured by its corresponding HAT : 4.9.[Type Soundness] Under a built-in operator type context that is well-formed, if a function preserves the representation invariant , i.e. it has the type: ∅ ⊢ f : : then for every set of well-typed terms ∅ ⊢ x : x and ∅ ⊢ y : y , applying to y under an effect context that is consistent with results in a context ′ that is also consistent with : 5 TYPING ALGORITHM Converting our declarative type system into an efficient algorithm requires resolving two key issues.First, we cannot directly use existing SFA inclusion algorithms [D' Antoni and Veanes 2014;D'Antoni and Veanes 2017] to implement the check in S A , because our SFAs may involve non-local variables corresponding to function parameters and ghost variables; these variables can have refinement types.We deal with this wrinkle by integrating the subtyping algorithm for pure refinement types into the existing SFA algorithm.Second, typing the use of an effectful operator, e.g., put or exists, depends on the set of effects in the precondition of the current HAT.The declarative type system can ensure that this automaton has the right shape by applying the TI and TS rules at any point in a derivation, but an efficient implementation must perform this conversion more intelligently.Our solution to this problem is to employ a bidirectional type system [Dunfield and Krishnaswami 2021] that closely tracks a context of effects that have preceded the term being typed, and selectively applies the subsumption rule to simplify the context when switching between checking and synthesis modes.

SFA Inclusion
Symbolic alphabets allow the character domains of SFAs to be infinite, making them strictly more expressive than standard FAs.This enhanced expressivity comes at a cost, however, as the standard FA inclusion algorithm cannot be directly applied to SFAs.Thankfully, prior work has shown how to reduce an inclusion check over SFAs to an inclusion check over FAs [D' Antoni and Veanes 2014;D'Antoni and Veanes 2017].The high-level approach is to first construct a finite set of equivalence classes over an SFA's infinite character domain, defined in terms of a set of maximal satisfiable Boolean combinations of logic literals (called minterms) in the automaton's symbolic alphabet.An alphabet transformation procedure then replaces characters in the original SFA with their corresponding equivalence classes, and replaces the SFA's symbolic alphabet with minterms, thus allowing SFAs to be translated into FAs.As long as the satisfiability of minterms is decidable, so is checking inclusion between two SFAs.Our use of EPR formulas for qualifiers guarantees that minterm satisfiability is decidable.
The pre-and post-conditions used in HATs have two features that distinguish them from standard SFAs, however: 1) characters are drawn from a set of distinct events and their qualifiers may range over multiple variables, instead of a single local variable; and, 2) event qualifiers may refer to variables in the typing context, i.e., function parameters and ghost variables.Our language inclusion algorithm, shown in Algorithm 1, accounts for all these differences by extending the existing SFA inclusion algorithm to incorporate a subtyping check between pure refinement types.
Candidate Minterms.The first step in checking SFA inclusion is to construct the equivalence classes used to finitize its character set.With HATs, these characters range over events triggered by the use of library operations op = , and return events ret .As each class of events is already disjoint (e.g., get and put events will never overlap), we only need to build minterms that partition individual classes.The minterms of each class of events have the form op x i = | ( ) ∧ ( ¬ ) .To build the literals used in these minterms for an event op, we collect all the literals used to qualify op, as well as any literals appearing in atomic predicates , and then construct a maximal set of satisfiable Boolean combinations of those literals.Notably, these literals may contain variables bound in the current typing context.Our algorithm divides minterms into two categories: Boolean combinations of literals from the typing context (line 3), Γ , and Boolean combinations of literals , Vol. 1, No. 1, Article .Publication date: September 2018.Satisfiability Check.The soundness of the alphabet transfer procedure requires that only satisfiable minterms are used, so that every transition in the output FA corresponds to an actual transition in the original SFA.To understand how this is done, observe that the typing context plays a similar role in checking both the satisfiability of minterms and the subtyping relation.Thus, the algorithm reduces checking the satisfiability of op x i = | ( ) ∧ ( ¬ ) to checking whether the refinement type

Type Synthesis
Inclusion Check.Equipped with the set of satisfiable minterms, the final inclusion check (line 10) is a straightforward application of the textbook inclusion check between the FA produced by the standard alphabet transformation algorithm (line 10).9SFAInclusion returns true only when automata is included by under all instantiations of the variables in Γ.

Bidirectional Type System
As is standard, our bidirectional type system consists of both type synthesis ( ⇒ ) and type checking ( ⇐ ) judgments.The bidirectional system features a minor divergence from the declarative rules.While the declarative system was able to use the subsumption rule (TS ) to freely instantiate ghost variables, a typing algorithm requires a more deterministic strategy.Our solution is to treat ghost variables : as instead being qualified by an unknown formula which is specialized as needed, based on the information in the current typing context.In order to efficiently infer the qualifiers of ghost variables, our algorithm only allows ghost variables to appear in SFAs.This restriction ensures that ghost variables are only used during SFA inclusion checks, and allows it to avoid the more sophisticated algorithms needed when ghost variables can appear in arbitrary refinement types [Tondwalkar et al. 2021].This restriction is enforced by our algorithmic typing rules: the second line of C EO A (∀ .Γ ⊢ ⇐ ) for example, only holds when no free ghost variables appear in the parameter types used by ≈ › (Γ ⊢[ ] : : With this minor tweak in hand, the top-level typing algorithm is mostly a matter of bidirectionalizing the typing rules of E by choosing appropriate modes for the assumptions of each rule.This is largely straightforward: the type checking rule for ghost variables (C G ) adapts the Fig. 9. Selected Auxiliary Typing Functions corresponding declarative rule (TG ) in the standard way, for example.The rule for checking effectful operations (C EO A ) is similar, although it adopts a fixed strategy for applying the subsumption rule: 1) instead of using TS to instantiate ghost variables directly, it now relies on an auxiliary function, ≈ ›, to strengthen their qualifiers based on the current context; 2) when the result of the library operation op is an intersection type ( [ ] [ ′ ]), C EO A considers each case of the intersection, instead of using TS to focus on a single case; and 3) instead of relying on TS to align the postcondition automata ( ′ ) of op with the precondition of the HAT being checked against ( ), the rule uses the conjunction of the automata (( ; ⊤ ) ∧ ′ ) as the precondition automata used to check the term that uses the result of op.The implementation of ≈ › relies on the Abduce subroutine. 10Given a typing context Γ, set of ghost variables : , and a pair of automata and ′ , Abduce infers a set of qualifiers for : sufficient enough to ensure that is included in ′ , i.e.Γ, :{ :b | } ⊢ ⊆ ′ , or reports that none exists.Abduce is an adaption of an existing algorithm [Zhou et al. 2021] which infers the weakest qualifiers needed for the inclusion check.

IMPLEMENTATION AND EVALUATION
We have implemented a tool based on the above approach, called Marple, that targets ADTs implemented in terms of other stateful libraries.Marple consists of approximately 12K lines of OCaml and uses Z3 [de Moura and Bjørner 2008] as its backend solver for both SMT and FA inclusion queries. 12 Marple takes as an input the implementation of an ADT in OCaml, enhanced signatures of ADT operations that include representation invariants expressed as HATs, and specifications of the supporting libraries as HATs (e.g., the signatures in Example 4.2).The typing context used by Marple includes signatures for a number of (pure) OCaml primitives, including the pure operators listed in Fig. 2. Marple also includes a set of predefined method predicates (i.e., in Fig. 4) that allow qualifiers to capture non-trivial datatype properties.For example, the method predicates isDir(x) and isDel(y) from the motivating example in Sec. 2 encode that x holds the contents of a directory, and that y is marked as deleted, respectively.The semantics of method predicates are defined via a set of lemmas in FOL, in order to enable automated verification; e.g., the axiom ∀x.isDir(x) =⇒ ¬isDel(x) encodes that a set of bytes cannot simultaneously be an active directory and marked as deleted.
Our experimental evaluation addresses three research questions: Table 1.Experimental results of using Marple to verify representation invariants of ADTs.Benchmarks from prior works are annotated with their source: Okasaki [Okasaki 1999]( † ), Hanoi [Miltner et al. 2020]( ★ ).We have rewri en functional implementations of these benchmarks into stateful versions by assuming there is a hidden mutable datatype instance that ADT methods use to perform their operations.The backing stateful libraries are drawn from a verified OCaml library [Charguéraud et al. 2017].Q1: Is Marple effective?Can it verify a diverse range of ADTs that are implemented using a variety of backing stateful libraries?Q2: Is Marple expressive?Are HATs able to capture a rich set of representation invariants?Q3: Is Marple efficient?Is it able verify clients of stateful APIs in a reasonable amount of time?

ADT
We have evaluated Marple on a corpus of complex and realistic ADTs drawn from a variety of sources (shown in the title of Table 1), including general datatypes such as stacks, queues, sets, heaps, graphs, as well as custom data structures including the Unix-style file system from Sec. 1 and the deterministic finite automata described in Sec. 4 (Q1).Table 1 presents the results of this evaluation.Each datatype includes multiple implementations using different underlying libraries (Library column), each of which provides a distinct set of operations.Our backing APIs include linked lists, a persistent key-value store, sets, trees, and graphs, each of which induces a different encoding of the target ADT's representation invariant.
Table 1 divides the results of our experiments into two categories, separated by the double bars; the first gives comprehensive results about analyzing the entire datatype, while the second reports information about the most complex method of each ADT implementation. 13The first group of columns describes broad characteristics of each ADT, including its number of methods (#Method), the number of ghost variables in its representation invariant (#Ghost), and the size of the formula encoding the representation invariant (s ), after it is desugared into a symbolic regular expression.The next column reports the total time needed to verify all the method of each ADT (t total ).Marple performs quite reasonably on all of our benchmarks, taking between 1.42 to 176.89 seconds for each ADT.As expected, more complicated ADT implementations (i.e., those with larger values in the #Method, #Ghost, and s columns), take longer to verify (Q3).
Details about the representation invariants used in our benchmarks are shown in Table 2 (Q2).The stack and queue ADTs require a low-level guarantee that the underlying linked list implementation is not circular.The set ADTs maintain the invariant that there are no duplicate elements.The connected graph benchmark requires that every pair of nodes is connected, necessitating the need for ghost variables.As shown in Table 1, all of these properties, as well as those discussed in previous sections, can be expressed using LTL using no more than 2 ghost variables (#Ghost) and 28 literals (s ).
The first group of columns in the second half of Table 1 list features relevant to the complexity of the most demanding method in the ADT.These methods feature between 3 and 5 branches (#Branch) and 3 to 12 uses of built-in operators (#App).The last two groups of columns present type checking results for these methods.The #SAT and #FA ⊆ columns list the number of SMT queries and finite automata (FA) inclusion checks needed to type check the method.The next column (avg.s FA ) gives the average number of transitions in the finite state automata (FA) after the alphabet transformation described in Sec. 5.These numbers are roughly proportional to code complexity (column #Branch and #App) and invariant complexity (column #Ghost and s ) -intuitively, programs that have more uses of operators and larger specifications, require more queries and produce more complicated FAs.The last two columns report verification time for the method (Q3).These times are typically dominated by the SFA inclusion checks (ranging from .63 to 73.09 seconds) that result from minterm satisfiability checks during the alphabet transformation phase (t SAT ) and FA inclusion checks (t FA ⊆ ).Unsurprisingly, generating more queries (both #SMT and #FA ⊆ ) result in more time spent checking minterm satisfiability and FA inclusion.Our results also indicate that FA inclusion checks are not particularly sensitive to the size of the FA involved; rather, the cost of these checks corresponds more closely to the deep semantics of the representation invariant, in which the choice of the underlying library is an important factor.Taking the FileSystem benchmark as an example, both Tree and KVStore implementations lead to similar sizes for the invariant, in term of #Ghost.s , and FA (avg.sFA ).However, the use of the former library results in a significantly shorter verification time since the relevant property captured by the invariant "every child has its parent in the file system" is naturally supported by the Tree library; the only remaining verification task in this case for the client to handle involves ensuring these parents are indeed directories in the file system.In contrast, the KVStore provides no such guarantees on how its elements are related, requiring substantially more verification effort to ensure the invariant is preserved.

RELATED WORK
Representation Invariant Inference.Miltner et al. [2020] develop a data-driven CEGIS-based inference approach, while Malik et al. [2007] develop a solution derived from examining testcases.Solvers used to infer predicates that satisfy a set of Constrained Horn Clauses (CHCs) [Ezudheen et al. 2018;Hojjat and Rümmer 2018;Zhu et al. 2018] can also be thought of as broadly addressing similar goals.Our focus in this paper is orthogonal to these efforts, however, as it is centered on the automated verification of user-specified representation invariants expressed in terms of SFAs.
Effect Verification.There has been significant prior work that considers the type-based verification of functional programs extended with support for (structured) effectful computation.Ynot [Nanevski et al. 2008b] and F* [Swamy et al. 2023] are two well-known examples whose type specifications allow fine-grained effect tracking of stateful functional programs.These systems allow writing specifications that leverage type abstractions like the Hoare monad [Nanevski et al. 2008a] or the Dijkstra monad [Ahman et al. 2017;Fromherz et al. 2021;Maillard et al. 2019;Swamy et al. 2020Swamy et al. , 2013]].For example, specifications using the Hoare monad involve writing type-level assertions in the form of pre-and post-conditions over the heaps that exist before and after a stateful computation executes.The Dijkstra monad generalizes this idea by allowing specifications to be written in the style of a (weakest precondition) predicate transformer semantics, thus making it more amenable for improved automation and VC generation.While our goals are broadly similar to these other efforts, our setup (and consequently, our approach) is fundamentally different.Because we assume that the implementation of effectful operations are hidden behind an opaque interface, and that the specifications of these operations are not tailored for any specific client application, our verification methodology must necessarily reason about stateful library actions, indirectly in terms of the history of calls (and returns) to library methods made by the client, rather than in terms of predicates over the concrete representation of the state.One important way that our significantly different representation choice impacts our verification methodology is that unlike e.g., the Dijkstra monad that uses a predicate transformer semantics to establish a relation between pre-and post-states of an effectful action, our typing of interaction history in terms of HATs allows us to simply reuse a standard subtying relation (suitably adapted), instead.Consequently, the implementation of our typing algorithm is amenable to a significant degree of automation, on par with other refinement type systems like Liquid Haskell [Vazou et al. 2014].
Monadic-based techniques [Chappe et al. 2023;Li and Weirich 2022;Wiegley and Delaware 2017;Xia et al. 2019;Zhang et al. 2021] to reason about effectful programs have also been well-studied, especially in the context of interactive (mechanized) verification.One notable instance are interaction trees (ITrees) [Xia et al. 2019], a coinductive variant of the free monad that also provide an abstraction of an (infinite) sequence of visible events (i.e., traces) produced by effectful programs.Analogous to HATs, context and effect traces can be expressed as pre-and post-ITrees [Zhang et al. 2021].In contrast to ITrees, HATs use of SFAs provides a decidable inclusion check, which enables fully automated verification.Furthermore, because SFAs denote a regular trace language, they enable the use of LTL , an arguably more readable and lighter-weight specification language.
Type and Effect Systems.Type and effect systems comprise a broad range of techniques that provide state guarantees about both what values a program computes and how it computes them.These systems have been developed for a number of different applications: static exception checking in Java [Java 2013], ensuring effects are safely handled in the languages with algebraic effects [Bauer and Pretnar 2013], and reasoning about memory accesses [Gifford and Lucassen 1986], in order to ensure, e.g., programs can be safely parallelized [Abadi et al. 2011].Notably, a majority of these systems are agnostic to the order in which these effects are produced: the effect system in Java, for example, only tracks whether or not a computation may raise an exception at run-time.
In contrast, sequential effect systems [Tate 2013] provide finer grained information about the order in which a computation's effects may occur.More closely related to this work are type and effect systems that target temporal properties on the sequences of effects a program may produce.For example, Skalka and Smith [2004] present a type and effect system for reasoning about the shape of histories (i.e., finite traces) of events embedded in a program, with a focus on security-related properties expressed as regular expressions augmented with fixed points.Their specification language is not rich enough to capture data-dependent properties: the histories of a conditional expression must include the histories of both branches, even when its condition is a variable that is provably false.Koskinen and Terauchi [2014] present a type and effect system that additionally supports verification properties of infinite traces, specified as Büchi automata.Their system features refinement types and targets a language similar to E .Their effect specifications also include a second component describing the sets of infinite traces a (non-terminating) program may produce, enabling it to reason about liveness properties.Unlike E , however, events in their system are not allowed to refer to program variables.This restriction was later lifted by Nanjo et al. [2018], whose results support value-dependent predicates in effect specifications written in a first-order fixpoint logic.To solve the resulting verification conditions, they introduce a novel proof system for this logic.More recently, Sekiyama and Unno [2023] consider how to support richer control flow structures, e.g., delimited continuations, in such an effect system.To the best of our knowledge, all of these effect systems lack an analogue to the precondition automaton of HATs, and instead have to rely on the arguments of a dependently typed function to constrain its set of output traces, as opposed to constructing and managing the context of previously seen events and their results.
While our focus has been on the development of a practical type system for the purposes of verification, there have also been several works that provide foundational characterizations of sequential effect systems.Most of these works adopt a semantics-oriented approach and seek to develop categorical semantics for effectful languages [Katsumata 2014;Mycroft et al. 2015;Tate 2013].More closely related is the recent work of Gordon [Gordon 2021], which defines a generic model of sequential effects systems in terms of a parameterized system.The key idea is to represent effects as a quantale, a join semi-lattice equipped with a top element and whose underlying set is also a monoid.The proposed type system is parameterized over an arbitrary effect quantale; this system is proven to be safe for any appropriate instantiation of this parameter.
Type-Based Protocol Enforcement.Because HATs express a history of the interaction between a client and an stateful library, they naturally serve as a form of protocol specification on client behavior (e.g., session types [Honda 1993;Vasconcelos 2009]), although the kinds of protocols we consider in this work are limited to those that are relevant to defining a representation invariant, i.e., those that capture constraints on library methods sufficient to ensure a desired client invariant.In this sense, HATs play a similar role as typestate in object-oriented languages [Bodden and Hendren 2012;DeLine and Fähndrich 2004;Sunshine et al. 2011], which augments the interface of objects with coarse-grained information about the state of an object, in order to constrain its set of available methods.Typestate specifications differ in obvious ways from HATs, most notably with respect to the level of granularity that is expressible; in particular, HATs are designed to capture fine-grained interactions between libraries and clients that are necessary to specify useful representation invariants.

CONCLUSION
This paper explores the integration of SFAs within a refinement type system as a means to specify and verify client-specific representation invariants of functional programs that interact with stateful libraries.Our key innovation is the manifestation of SFAs as HATs in the type system, which allows the specification of context traces (preconditions) as well as computation traces (postconditions) that allow us to characterize the space of allowed client/library interactions and thus enable us to prove that these interactions do not violate provided representation invariants.HATs integrate cleanly within a refinement type system, enjoy pleasant decidability properties, and are amenable to a high-degree of automation.
There are several interesting directions for future work.While the focus of this paper is the verification of representation invariants, there is no instrinsic requirement that HATs use the same automaton in their pre-and postcondition.We intend to explore more general verification problems under this relaxation.Another potential direction is extending our current formalization to support richer automata classes (e.g., symbolic visibly pushdown [D' Antoni and Alur 2014] or Büchi automata), in order to support a larger class of safety properties, e.g., re-entrant properties of locks, quantitative properties, and properties over infinite traces) or more general categories of effects, e.g., control effects [Bauer and Pretnar 2013]).

A OPERATIONAL SEMANTICS
The auxiliary big-step reduction rules for effect operators and the small-step operational semantics of our core language are shown in Fig. 10.

B BASIC TYPING RULES
The basic typing rules of our core language and qualifiers are shown in Fig. 11 and Fig. 12.We use an auxiliary function Ty to provide a basic type for the primitives of our language, e.g., constants, built-in operators, and data constructors.

C DECLARATIVE TYPING RULES
The full set of rules for our auxiliary typing relations are shown in Fig. 13, and the full set of declarative typing rules are shown in Fig. 14.We elide the basic typing relation (∅ ⊢ s : ) in the premises of the rules in Fig. 14; which assume all terms have a basic type.

D TYPING ALGORITHM
In this section, we provide the details of our typing algorithm.The full set of bi-directional typing rules for this algorithm are shown in Fig. 15; again, we again omit the basic typing relation (∅ ⊢ s : ) in the precondition of these rules.The detail of alphabet transformation (AlphaTrans) and SFA instantiation (Abduce) procedures are provided in Algorithm 2 and Algorithm 3, respectively.
Alphabet Transformation.The alphabet transformation function AlphaTrans translates a SFA into a FA that shares the same regular operators.Notably, the alphabet transformation for effect events gathers all minterms that can imply the qualifier of the effect event (line 17); moreover, the effect event op x i = | requires the gathered minterms have the same operator (op).Ghost Variable Instantiatation.As we show in Algorithm 3, the abductive synthesizer Abduce adapts an existing algorithm [Zhou et al. 2021] that synthesizes a maximal assignment of unknown qualifier for the given ghost variables : .These inferred qualifiers are strong enough to prove the given inclusion relation between two input automata under the given type context (i.e., Γ ⊢ ⊆ ′ ).Note that the ghost variables only make sense when the inclusion relation holds with all unknown formulae are maximally weak (i.e., true) (line 2).Similar to the minterm algorithm in Algorithm 2, the abductive algorithm gathers all literals that appear in the pre-and , Vol. 1, No. 1, Article .Publication date: September 2018.

Type Synthesis
Finally, the function Abduce returns refinement type { : | } for each ghost variable , where is the union of all local minterms that can imply (line 14 -15)., Vol. 1, No. 1, Article .Publication date: September 2018.Now we can prove the soundness theorem of our typing algorithm with respect to our declarative type system.As the type synthesis rules are defined mutually recursively, we simultaneously prove both are correct.The following theorem states a stronger inductive invariant that is sufficient to prove the soundness of the typing algorithm.
T E.7.[Soundness of the type synthesis and type check algorithm] For all type context Γ, term , pure refinement type and HAT ,

P
. We proceed by induction over the mutual recursive structure of type synthesis ( ⇒ ) and type check ( ⇐ ) rules.In the cases for; synthesis and checking rules of rule S , the typing rules in Fig. 14  Case C S : This rule connects the relation between the type synthesis and type check judgement.From the induction hypothesis and the precondition of C S , we know Then the proof immediate holds in this case.
Case C L E: This rule can be treated as a combination of subtyping of intersection types (e.g., S I LL) which is exactly what we needed to prove for this case.
Case C EO A : P .As we mentioned in Section 5.1, similar to standard refinement type system, the subtyping check will be encoded as a query in EPR, which is in the decidable fragment of FOL.Thus, the base subtyping check is decidable.

P
. According to the subtyping rules shown in Fig. 13, Lemma Theorem E.8 and Lemma Theorem E.9, these rules are inductively defined and structural decreasing over the types ( 1 or 1 ).Thus, they always terminate.≈ › : is decidable.

P
. According to the rule shown in Fig. 13 and Lemma E.10, it always terminates.Now we can prove the decidability theorem of our typing algorithm.As the type synthesis rules are defined mutually recursively, we simultaneously prove both are decidable.The following theorem states a stronger inductive invariant that is sufficient to prove the decidability of the typing algorithm.

P
. According to Lemma E.11, Lemma E.12, and Lemma E.13, the auxiliary functions (wellformedness, subtyping, instantiation) always terminate.Thus, the key of the decidability proof is finding a ranking function over the inputs of typing algorithm, which is based on the size of terms (TermSize) and types (TypeSize): With the help of TermSize, we define a lexicographic ranking function as a string contains two natural numbers (a pair of natural numbers).
Rank(Γ ⊢ ⇐ ) (TermSize( ), TypeSize( )) Rank(Γ ⊢ ⇒ ) (TermSize( ), 0) where one rank is less than another iff We proceed by induction over the mutual recursive structure of type synthesis ( ⇒ ) and type check ( ⇐ ) rules.In the cases for; synthesis rule S C , S B V , and S F V don't have recursive call, thus always terminates.Consequently, we try to prove that the rank current call is greater than recursive calls in the rest of cases below.
Fig. 1.A file system datatype based on underline key-value store library.
. The denotation of ghost variables is similar to function types whose , Vol. 1, No. 1, Article .Publication date: September 2018.

Fig. 15 .
Fig. 15.Bidirectional Typing Rules align exactly with these rules.There are seven remaining cases that are about let-bindings (C L V and C L E), application (C A , C PO A , and C EO A ), and subtyping (C S V and C S ).The rule C L V, C PO A , and C S V are less interesting, which are similar with standard refinement typing algorithm[Jhala and Vazou 2021] and simply drop the pre-and post-condition automata from their effectful version.Consequently, we show the proof of three interesting cases (C S , C L E, C EO A ) below, where the case C A is almost the same as C EO A .

L E. 9 .
[Decidability of Inclusion Algorithm] the SFAInclusion is decidable.P .The gathered literals are finite, thus the induced boolean combination is also finite.Then the loop in SFAInclusion (line 3 in Algorithm 1) has a finite bound.According to Lemma E.8, SFAInclusion is decidable.L E.10.[Decidability of Instantiation Algorithm] the Abduce is decidable.P .The gathered literals are finite, thus the induced boolean combination is also finite.Then the loop in Abduce (line 6 in Algorithm 1) has a finite bound.According to Lemma E.8, Abduce is decidable.L E.11.[Decidability of Well-Formedness] For all type context Γ, type , and , the Γ ⊢ WF and Γ ⊢ WF is decidable.

Table 2 .
Details of the properties used in the benchmarks shown in Table1.Representation invariants of client ADTs are expressed in terms of their interactions with the backing stateful library.
DFA Determinism KVStore All stored transitions are represented as tuples (start, char, end); of Transitions starting state has at most have one transition for a character Graph Two nodes (which represent states in FA) can have at most one edge, which is labeled with a character ConnectedGraph Connectivity Set The set stores unique pairs (fst, snd); only elements that have both in and out edges are treated as a value node in the graph Graph All nodes in the graph are connected , Vol. 1, No. 1, Article .Publication date: September 2018.
TL .From the induction hypothesis and the precondition of C A , we know , Vol. 1, No. 1, Article .Publication date: September 2018.and