Online Detection of Effectively Callback Free Objects with Applications to Smart Contracts

01/12/2018 ∙ by Shelly Grossman, et al. ∙ 0

Callbacks are essential in many programming environments, but drastically complicate program understanding and reasoning because they allow to mutate object's local states by external objects in unexpected fashions, thus breaking modularity. The famous DAO bug in the cryptocurrency framework Ethereum, employed callbacks to steal 150M. We define the notion of Effectively Callback Free (ECF) objects in order to allow callbacks without preventing modular reasoning. An object is ECF in a given execution trace if there exists an equivalent execution trace without callbacks to this object. An object is ECF if it is ECF in every possible execution trace. We study the decidability of dynamically checking ECF in a given execution trace and statically checking if an object is ECF. We also show that dynamically checking ECF in Ethereum is feasible and can be done online. By running the history of all execution traces in Ethereum, we were able to verify that virtually all existing contracts, excluding the DAO or contracts with similar known vulnerabilities, are ECF. Finally, we show that ECF, whether it is verified dynamically or statically, enables modular reasoning about objects with encapsulated state.

READ FULL TEXT VIEW PDF
POST COMMENT

Comments

There are no comments yet.

Authors

page 1

page 2

page 3

page 4

This week in AI

Get the week's most popular data science and artificial intelligence research sent straight to your inbox every Saturday.

1. Introduction

The theme of this paper is enabling modular reasoning about the correctness of objects with encapsulated state. This is inspired by platforms like Ethereum (Eth:EthYellowPaperGavWood) that facilitate execution of Smart Contracts (Szabo1997formalizing) on top of a blockchain-based distributed ledger (nakamoto2008bitcoin). A key property in Ethereum Smart Contracts is the lack of global mutable shared state, in contrast to common standard programming environments such as C and Java. A smart contract is analogous to an object with encapsulated state.

However, the Ethereum blockchain, and many other dynamic environments, implement event-driven programming using callbacks. These callbacks are necessary for functionality, but can compromise security. For example, the famous bug in the DAO contract exploited callbacks to steal $150M (Dao).

Indeed, callbacks may break modularity which is essential for good programming style and extendibility. In the context of Blockchain, modularity is even more important since contracts are contributed by different sources, some of which may be malicious. Accordingly, the bug in the DAO allowed an adversarially crafted contract to mutate the DAO’s state by calling back to it.

The DAO contract, that implemented a crowd-funding platform, was attacked by a ‘callback loop-hole’ (to be precisely described below). This attack, the recovery from which required a controversial hard-fork of the blockchain,111A hard fork can be thought of as taking an agreed history of transactions, and manually change it. exhibits a vulnerability that is peculiar to decentralized consensus systems, like Ethereum: in such systems, a buggy contract cannot be updated or fixed (except for extreme measures like hard-forking), which makes validation and verification of smart contracts of even greater importance for this application.

Effectively Callback-Free Objects

We identify a natural generic correctness criteria for objects which enables modular reasoning in environments with local-only mutable states, and expect most correct objects to satisfy this requirement. Informally, if an object calls another object , and the execution of calls again, this second call to is defined as a callback. The main idea is to allow callbacks in only when they cannot affect the serial non-interruptible behavior of . Thus, such callbacks can be considered harmless and do not affect the set of local reachable states of the object . In particular, the behavior of such objects is independent of the client environments and of other objects. It is possible to reproduce all behaviors of the object using a most general client and without analyzing external objects.

We say that an execution is Dynamically Effectively Callback Free (dECF) when there exists “an equivalent” execution without callbacks which starts in the same state and reaches the same final state. By equivalent, we refer to the behavior of a particular object as an external observer may perceive. We say that an object is Statically Effectively Callback Free (sECF) when all its possible executions are dynamically ECF. We do not distinguish between dynamic and static ECF when the context is clear. Both definitions are useful. Dynamic ECF in particular is applicable to the blockchain environment, since static ECF is undecidable in the general case. We ran experiments on Ethereum, proving that checking dynamic ECF is inexpensive, and thus can be done efficiently in-vivo. This, combined with Ethereum’s built-in rollback feature, would have allowed to prevent the DAO bug from occurring, without invalidating legitimate executions. (In fact, we found just one such legitimate non-ECF contract, discussed in Section 9).

We show that the vulnerable DAO contract is non-ECF while no non-ECF executions are detected after applying the suggested corrections to it. Notice that the ECF notion is similar to the notion of atomic transactions in concurrent systems. Indeed, despite the fact that contract languages do not usually support concurrency, modularity and callbacks require similar kind of reasoning.

The ECF property’s usefulness is not limited to bug-finding; once ECF is established, it can be served to simplify reasoning on the object in isolation of other objects: We show that the set of reachable local states in ECF objects can be determined without considering the code of other objects and thus enable modular reasoning. This modular reasoning can be performed automatically using abstract interpretation e.g., as suggested in LogozzoCLSS09 or by using deductive verification which is supported by Dafny (Leino2010). We demonstrate this by verifying an interesting invariant of the DAO contract. (See Section 2).

Online Detection of ECF executions

A naïve detection of dECF may be costly because of the need to enumerate subexecution traces. Therefore, we develop an effective polynomial online algorithm for checking if an execution is ECF. The main idea is to detect conflicting memory accesses and utilize commutativity in an effective manner. We integrated the algorithm into the Ethereum Virtual Machine (EVM) (Eth:EthYellowPaperGavWood). We ran the algorithm on all executions kept in the Ethereum blockchain until 23 June 2017, and demonstrate that: (i) the vulnerable DAO contract and other buggy contracts are non-ECF. (ii) very few correct contracts are non-ECF, (iii) callbacks are not esoteric and are used in many contracts, and (iv) the runtime overhead of our implementation is negligible and thus can be integrated as an online check.

This online detection can thus be used to prevent incidents like the theft from the DAO at the cost of slightly more restricted form of programming.

As far as we are aware, our tool is also the most precise and effective tool for finding such vulnerable behaviors due to callbacks. We compared it to the Oyente tool (RW:LuuCCS16; Eth:OyenteOnline), by giving it both ECF and non-ECF contracts based on the DAO object (Figure 1). We found that it has false positives, as it detects a ‘reentrancy bug’ (the common name of the DAO vulnerability in the blockchain community) for any one of the fixes that render our example contract ECF.

Decidability of sECF for objects

We also consider the problem of checking sECF algorithmically. Obviously, since modern contract languages, such as Solidity (Eth:Solidity), are Turing complete, checking if a contract is ECF is undecidable.

We show that checking that a contract is sECF in a language with finite local states is decidable. This is interesting since many contracts only use small local states or maps with uniform data independent accesses. Technically, this result is non-trivial since the nesting of contract calls is unbounded, and since ECF requires reasoning about permutations of nested invocations. The reason for the decidability is that non-ECF executions which occur in high depth of nesting must also occur in depth .

Main Results

Our results can be summarized as follows:

  1. We define a general safety property, called ECF, for objects (sECF) and executions (dECF). Our definition is inspired by the Blockchain environment but it may also be useful for other environments with encapsulated states, such as Microservices.

  2. We show that objects with encapsulated data, under the assumption that they satisfy ECF, can be verified using modular reasoning in a sound manner.

  3. A stronger notion of ECF, based on conflict-equivalence, enabling efficient verification of dECF in real-life environments, and for which sECF is decidable for programs with finite state and unbounded stack.

  4. A polynomial time and space algorithm for online checking of dECF and prototype implementation of it as a dynamic monitor of dECF, built on top of an Ethereum client.

  5. Evaluation of the algorithm on the entire history of the Ethereum blockchain (both main and ‘Classic’ forks, see Section 9). The monitor detects true bad executions (the infamous DAO and others) as non-ECF, and has near-zero false positives. Based on this result, it can be inferred that, in practice, most non-ECF executions correspond to bad executions. We also show that our monitor has a very small runtime overhead. By retroactively running the dECF monitor on the available history, we were able to prove its effectiveness in preventing the exploitation of the vulnerability in the DAO, and even more importantly, the feasibility of leveraging it in other applications, e.g., simplifying modular contract verification.

2. Overview

This section provides some necessary background and an informal overview of our approach.

2.1. The DAO Bug

Figure 1 shows pseudocode illustrating the vulnerability in the DAO.222DAO is acronym for decentralized autonomous organization, and its purpose is to facilitate voting on proposals and on investments by the owners of the DAO. The contract stores a credit for each object, as well as the current balance.333In programming languages like Solidity, balance is a predefined field of every contract, maintained by the runtime system. We write it explicitly for clarity. The credit represents individual investments per object. To align with the Ethereum terminology, the unit of currency represented by credit and balance is called ether. The contract maintains a representation invariant, where the sum of the credits equals to the current balance, i.e.,

(1)

The contract offers two methods for manipulating states: deposit for depositing money and withdrawAll for withdrawing all available funds of a specific object.




Object DAO



Map<Object, int> credit



int balance



Invariant (sum o: credit[o]) = balance



Method withdrawAll(Object o)         Method deposit(Object o, int amount)




2: if (oCredit > 0)                    6: credit[o] += amount





// 2.5: credit[o] = 0                7: this.balance += amount





3: this.balance -= oCredit





4: o.pay(oCredit)





5: credit[o] = 0

Figure 1. A contract illustrating the DAO bug. The representation invariant may be violated by callbacks from malicious contracts. Line fixes the bug.
Object GoodClient
Object Dao, int balance
Method init(Object dao)
1: this.Dao = dao
Method pay(int profit)
2: this.balance += profit
Method depositCredit( Object dao, int amount)
3: Dao.deposit(this, amount)
Method getCredit(Object dao)
4: Dao.withdrawAll(this)
(a) An innocent client using the DAO object without violating its representation invariant.
Object Attacker
Object Dao, bool stop, int balance
Method init(Object dao)
1: Dao = dao
2: stop = false
Method pay(int profit)
3: this.balance += profit
4: if (!stop)
5: stop = true
6: Dao.withdrawAll(this)
7: stop = false
(b) A snippet of an Attacker object. It is stealing money from the DAO object by violating its representation invariant.
Figure 2. An innocent and a malicious client using the DAO object
@C14pt *+[F=] [r]^-w {_-1 &*+[F] [r]^-p { _-2&*+[F] [r]^-w { _-3&*+[F] [r]^-p { _-4 &*+[F] [dlll]^-p } _-5
&*[F] [r]^-w } _-6&*+[F] [r]^-p } _-7&*+[F] [r]^-w } _-8&*+[F=]
Figure 3. A trace of calls illustrating an attack on the DAO. Nodes are labeled by local changed states and edges are labeled by actions and by the corresponding order in the original trace. denotes the DAO, denotes a GoodClient and is an Attacker object. denotes the withdrawAll operation and denotes the pay operation. is a shorthand for balance, is a shorthand for credit, and is a shorthand for stop.
@C14pt *+[F=] [r]^-w {_-1 &*+[F] [r]^-p { _-2 &*+[F] [r]^-w { _-3 &*+[F] [r]^-w } _-4 &*+[F] [r]^-p } _-5 & *+[F] [r]^-w } _-6 &*+[F=]
@C14pt *+[F=] [r]^-w {_-1 &*+[F] [r]^-w } _-6 &*+[F] [r]^-p { _-2 &*+[F] [r]^-w { _-3 &*+[F] [r]^-w } _-4 &*+[F] [r]^-p }_-5 & *+[F=]
Figure 4. Two traces of calls illustrating the original and callback-free versions of a failed attack on the ECF version of the DAO.

Figure 1(a) shows a simple client illustrating the expected usages of the DAO object. Figure 1(b) shows a simple attack on the DAO object. The code callbacks to the DAO method withdrawAll to steal money. Figure 3 depicts a concrete trace of attacking the DAO assuming that the DAO’s initial balance is ether. We reached that state after a GoodClient object and an Attacker object deposited each ether. In the first call to withdrawAll, the attacker will get the amount he invested originally in the attack ( ether). The DAO then calls to the Attacker object’s pay method, which increases the attacker’s balance by ether, and calls withdrawAll again. The pay method is designed to call withdrawAll at most once in a trace by updating the stop variable, and avoid infinite recursion.444For clarity, we avoid technical discussion of the semantics of executions and exceptions in Ethereum/Solidity, to allow us to focus on the ECF property. The code of withdrawAll in the second run will transfer an additional ether from the DAO object to the attacker. In the end of the trace, the DAO was depleted of its funds completely, and the attacker managed to illegitimately receive the funds that belonged to GoodClient.

2.2. Effectively Callback Free Contracts

In principle, semi-automatic program verification and abstract interpretation can be used to verify the absence of malicious attacks like the one in the Attacker object. However, this requires reasoning about the whole code.555In the case of Ethereum, it is in fact impossible to reason about the whole code, as new contracts can be added at any time, and these contracts could interact with the contract being checked. This paper advocates a different solution by exploring modularity. The idea is to require stronger conditions from the contracts which prevent the need to reason about other objects at all.

Specifically, we define the notion of effectively callback free (ECF) objects. Our definition is inspired by Blockchain contracts but is applicable to enforce modularity in other environments with local states.

We say that an execution of an object with an initial state and final state is Dynamically Effectively Callback Free (dECF) when there exists “an equivalent” execution of the contract without callbacks which starts in the same initial state and reaches the final state . We say that an object is Statically Effectively Callback Free (sECF) when all its possible executions are effectively callback free.

The DAO object is not ECF. For example, the trace depicted in Figure 3 cannot be reproduced without callbacks to reach the same state. In contrast, the fix to the DAO object by uncommenting line and deleting line makes the contract ECF. This contract is now ECF since all its traces can be reordered to avoid callbacks. For example, Figure 4 shows a trace of an attempt to perform an attack similar to the attack in Figure 3 and its corresponding reordering that avoids callbacks. Note, that in the reordered trace, withdrawAll did not execute line . Omitting calls is allowed for the sake of proving an execution is ECF, as our goal is to be able to reproduce, assuming there are no callbacks, the same behaviors that are feasible with callbacks.

2.3. Online ECF Detection

It is possible to check in a naïve way that an execution is ECF by recording the trace and checking the ECF property at the end of the execution, by enumerating all possible permutations. However, this is costly both in space and in time, since the number of permutations grows exponentially with the size of the trace. In particular, it is hard to see if such a solution can be integrated into a virtual machine.

In order to obtain a feasible online algorithm, we check a stronger requirement than ECF, which is inspired by conflict serializability of database transactions. The main idea is to explore commutativity of operations for efficient online checking of a correctness condition which guarantees that the callback-free trace results in the same state as the original trace.

Consider a trace with potential callbacks and a reordered trace which does not include callbacks. is not necessarily feasible, unless we permit to ignore external calls by objects and force the clients to perform these calls instead. We say that and are conflict equivalent if every pair of conflicting read/write operations in appear in the same order in . Operations conflict when they are not commutative. Commutativity is mechanically checked by comparing the read and the write sets of operations, and forbidding intersection of read/write conflicts. For example, in Figure 3, the read operation of D.c[A] in the withdrawAll action labeled 1 (lines 1-3) does not commute with the write operation of D.c[A] in the withdrawAll action labeled 5 (line 5). However, the top trace in Figure 4, depicting a trace of the ECF version of DAO, the operations in the withdrawAll action labeled 3 (lines 1-2) commute with the operations in the withdrawAll action labeled 6, which has an empty read and write sets (no code was executed since line 5 was deleted). The information regarding commutativity of different subtraces is used to build a constraint graph on the ordering of object invocations. When this constraint graph contains no cycles, it is possible to perform topological sort to find a concrete callback-free trace. A full description of the algorithm and its complexity is available in Section 8.

We integrated this algorithm into the EVM (the Ethereum Virtual Machine) and applied it to all available executions in the blockchain. The results are summarized in Section 9. They indicate that the vast majority of non-ECF executions come from erroneous contracts. They also indicate that the runtime overhead of our instrumentation is neglectable. From these encouraging results, we concluded that if the ECF check was part of the Ethereum protocol, it could have prevented the vulnerability in the DAO from being exploited. Its clearly beneficiary for an environment like Ethereum, which handles sensitive financial transactions, and in which code is virtually impossible to upgrade.

2.4. Deciding ECF Contracts

We also investigated the possibility to verify at compile-time that a contract is ECF (the sECF property). In general, this is undecidable, since languages such as Solidity, a high-level front-end to EVM bytecode, are Turing-complete. However, we show that for contracts with finite local states, checking ECF is decidable. This result is non-trivial as the model allows for an unbounded stack length. The decision procedure devised provides insight on additional techniques for checking ECF in practice.

2.5. Verifying Properties of ECF Contracts

In this paper, we show that reasoning about ECF contracts can be performed in a modular fashion. The local reachable states of an ECF contract are only affected by the code of the contract, and cannot be changed by external contracts.

This is useful for program verification and program analysis, treating external calls as non-deterministic operations that may return an arbitrary value, but cannot change the local state. We utilized this property using Dafny (Leino2010), to verify correctness of the revised DAO object from Figure 1 (including line 2.5, excluding line 5). When doing so, we ignored the call in line 4, because the return value was not used. We provide a deeper discussion on verifying this example using Dafny in Section 7.

2.6. Summary of the Rest of This Paper

The paper is organized as follows: In Section 3 we formally present the syntax and semantics of our programming language for contracts, called . Notions of equivalence are presented in Section 4. The ECF property and its different ‘flavors’ (dynamic vs. static, and for different equivalence notions) are presented in Section 5. We discuss decidability results for ECF in Section 6. Section 7 shows the application of the ECF property to achieve modular object-level analysis. The algorithm for online verification of dECF is given in full in Section 8. We discuss our experimental results obtained by running the algorithm on the Ethereum blockchain in Section 9. Related work is provided in Section 10 and we conclude in Section 11.

3. Programming Language

We formalize our results for , a simple imperative object-based programming language with pass-by-value parameters with integer-typed local variables and data members (fields). For simplicity, and without loss of generality, every method has a single formal parameter named arg and returns a value by assigning it to a designated variable ret. Even though we present our theoretical development for contracts in , for readability we use a Java-like notation in our examples, which can be easily desugared.

Figure 5. Syntax.

3.1. Syntax

Figure 5 defines the syntax of . We assume infinite syntactic domains of , , and contract identifiers, field names, and variable identifiers, respectively. A contract is identified by a (unique) contract identifier , and contains a sequence of field definitions and a (single) nameless contract method. The contract method is comprised of a sequence of local variable definitions and a command . may be a primitive command or a compound command, i.e., a sequential composition of commands, a conditional, or a loop. A primitive command may be either an assignment of an expression to a local variable (, an assignment of the value of a local variable to a field (), an assignment of the value of a field to a local variable (), an assert command (), a call to a contract method with a single argument , keeping the returned value in a local variable (), or a skip command. Each contract has a single method, thus methods are not named, and may be colloquially referred to using the name of their contract.

Without loss of generatility, we assume that no two contracts contain a field with the same name. In the following, we use the terms ‘contract’ and ‘object’ interchangeably.

Figure 6. Semantic domains.

3.2. Semantics

has a rather mundane stack-based operational semantics, which handle method calls using a stack of activation records (frames), and uses a store to record the values stored in object fields. We refer to a state in which the stack is empty as a quiescent state and to a non-quiescent state as an active state. Once the execution reaches a quiescent state, any object method may start running. We refer the reader’s attention to three important points: (i) contract states are encapsulated: A contract can only access its own fields, (ii) local variables are private to their invocation, and (iii) once a contract method is invoked, the semantics is deterministic. We denote the code context (context for short) which provides the code of every called object by , i.e., denotes the code of object .

States

Figure 6 defines the semantic domains. A state is a pair comprised of a (possibly empty) stack of frames and a store , denoted by and , respectively. The depth of a state , denoted by , is the number of elements in its stack, i.e., .

We denote the top of the stack in an active state by . Intuitively, contains the local state of the active (i.e., currently executing) contract method, while the other frames record the locals states of pending calls to contract methods. A frame records the local state of (a call to the contract method of) an object. Formally, is a triple comprised of an object identifier, denoted by , a command, denoted by , which the method needs to execute, and a local environment , denoted by , which assigns values to the invocation’s local variables. A store is a mapping from a finite number of object identifiers to their object state.

Figure 7. Operational semantics with a context . is naturally extended for expressions over variables in . We denote .
Transition relations

We formalize the semantics of our programming language using a transition relation. A transition is a pair comprised of a source state , denoted by , and target state , denoted by . For clarity, we sometimes write a as . We denote the active object of the transition by , or if it starts in a quiescent state. We denote by the primitive command that justifies the transition.

The meaning of primitive and compound commands is standard, and thus omitted. We mention that primitive commands can only use local variables taken from the top stack frame, and that only the fields of the active object can be accessed.

Figure 7 defines meaning of method calls and returns. When an object is called from a quiescent state, a new stack frame is pushed to the currently empty stack. The frame determines that the active object is , the command executing is the code of , and the local environment for the invocation is the assignment of the value of to arg. The last command in is always a return, after which the frame is popped, leading to a quiescent state. When a call is made from an active state, a new stack frame is pushed as in the previous case. We note that the local environment is initialized by assigning to arg the value of in the local environment belonging to the caller, . To handle retrieval of the return value from the callee, the command in the caller is modified to assign to the value of a specially designated variable res. When the callee invocation of finishes, the command in the top frame is return and we let denote the local environment of the callee. The control transfers back to the caller object , and the value of res is set to be the value of ret in . The assigned value of res is then automatically assigned to , as determined by the operational semantics of the call. The primitive command associated with a call is enter, and with a return is return.

Executions

An execution is a finite sequence of transitions coming from . An execution is well-formed if the target state of every transition is the source state of the following one, i.e., . For clarity, we sometimes write an execution as . We use to denote that an execution takes objects’ code from context . We omit the context when no confusion is likely.

We say that a transition appears in a , denoted by , if . We say that a state appears in a , denoted by , if there is a transition such that . We denote the sets of transitions and states that appear in an execution by and , respectively. An execution is a subexecution of an execution , denoted by , if it is a subsequence of .

We denote the first and last states of a non-empty execution by and . We say that is a complete execution if and are quiescent states and contains only active states. A run is a concatenation of complete executions executed in the same code context , By abuse of notation, we use to denote runs as well as executions. In case we want to make the code context of the run explicit, we write .

The minimal and maximal depths of a non-empty execution , denoted by and are the minimal, respectively, maximal depths of any of the states it contains.

A well formed execution is an invocation in an execution if there exist transition and such that , where and . We refer to as the depth of the invocation and denote it by . Note that according to this definition, the depth of an invocation that results from calling a contract method on a quiescent state is one.

Traces

We define an event as a pair , consisting of an object , and a primitive command . Each transition can be transformed to an event by . The object and primitive command of an event are can be retrieved with and , respectively. A trace is a sequence of events, denoted by . The trace matching an execution is received by point-wise application of on all the transitions in , denoted . We denote by the maximal subsequence of comprised of events whose object is .

4. Execution Equivalence

We define two notions of equivalence of runs (sequences of complete executions) with respect to a given (arbitrary) object : final-state equivalence and conflict equivalence.

4.1. Object-Final-State Equivalence

Definition 4.1 ().

Runs and are object-final-state equivalent for an object , denoted by , if their respective first and last states have the same store for , and their code contexts agree on :

Note that in Definition 4.1 it may be that as long as and map to the same code.

4.2. Object Conflict-Equivalence

Conflicts

Two primitive commands and conflict, denoted by , if both access the same field and at least one of these accesses is a write.

Recall that we assume that object states are comprised of integer-typed data members (fields); thus it is possible to detect the heap locations they access from the (syntactic) primitive commands that they execute. Furthermore, recall that different objects contain different fields and an object cannot directly access the fields of another; thus if two primitive commands conflict then they must be executed by the same active object.

Modular Well-Formed Executions

We extend the definition of well-formed executions to modular well-formed executions, which allow to replace the subexecution resulting from a method invocation by an assignment of arbitrary value to . We refer to such an (implicit) transition as a havoc transition. Intuitively, a havoc transition allows to safely overapproximate the only effect that an object may observe from the invocation of a method on an object .

Definition 4.2 ().

A havoc transition, denoted by , is a pair of states such that and for any values of , , , , , and .

Definition 4.3 ().

A modular well-formed execution is a finite sequence of transitions coming from such that for any consecutive transitions it contains, the target state of and the source state of are either (i) equal, i.e., , or (ii) induce a havoc transition, . By abuse of notations, we use to denote modular well-formed executions too. A sequence of transitions is a complete modular well-formed run if it is modular well-formed and its first and last states are quiescent. Such a run is a complete modular well-formed execution if, in addition, all other states are active.

Projected Executions

We define what is the projection of an execution on an object, called projected execution. The definition readily generalizes to any sequence of transitions.

Definition 4.4 ().

Let be an execution. The projected execution of on , denoted by , is the subsequence of comprised of the transitions whose active object is .

Lemma 4.5 ().

For any execution and object it holds that .

It will be useful in the following sections to consider modular well-formed executions yielded by a projection on an object . The following proposition states that it is possible to reverse that projection, namely, to find a minimal well-formed execution that, when projected, yields the modular well-formed execution we started with.

Proposition 4.6 ().

Let be a modular well-formed execution where all transitions have the same active object . Then there is a context such that and an execution such that is a subsequence in and is well-formed. In addition, such an execution is minimal in the sense that

Finally, we present the definition for execution conflict equivalence with respect to an object .

Definition 4.7 ().

Let and be modular well-formed runs and and be their the traces of their respective projections on . and are object conflict-equivalent for an object , denoted , if:

  1. ,

  2. ,

  3. there exists a permutation such that:

    1. for any it holds that , and

    2. for any , if then .

5. Correctness conditions

In this section we give a formal definition for two notions of the ECF property: final-state ECF and conflict ECF. We start by formally defining callbacks and callback-freedom in executions.

A stack frame is a callback frame (to object ) in a stack if there exist stack frames and such that and , but . A stack contains a callback (to ), denoted by , if it contains a callback frame. A state contains a callback (to ), denoted by , if its stack does, and an execution contains a callback (to ), denoted by , if it contains a state such that . A stack resp. state resp. execution is callback-free (for ), denoted by resp. resp. , if it does not contain a callback.

We now define what it means for an execution to be effectively callback free, or ECF, with respect to a given object . Note that ECF is a property of both an execution and some object . Specfically, we are interested in the case where has a callback to .

Definition 5.1 ().

A well-formed complete execution is equivalently effectively callback-free for an object , denoted by , if there is a well-formed callback-free run , which is final-state equivalent for to :

We say that the execution is a witness for being a execution.

Checking is difficult in practice, and undecidable in general for models with an infinite state. We describe a stronger definition of ECF, based on conflict-equivalence, called ECFc, which permits an efficient algorithm for checking it. Interestingly, even though executions in our model do not allow for concurrency, callbacks can be thought of as allowing to express a limited subset of concurrent executions. In fact, the ECF property in our model is analogous to serializability in models that permit concurrency. Using this analogy, invocations are analogous to transactions. We show what this means to reorder invocations in the sequential semantics of .

In general terms, ECFc requires to find a callback-free execution which is conflict-equivalent to the execution with the callbacks. Conflict-equivalence requires that the trace of the callback-free execution is a permutation of the trace of the original execution. It is thus useful to start with a characterization of the legal permutations of an execution. Firstly, the permutation may not break program order of contract code. That is, the permutation must retain the ordering of events whose transitions are part of the same invocation , and their state have the same depth as . Secondly, we want to allow permutations that remove callback invocations from their original call location, and sets them to execute in a quiescent state, and still receive a modular well-formed execution.

Figure 8. The callback reorder process. The first graph represents the original execution, which contains a callback (in red). The three other graphs represent all possible callback free executions. Red marks the moved callback transitions. Wave edges indicate that a call was replaced with a havoc transition.

When we permute a trace such that a callback invocation is removed from its original place, we replace the call transition leading to the callback with a havoc transition. An example can be seen in Figure 8, showing all legal permutations of a trace that has a callback-free execution with havoc transitions.

A havoc transition is not part of the permuted trace; it is merely used to justify the execution which is no longer well-formed as we defined earlier.

Using modular well-formed executions, we can formally define the ECFc property for executions, dECFc:

Definition 5.2 ().

A well-formed complete execution is conflict-equivalently effectively callback-free for an object , denoted by , if there is a modular well-formed callback-free run which is object-conflict-equivalent to for :

It is easy to prove that conflict equivalence implies final-state equivalence (see, e.g., (BOOK:BHG87)). Thus, it can be concluded that ECFc implies ECFfs as, by using Proposition 4.6, we can use the same witness for being dECFc to prove that is also dECFfs.

Theorem 5.3 ().

Let be a well-formed complete execution. If then .

Proof.

Since , there is a callback-free modular well-formed complete execution such that . It is easy to see that since then is too a callback-free and modular well-formed run. From an immediate generalization of Proposition 4.6 to runs, we conclude that for there is a context and a well-formed run such that , and . It should be noted that the equality of the trace projected on implies callback-freedom for is retained: namely, . Conflict equivalence implies final-state equivalence ((BOOK:BHG87)), hence, and . Also, because conflict ordering in primitive commands of is retained between and , we conclude that , therefore and . It can be concluded then that since (i) and , (ii) , and (iii) is well-formed, then: . making a witness proving . ∎

Finally, as we are also interested in ECF as a property of objects (sECF), we extend the definitions of ECFfs and ECFc to objects (sECFfs and sECFc) instead of executions (dECFfs and dECFc), which we refer to as static ECF.

Definition 5.4 ().

An object is sECFfs if for every complete execution it holds that . is sECFc if for every complete execution it holds that .

6. Decidability

This section discusses the decidability of verifying ECF. Using Rice Theorem (see, e.g., ullman), it is easy to show that verifying sECF, namely, statically verifying whether all executions of an object are ECFfs or ECFc, is an undecidable problem. Interestingly, checking ECFfs for a single execution () is also undecidable.

Theorem 6.1 ().

Given an execution , checking if it is is undecidable.

Proof.

We show a reduction from the halting problem.

is Turing-complete, thus we encode the operation of a Turing machine

as a command . In Figure 9, we present the code of a contract A. The store of A has a single field initialized as , and a single argument denoted . The field is unchanged by . We also write the code of a contract B. The method of A is separated to 3 branches. If then the method returns without any effect. If , then is updated to and the method returns. If , then is updated to and if the argument is equal , we execute the TM and update to 2 if and when finished running. If the argument is not equal , we call contract B. If right after B’s execution is not equal , then is updated to . The code of B is calling to A. Therefore, when A calls B, B always creates a callback to A.

Object A
int X
M’s fields
enter (arg)
if X == 0 then
X := 1
if arg != 0 then
B()
if X != 2 then X := 3
else
run M()
X := 2
else if X == 1 then
X := 2
return
Object B
enter
A(0)
return

Figure 9. The codes of two objects A and B showing that is undecidable, and a diagram showing the possible flows of the system. Nodes marked are quiescent states, with the current value of A’s variable in subscript. Nodes starting with indicate entry to an object , and a return from an object . The notation indicates the call is a callback. Entry nodes are marked with the value of for better readability. Syntactic references to the code (assignments, calls, conditions) appear on the edges. Missing call edges from quiescent states indicate that the resulting quiescent state is the same, hence, and are sinks.

We consider the execution starting from the initial state in which A’s field is equal , with . This execution calls the object B, which calls back to A, and in the callback the value of is set to . We show that is if and only if halts. For the ‘if’ direction, we note that if halts, then the execution of from the state where leads to being set to right after ’s run finished, and that execution has no callbacks, as required. For the ‘only if’ direction, we note that the only callback-free execution that starts from and ends with is the call A(0), which is the execution that executes , and it is a legal execution only if halts. The reason that this must be the only execution, is that for any choice of input argument and context that maps a different code for B, does not allow reaching the required final state unless callbacks are used. If still calls back to A then clearly the resulting execution is not an eligible ECF witness. If does not call back to A, then is updated to . Therefore, when , any subsequent call to A cannot modify , and in particular is not reachable. ∎



arg = *;

... // Code of o

x := o’(y) --> E(); x := *;

...



E = while (*)


arg = *;


o(arg);



arg = *;

... // Code of o

x := o’(y) --> x := *;

...

Figure 10. The construction of automatons and . The notation --> indicates that a command in the code of is replaced with another.

In contrast, checking ECFc for a single execution (dECFc) is obviously decidable, as we can enumerate all of the permutations of a particular input trace.

Thus, we focus on verifying sECF, namely, statically verifying whether all executions of an object are ECFfs or ECFc, where the domains of the object variables are restricted to finite sets. Hence, such objects can be modeled with a pushdown-automaton (PDA). Such a PDA for an object is able to simulate any modular well-formed execution where the active object of all states in is . We denote this construction . Its code is shown in the left side of Figure 10. It executes the code of with a non-deterministically chosen argument, and replaces every call to an external object with a sequence of arbitrary calls to (the code in E), and a non-deterministic choice of the return value of the call. We begin with a rather simple lemma that shows ECFfs of objects is indeed decidable in this model. We assume that variables may take values coming from a finite domain.

Lemma 6.2 ().

Let be an object, assuming a finite domain for variables. Then there is an algorithm that decides if is sECFfs.

Proof.

We consider the pushdown automaton and an automaton that allows only callback-free behaviors for , . Both are shown in Figure 10. Each execution of consists of first choosing non-deterministically an argument with which the object is called. The automaton simulates the steps of with the only difference being in invocations of other objects, e.g. . This command is replaced with the code of the “environment” object , that performs a sequence of calls to with an arbitrary argument chosen in each iteration. The sequence length is arbitrary and may be also , i.e., no callbacks. After running the callbacks, the automaton non-deterministically chooses a return value, stored in the original variable intended to store the return value of , which in the figure is the variable .

The automaton that allows only callback-free behaviors for is simpler: the only difference is that it does not run the code of . In fact, is a finite state machine.

We now utilize the result by (bouajjani1997reachability) for reachability of a regular set of configurations of a pushdown automaton. Here, the set of configurations is , and the subset of configurations which we are interested in is the one with with an empty stack, i.e. where is the set of ’s possible states, which is finite and thus regular.

We consider an arbitrary pair of states . We first check if there is an execution of starting from and ending in . This can be done by checking if is reachable from the initial state and as well as if is reachable from . As the path that shows reachability in may not be callback-free, we check for reachability also in the finite state machine , in the same manner. Reachability in finite-state machines is known to be decidable. If there is no execution in from to , then the object is not sECFfs. After checking for all pairs in that for each pair of states there is an execution of starting in and ending in if and only if there is such an execution in , we verified that is sECFfs. The set of all pairs of states is finite, thus it describes a decision procedure for verifying sECFfs for an object .

Showing the decidability of sECFc of objects is not that easy, because it requires reasoning on permutations of events, which is not a regular property, even in the case of finite-state machines.

Our strategy for proving the decidability of sECFc will be as follows. As before, we consider executions of , which identify with the set of possible projected executions on . We will show that it is enough to check subexecutions of which involve at most two elements of the stack. Namely, it is the same as checking the set of executions of with a limit of two to the depth of the stack, where the initial state of the execution may include both the initial states of an execution of , and in addition any state where a callback may be called. Explicitly, is a state whose primitive command is a call. We describe such states using the set . It is regular as we only consider the top element of the stack. Finding for the set of states its subset of reachable states in can therefore be done using (bouajjani1997reachability).

We denote by the automaton that is received by limiting the stack depth of to two, and having initial states . For , deciding if all its executions are is decidable. We do so by constructing a monitor that receives as input execution traces of and outputs an error if the executions are not . Then we will show that is a finite state machine and thus checking reachability of its error states is decidable. Such a construction is possible because even though conflict-equivalence demands finding a permutation of a trace, which is not a regular property, the actual choice of permutations that have to be checked is much more limited.

Lemma 6.3 ().

Let denote the set of all executions of . There is an automaton that for each , ends in an accepting state if , and in a rejecting state otherwise.

=
d = 0, prefix = , delayedCbs =
for ()
if ()
R := R
if ()
W := W
if ( && d=0) // Execution starts
R, W :=
d := d+1
if ( && d=1) // Execution ends
assert((R,W) commutes with delayedCbs)
R, W :=
d := d-1
return
if ( && d=1) // Callback starts
assert((R,W) commutes with delayedCbs)
prefix := (R(prefix) R, W(prefix) W)
R, W :=
d := d+1
if ( && d=2) // Callback ends
if (!((R,W) commutes with prefix) || !((R,W) commutes with delayedCbs))
delayedCbs := (R(delayedCbs) R, W(delayedCbs) W)
R, W :=
d := d-1
Figure 11. The code of which accepts an execution of and verifies if it is
Proof.

We write the code of the automaton in Figure 11. The automaton loops on each event in the trace of the execution. The automaton states consist of a depth variable d, and two pairs of read and write sets: prefix and delayedCbs. The sets R and W are updated in each command that reads from or writes to the object store. They are reset when a callback starts or ends. The prefix pair retains the accumulated reads and writes in the invocations at depth , i.e. the first invocation of and which we refer to sometimes as the main invocation. The delayedCbs pair retains the accumulated reads and writes by callbacks that we choose to execute after the main invocation ends.

Intuitively, the monitor checks each time a pair of (R,W) is finalized and before it is reset, if it satisfies conditions that will allow to find a conflict-equivalent execution. For a callback execution, we check if the pair commutes with the prefix, i.e. all portions of the main invocation already executed. If it does not commute with the prefix, we mark it as a delayed callback, and update the delayedCbs read and write sets. As we do not know whether delayed callbacks actually commute with the rest of the invocation, and with future callbacks that may be executed before the main invocation, we retain the conflict information in delayedCbs to be checked later against any finalized (R,W) sets that is belonging either to the main invocation, or to a callback that is chosen to be executed before the main invocation. Therefore, even in the case when a callback commutes with the prefix, but in which it does not commute with the delayed callbacks (which are ‘jumping over’ the callback under consideration in order of execution), we have to try to execute it as a delayed callback as well. Otherwise, the execution is surely not , and we move to an error state. For a portion of the main invocation (the first or last one, or between callbacks), we check if it does not conflict with any of the relevant delayed callbacks. The delayedCbs pair is always updated correctly with the currently delayed callbacks’ read and write locations.

It can be seen from the construction that if accepts, then we can build from ’s execution a witness for being by ordering all non-delayed callbacks before the main invocation and the delayed callbacks after the main invocation, both of those in the order in which they appear in the original execution. E.g., if callbacks and are both delayed callbacks, then if is executed before in then the ordering between them in the witness is not changed. The same argument applies for non-delayed callbacks.

In addition, if is then must accept. This is because any witness for being is conflict-equivalent to the witness implicitly produced by . The reason for that is that in the witness, we can also identify a set of callbacks executed before the main invocation and a set of callbacks executed after it. The internal ordering of callbacks that are executed before the main invocation does not matter as long as it does not reorder conflicts, and thus may in fact be the same as in the original execution, ditto for the other set of callbacks. ∎

We note that has a finite state: d in , and prefix and delayedCbs in where is the set of ’s fields. Therefore, is a finite state machine, and thus reachability is decidable. In particular, we can check if the error states are reachable. However, is a machine that works on any execution of any object. It is not difficult, though, to build as a monitor of . We denote this construction . The result is still a finite state machine with decidable reachability, since , which has a bounded stack depth of 2, is also a finite state machine.

Corollary 6.4 ().

It is decidable to check if all executions of are

We now show that the previous corollary implies that if all executions of are , then then so are all executions of . In particular, it shows that is sECFc. Importantly, the converse is also true: if an object is sECFc, then all executions of are . This is trivial to show: for an execution of such that it is easy to find a complete execution in of the form which is also not . Specifically, will be a subexecution that reaches the initial state of , will be identical to , only changing the stacks in ’s states to account for ’s transitions, and will complete the execution. Any permutation of will induce a conflict equivalence breaking permutation on , whose conflicts are the same as those of , therefore