<<

DEGREE PROJECT IN TECHNOLOGY, FIRST CYCLE, 15 CREDITS STOCKHOLM, SWEDEN 2020

Extracting scalable program models for TLA model checking

THEO PURANEN ÅHFELDT

ANDERS ÅGREN THUNÉ

KTH ROYAL INSTITUTE OF TECHNOLOGY SCHOOL OF ELECTRICAL ENGINEERING AND

Extracting scalable program models for TLA model checking

THEO PURANEN ÅHFELDT ANDERS ÅGREN THUNÉ

Bachelor in Computer Science Date: June 5, 2020 Supervisor: Dilian Gurov Examiner: Pawel Herman School of Electrical Engineering and Computer Science Swedish title: Extrahering av skalbara programmodeller för TLA-modellprövning

iii

Abstract

Program verification has long been of interest to researchers and practition- ers for its role in asserting reliability in critical systems. Many such systems feature reactive behavior, where temporal properties are of interest. Conse- quently, a number of systems and program verification tools for dealing with temporal have been developed. One such is TLA, whose main purpose is to verify temporal properties of systems using model checking. A TLA model is determined by a logical formula that describes all possible behaviors of a system. TLA is primarily used to verify abstract system designs, as it is considered ill-suited for implementation code in real programming languages. This thesis investigates how TLA models can be extracted from real code, in order to verify temporal properties of the code. The main problem is get- ting the model size to scale well with the size of the code, while still being representative. The paper presents a general method for achieving this, which utilizes deductive verification to abstract away unnecessary implementation details from the model. Specifically, blocks which can be considered atomic are identified in the original code and replaced with Hoare-style assertions rep- resenting only the data transformation performed in the block. The result can then be translated to a more compact TLA model. The assertions, known as block contracts, are verified separately using deductive verification, ensuring the model remains representative. We successfully instantiate the method on a simple C program, using the tool Frama-C to perform deductive verification on blocks of code and translat- ing the result to a TLA model in several steps. The PlusCal language is used as an intermediary to simplify the translation, and block contracts are successfully translated to TLA using a simple encoding. The results show promise, but there is future work to be done. iv

Sammanfattning

Programverifiering har länge varit av intresse för att kunna försäkra sig om tillförlitligheten hos kritiska system. Många sådana system uppvisar ett reak- tivt beteende, där temporala egenskaper är av intresse. Som följd har ett antal system och programverifieringsverktyg för hantering av temporallogik utveck- lats. Ett sådant är TLA, vars huvudsakliga syfte är att verifiera egenskaper hos abstrakta algoritmer med hjälp av modellprövning. En TLA-modell bestäms av en logisk formel som beskriver alla möjliga beteenden av ett visst system. TLA anses mindre lämpligt för riktig implementationskod och används främst för att verifiera egenskaper hos abstrakta systemmodeller. Denna uppsats undersöker hur TLA-modeller kan extraheras från verklig kod för att verifiera kodens temporala egenskaper. Det huvudsakliga proble- met är att även för större program kunna skapa en modell av hanterbar storlek som ändå är representativ. Vi presenterar en allmän metod för att uppnå det- ta som använder deduktiv verifiering för att abstrahera onödiga implemente- ringsdetaljer från modellen. Kodblock som kan betraktas som atomiska i den ursprungliga koden identifieras och ersätts med blockkontrakt som represente- rar datatransformationen som utförs i blocket. Resultatet kan sedan översättas till en mera kompakt TLA-modell. Kontrakten verifieras separat med deduktiv verifiering, vilket säkerställer att modellen behålls representativ. Vi instantierar med framgång metoden på ett enkelt C-program. Verkty- get Frama-C används för att utföra deduktiv verifiering på kodblock och flera steg genomförs för att översätta resultatet till en TLA-modell. Algoritmspråket PlusCal används som ett mellansteg för att förenkla översättningen, och block- kontrakt översätts till TLA med en enkel kodning. Resultaten är lovande, men det finns flera punkter som kräver ytterligare arbete. Contents

1 Introduction 1 1.1 Research Problem ...... 2 1.2 Thesis Organization ...... 2

2 Background 3 2.1 Deductive Verification ...... 3 2.1.1 Hoare Logic ...... 3 2.1.2 Frama-C ...... 5 2.2 Model Checking ...... 6 2.3 The of Actions ...... 6 2.4 TLA+ ...... 9 2.4.1 PlusCal ...... 10 2.5 Combining Approaches ...... 11

3 Method 14 3.1 Problem Statement ...... 14 3.2 Approach ...... 15 3.2.1 General Approach ...... 15 3.2.2 Instantiation ...... 15 3.2.3 Motivation ...... 16

4 Results 18 4.1 A Server Example ...... 18 4.2 A Truck Example ...... 22

5 Discussion 24 5.1 Related Work ...... 25

6 Conclusion 27

v vi CONTENTS

Bibliography 28

Acknowledgment 30

A TLA+ Specifications 31 A.1 The Server Example ...... 31 A.2 The Truck Example ...... 33 Chapter 1

Introduction

Digital systems are growing more and more complex, and we often depend on these systems being reliable. This is especially true in critical systems like self-driving cars, where an error could lead to lives being lost. Program verifi- cation is the study of making sure programs, such as these, work in the intended manner. This is usually done by first specifying exactly what the intended be- havior of the program is, and then formally verifying that the program adheres to the specification using logic. Typically, there are two primary aspects of program behavior that are verified in this way: how a program transforms data and how the program state changes over time. The difference between the two may seem insignificant, but when larger programs involving concurrency are considered, they become more pronounced. Sequential procedures that only transform data, without other side effects, can be described with a Hoare-style assertion, which is simply a relation be- tween the state before the procedure and the state after (Hoare 1969). This relation can then be verified using a set of rules, a process called deductive ver- ification. However, for many concurrent systems it is also of interest to verify temporal properties, such as safety properties (asserting bad things never hap- pen) and liveness properties (asserting good things eventually happen) (Lam- port 1994). Verifying such properties requires reasoning about time, using temporal logic. One framework for modelling programs in temporal logic is TLA, the Tem- poral Logic of Actions (Lamport 1994), where programs are represented by logical formulas. PlusCal is a language that allows smaller concurrent pro- grams to be automatically translated to TLA, after which they can be checked by existing TLA verification tools (Lamport 2009). However, this does not scale for large concurrent applications: Lamport states that PlusCal would be

1 2 CHAPTER 1. INTRODUCTION

unlikely to work well for of over one or two thousand lines. In- stead, TLA typically works on a higher level of abstraction, containing as few unessential implementation details as possible.

1.1 Research Problem

In order to verify large programs using TLA, it is only feasible to verify the abstract algorithm, which might hide faults in the implementation. The pur- pose of this thesis is to present an approach that could be used to verify such large programs without ignoring implementation details, by combining TLA with deductive verification using Hoare-style assertions. This aims to provide a clear connection between the abstract TLA model and the concrete imple- mentation, in a way that is not possible using only TLA. While the approach should be applicable to any program, in this thesis, the scope is limited to simple, sequential C programs.

1.2 Thesis Organization

First, Chapter 2 explains the concepts of deductive verification, model check- ing and the temporal logic TLA, and their relationship. It also describes exist- ing tools for automatic verification in these that makes them practical. Chapter 3 then restates the problem addressed by the thesis and presents the ap- proach proposed for solving it. The abstract approach proposed is described in detail, and a workflow for implementing it on C programs using specific tools is proposed. Chapter 4 implements the proposed workflow for a repre- sentative example program. Chapter 5 discusses the strengths and weaknesses of the proposed approach, and relates this to other work and suggestions for future work. Finally, Chapter 6 presents the conclusion of the thesis. Chapter 2

Background

When verifying correctness properties of systems, two principal approaches are algorithmic verification and deductive verification. Deductive verification relies on using a set of rules to formulate proofs about the system. Algorithmic verification, or model checking, is based on constructing a model of the system to be verified, which can then be used to (computationally) check properties of interest. This has the advantage of requiring less input from the programmer at the cost of generality and computational efficiency (Pnueli and Shahar 1996). This chapter describes deductive verification and model checking to the reader and then proceeds to introduce the main background matter, TLA. It also touches upon the tools that are used when working with program verifi- cation.

2.1 Deductive Verification

This section gives an overview of Hoare logic (Hoare 1969), a formal sys- tem for deductive verification, and Frama-C (Kirchner et al. 2015), a tool for automating deductive verification proofs.

2.1.1 Hoare Logic When it comes to procedures that can be fully specified by how they transform data, in the form of a relation between the state of the involved variables before and after the execution of the procedure, Hoare logic is sufficient for verifica- tion. In Hoare logic one specifies a program, or part of a program (referred to as a block) in terms of an assertion. An assertion is a triple {P}φ{Q} where P and Q are predicates, and φ is a code block (H. R. Nielson and F. Nielson

3 4 CHAPTER 2. BACKGROUND

2007). P is known as the precondition and Q as the postcondition. The triple asserts that if the code φ is run in a state where P holds, and if φ terminates, then it will produce a state where Q holds. The predicates P and Q are com- monly used to specify how the values of variables should change when running the code block. A simple example of an assertion is {x = n ∧ x > 0}φ{y = n!}, speci- fying that running φ in a state where x is positive and has value n computes n! and assigns the result to y. In this case, n is an auxiliary or logic variable, used to relate the new value of y with the old value of x. Note that the post- condition y = x! may not achieve the intended effect, as it refers to the value of x after the execution of φ. A program that sets both x and y to 1 would always fulfill this postcondition. After specifying a program using an assertion, the assertion must be proved to be correct. Hoare logic includes a set of axiomatic rules that are used de- ductively to prove that a program fulfills its assertion, a property known as partial correctness. If only partial correctness is shown, one can not be sure that the program will terminate. If termination is also proved (whenever the precondition is fulfilled), it is known as total correctness. Figure 2.1 shows a summary of the rules. For a detailed explanation and proofs of soundness and completeness, see H. R. Nielson and F. Nielson (2007).

Figure 2.1: The rules for deductive verification using Hoare logic (H. R. Niel- son and F. Nielson 2007, p. 215) CHAPTER 2. BACKGROUND 5

2.1.2 Frama-C Using proof-systems by hand to perform deductive verification is both inef- ficient and prone to error. To combat this issue, a lot of work has been put into the automatization of deductive verification, and there exists many tools for precisely that purpose. Frama-C (Kirchner et al. 2015) is one such tool, designed for analyzing C-programs. Frama-C consists of a set of plug-ins with various purposes. To perform automatic deductive verification, it uses the plug-in WP, named after the Weakest Precondition calculus (Kirchner et al. 2015, Section 5). WP depends on ACSL annotations provided by the pro- grammer. ACSL stands for ANSI/ISO C Specification Language and is used to write formal specifications (contracts) for procedures or blocks in C in first- order logic (Baudin et al. 2008). The contract is analogous to an assertion and states the precondition, annotated by requires, as well as the postcondition, annotated by ensures. Additionally, there is the annotation assigns that spec- ifies which memory locations that might be modifed in the block. WP also requires annotations for each loop. This includes the aforementioned assigns as well as an . In order to prove termination a variant that is a strictly decreasing metric bounded by 0 is needed. An example of an ACSL specification of a procedure is given in Figure 2.2. The contract in the figure is a function contract, applying to the entire swap function following it. There are also statement contracts (which we also refer to as block contracts), which apply only to the block immediately following the contract.

1 /*@ requires\valid(t+i)&&\valid(t+j); 2 @ assignst[i],t[j]; 3 @ ensurest[i] ==\old(t[j])&&t[j] ==\old(t[i]); 4 @*/ 5 void swap(color t[], int i, int j) { 6 int tmp = t[i]; 7 t[i] = t[j]; 8 t[j] = tmp; 9 } 10

Figure 2.2: Example of an ACSL annotation (Baudin et al. 2008, p. 74)

Provided sufficient annotations WP can prove both partial and total cor- rectness. It does this by first generating verification conditions, which are a set of proof obligations that when proven verifies the function. These are then first simplified using an internal tool Qed, after which they are sent to exter- nal provers, which will attempt to prove every obligation. If successful, the 6 CHAPTER 2. BACKGROUND

function has been verified.

2.2 Model Checking

A significant drawback of Hoare logic and similar formalisms is that they re- quire a relatively large amount of work, and sometimes ingenuity, from the person performing the verification (Emerson 2008). In addition, deductive verification with Hoare logic can only prove assertions about how a program transforms data. Systems featuring more subtle behavioral properties, such as temporal safety and liveness properties, require a different method. For instance, in a web server reacting to incoming requests from clients or a mi- croprocessor continually executing instruction upon instruction, it is not of interest to prove properties like termination or that some output is produced correctly. Instead, we would like to verify properties indicating that the system behaves correctly, such as “whenever a request is received, it is also served”. This kind of property requires a different kind of logic than the one used in Hoare-style assertions. Linear Time Logic (LTL), is one such logic, which uses operators such as F (sometimes) and G (always) to express properties concerning temporal relations in a program. TLA, presented in the next sec- tion, is another. While these logics can be used for deductive verification, they have also facilitated the development of verification by model checking. By ex- pressing programs and specifications precisely using temporal logic, one can extract a model of the program’s states and mechanically check that the spec- ification holds over all these states (Emerson 2008). While somewhat more computationally expensive and less plausible for models with a large or infi- nite amount of states, this approach requires less human input, making it more practical in many cases. Chen, Dean, and Wagner (2004) provide an example of this, by successfully using model checking to verify safety properties (and to find violations indicating bugs) in industrial C packages containing a total of roughly a million lines of code. Nevertheless, model checking has an in- herent problem in that state models tend to grow exponentially in relation to the program model itself; the only known method to mitigate this problem is abstraction (Emerson 2008).

2.3 The Temporal Logic of Actions

The Temporal Logic of Actions (TLA), introduced by Lamport (1994), is a formalism for describing abstract systems and algorithms and proving tem- CHAPTER 2. BACKGROUND 7

poral properties using a simple logical framework. In TLA, both programs and their properties are described with logical formulas, using common op- erators such as ¬, ∧ and the temporal operator 2. Some defining features of TLA as compared to similar formalisms is that it is intended to be as simple as possible to facilitate mathematical reasoning, that it is not based on a specific class of program texts (instead modelling the abstract algorithm as a logical formula) and that it allows for abstraction, where less abstract specifications can be proved to implement more abstract ones. First, consider the abstract algorithm of Figure 2.3. The algorithm features two concurrent loops indefinitely incrementing two variables x and y, using the lock sem for . P and V are lock and unlock operations re- spectively, and labels α, β and γ denote the “control points” where the control of the concurrent processes may be at any given moment.

Figure 2.3: A simple concurrent algorithm (Lamport 1994, p. 24)

Figure 2.4 shows how the algorithm may be described by a TLA specifi- cation. The formula

∆ Ψ = Init ∧ 2[N ]w ∧ SFw (N1) ∧ SFw (N2) is the logical formula representing the algorithm, and the other formulas are smaller parts which Ψ is defined in terms of (it would have been possible, but less readable, to define Ψ as one big formula). Here, =∆ denotes equality by definition. The symbol 2 is read always, signifying that the following predi- cate must always hold. The specification is a logical formula describing how a valid execution of the algorithm may look in terms of its sequence of states: it must fulfill an initial condition given by Init, each state transition must be a valid one as given 8 CHAPTER 2. BACKGROUND

∆ Init = ∧ (pc1 = “a”) ∧ (pc2 = “a”) ∧ (x = 0) ∧ (y = 0) ∧ sem = 1 ∆ ∆ α1 = ∧ (pc1 = “a”) ∧ (0 < sem) α2 = ∧ (pc2 = “a”) ∧ (0 < sem) 0 0 ∧ pc1 = “b” ∧ pc2 = “b” ∧ sem0 = sem − 1 ∧ sem0 = sem − 1 ∧ Unchanged hx, y, pc2i ∧ Unchanged hx, y, pc1i ∆ ∆ β1 = ∧ pc1 = “b” β2 = ∧ pc2 = “b” 0 0 ∧ pc1 = “g” ∧ pc2 = “g” ∧ x 0 = x + 1 ∧ y0 = y + 1 ∧ Unchanged hy, sem, pc2i ∧ Unchanged hx, sem, pc1i ∆ ∆ γ1 = ∧ pc1 = “g” γ2 = ∧ pc2 = “g” 0 0 ∧ pc1 = “a” ∧ pc2 = “a” ∧ sem0 = sem + 1 ∧ sem0 = sem + 1 ∧ Unchanged hx, y, pc2i ∧ Unchanged hx, y, pc1i ∆ ∆ N1 = α1 ∨ β1 ∨ γ1 N2 = α2 ∨ β2 ∨ γ2 ∆ N = N1 ∨ N2 ∆ w = hx, y, sem, pc1, pc2i ∆ Ψ = Init ∧ 2[N ]w ∧ SF(N1) ∧ SF(N2)

Figure 2.4: A TLA specification (Lamport 1994, p. 25) by the next-state relation N and it must fulfill some fairness conditions given by SFw (N1) ∧ SFw (N2). Roughly speaking, the fairness conditions specify that the program must eventually take each possible path during the execution; an execution of the algorithm where only x is incremented infinitely many times would not fulfill the specification. The variable pc is included to track the control state of the program, as- suming values “a”, “b” or “c” depending on whether a process is at control point α, β or γ. The translation of program code to TLA is further discussed in Section 2.4.1. In TLA an algorithm is not defined by how it manipulates state, but rather by all the sequences of states, known as behaviors, that it can induce. This is done by specifying initial values of all variables using an initial condition, and state transitions using a next-state relation. Therefore, a TLA specification can always be written on the form Init ∧ 2[N ]f ∧ F , where Init is some initial condition, N is the next-state relation, f is the tuple of variables used and F specifies some fairness conditions. The formula can be true or false for a specific behavior, and is true exactly for those behaviors which represent a possible execution of the algorithm. Init is usually a simple predicate on the form x = a ∧ y = b ∧ ..., giving CHAPTER 2. BACKGROUND 9

initial values for all variables used in the algorithm. N on the other hand is an action which is usually more complex. An action is a relation which may or may not hold between a pair of consecutive states. The action is a predicate containing both regular and primed variables, where primed variables repre- sent variables in the new state and unprimed ones represent variables in the old state. For instance, the action sem0 = sem − 1 holds true for any pair of consecutive states s1 and s2 where the value of sem in s2 is one less than the value of sem in s1. N is typically the disjunction of multiple smaller actions, representing different steps the algorithm can take. The operator 2 in 2[N ]f indicates that every consecutive pair of states must be related by the action N for a behavior to be part of the specification. TLA formulas are not only used to specify systems, but also to specify their properties. For instance, the formula T =∆ x ∈ Nat expresses the property that x is a natural number. If we can show that a specification F implies 2T , then x is always a natural number in any behavior which is a valid execution of F . That is, F fulfills the property “x is always a natural number”. This is a simple type correctness invariant, but more involved properties can also be proved. In general, to check that a specification F implements the property G, it is sufficient to verify the validity of the formula F ⇒ G. Lamport has developed a sound and relatively complete proof system which can be used to verify such formulas (Lamport 1994, Section 5.6). In fact, a program specification can also be viewed as a property: the prop- erty requiring the program to behave in accordance with the algorithm. This allows a notion of abstraction in TLA specifications: the formula F ⇒ G, representing that F has the property G, may also be interpreted as “G is an abstract specification which is implemented by the more detailed specifica- tion F ”. An important feature of TLA enabling this are so-called stuttering steps, the interested reader is referred to Lamport (1994) for details. Finally, it is interesting to note how TLA can represent several concurrent processes executing independently: if F and G are specifications, then any behavior ful- filling F ∧ G must fulfill both F and G, or in other words, must represent an execution of both F and G. That is, the formula F ∧ G specifies a program which runs F and G as two concurrent processes.

2.4TLA +

Just as with other program verification, the practical value of TLA is provided by the fact that it can be automated and used to mechanically check proofs and properties of programs. TLA+ is a syntactic extension to TLA which uses 10 CHAPTER 2. BACKGROUND

mathematical notation to model . The language is untyped, and uses Zermelo-Fraenkel set theory to represent data structures. TLA+ has an IDE called TLA+ Toolbox with multiple tools for writing and checking models in TLA+, see Kuppe, Lamport, and Ricketts (2019). The tools that will be dis- cussed in this section are the model checker TLC, the TLA+ proof system (TLAPS), and the programming-like language PlusCal. TLC is used to check properties of a finite-state model of a TLA+ speci- fication. TLC generates a model automatically given a specification, and we will sometimes refer to TLA+ models and specifications synonymously. The primary purpose of TLC is checking invariance properties. Although not as common and not as optimized, it is also possible to check general temporal properties expressed in TLA+. As with all model checkers, it is generally not possible to check a specification with an infinite state space using TLC. In- stead, the state space has to be restricted to a finite one, which can be done in multiple ways, for example by setting bounds and assigning values to param- eters (Yu, Manolios, and Lamport 1999). Consequently, TLC can never prove that a property holds for an infinite state space. However, one can become more confident by checking a specifi- cation against multiple different TLC models with different restrictions. In contrast to TLC, TLAPS can be used to prove temporal properties of a TLA+ specification with infinitely many reachable states, by using deductive reasoning (Cousineau et al. 2012). TLAPS mechanically verifies proofs writ- ten by the user. The user can leave out parts of the proof, which will result in proof obligations that will be sent to back-end provers that will try to prove them automatically. If they fail, the user may have to decompose a step into multiple smaller steps, each of which is easier to prove.

2.4.1 PlusCal While TLA may seem disconnected from traditional programming languages, PlusCal shows that this is not the case. PlusCal (Lamport 2009) is a lan- guage for describing both sequential and concurrent algorithms, that can be automatically transpiled to TLA+. As such, PlusCal offers an alternative to writing specifications directly in TLA+ which is intended to be more practical and familiar to programmers. This allows algorithms written in PlusCal to be checked by TLC, and reasoned about using the logic of TLA, which can then also be formally verified by TLAPS. PlusCal is syntactically similar to Pascal or C and contains the usual im- perative program constructs, such as variable declaration, if-statements and CHAPTER 2. BACKGROUND 11

while-loops, as well as procedures. In addition, it also allows more powerful mathematical statements using predicate logic, sets and functions. PlusCal is ideal for describing concurrent algorithms, as it has simple constructs for ex- pressing nondeterminism and the atomicity of instructions. An atomic block can be indicated using a label, indicating that the code in the block should be regarded as one atomic instruction. This means that for instance, a variable can only be assigned once under the same label. Labels can either be given explicitly or inferred by the transpiler. An example of an algorithm written in PlusCal is presented in Figure 2.5.

Figure 2.5: A PlusCal implementation of Euclid’s algorithm (Lamport 2009, p. 17)

Figure 2.6 shows the TLA specification of the same algorithm as produced by the PlusCal transpiler. The program counter is explicitly included as a vari- able called pc in the TLA specification. The specification is mostly straight- forward, with each label in the PlusCal code corresponding to an action. Each such action must include values for pc and pc0 to control the order of the in- structions, and an unchanged predicate to disallow program executions which change the values of variables which should not change according to the al- gorithm. This means that each label must be explicitly included in the TLA specification along with a certain amount of overhead. So while the translation is straightforward, it does not scale well to large programs.

2.5 Combining Approaches

In order to make practical use of systems for temporal reasoning it is of interest to make them scalable. Soleimanifard and Gurov (2016) indicate that Hoare- style assertions can be used for this purpose. Modern software is composed of a number of smaller components, some 12 CHAPTER 2. BACKGROUND

Figure 2.6: A TLA translation of the algorithm in Figure 2.5 (Lamport 2009, p. 18) of which may often be replaced. When verifying the correctness of a program whose components changed, it is desirable to only have to verify the replaced components, instead of having to verify the whole program anew. Soleiman- ifard and Gurov (2016) propose a model for verifying temporal properties of programs with a high degree of code variability, by utilizing local specifica- tions for variable components and verifying global correctness properties rel- ative to these specifications. To verify properties, the authors use a framework based on flow graphs representing the control flow structure of a program. A flow graph is a finite collection of method graphs, each method graph representing a procedure of the program. Each edge in the method graph is an action, an operation of the program related to the properties we are interested in checking. For instance, if we are interested in memory correctness, we might specify that all heap management procedures are actions. Any operation which is not an action is not directly represented in the method graph. Instead, nodes in the graph CHAPTER 2. BACKGROUND 13

are tagged with Hoare-style assertions, specifying how data is transformed be- tween actions (Soleimanifard and Gurov 2016). This distinction between ob- servable and unobservable operations allows the programmer to freely choose the level of abstraction of the model. Procedures corresponding to variable components of the program are rep- resented by so called maximal flow graphs. For a certain property (as given by a local specification), the maximal flow graph represents the structure of any program which satisfies the property. While the computation of maxi- mal flow graphs can be costly, the fact that only the observable actions are directly included in the graph makes the cost feasible even for larger programs (Soleimanifard and Gurov 2016). Chapter 3

Method

This chapter restates the problem introduced in Section 1.1 and presents the approach proposed for solving it. First, an abstract approach is described in detail. Then, a workflow for implementing it on C programs using specific tools is proposed.

3.1 Problem Statement

TLA is an elegant and practical formalism for the specification and verifica- tion of abstract system designs. However, when applied to program code, the approach results in overly verbose TLA descriptions of the code. This presents a problem, as an abstract representation of a program may hide potentially im- portant implementation details. If the TLA specification is constructed in an ad-hoc way, there is no guarantee that it properly represents the system to be verified. One way to ensure a one-to-one correspondence could be to mechanically translate the program code to a specification (in the same way that PlusCal is translated to TLA). However, as the example in Section 2.4.1 shows, a faithful TLA specification of a procedural program needs to encode different program states and control flow mechanisms explicitly using logical formulas, causing the size of the model to grow faster than the size of the program it models. This is problematic, as it means that mechanical translations do not scale well to large systems. It is clear that the bigger the TLA specification becomes, the harder it is to maintain and reason about, diminishing the meaningfulness of having a formal specification. The problem is twofold: to create a model that is representative but still scales well for larger programs. In this thesis we propose a solution inspired

14 CHAPTER 3. METHOD 15

by Soleimanifard and Gurov (2016) which aims to address both of these prob- lems.

3.2 Approach

In this section we present our approach to the problem stated above.

3.2.1 General Approach When considering a certain set of temporal properties, not every part of a pro- gram is equally interesting. Large programs written in procedural languages will typically feature blocks of code that only perform basic data transforma- tions and are uninteresting when verifying these properties. In this case, these blocks can be abstracted away and regarded as atomic operations for the pur- poses of the TLA translation. The idea we propose is to substitute atomic blocks of code by their con- tracts and verify each block separately using deductive verification. The result, an outline featuring a mix of program code and contracts, can then be trans- lated to TLA, thus decreasing the size of the specification while maintaining a clear correspondence with the code. Soleimanifard and Gurov (2016) make the distinction between ’actions’, which are code statements included in the model, and other statements, which are abstracted away. We do not explicitly identify actions in this sense, instead we rely on the programmer to provide an ad-hoc classification of which blocks can be viewed as atomic. Contracts representing blocks of code can be translated to TLA using a simple encoding. The pre- and postconditions of Hoare logic are essentially conditions on the state of execution before and after the block of code is run. As TLA actions also correspond to relations between states, it is possible to create an action corresponding to precisely those state transitions which fulfill a certain contract. In principle, it suffices to conjoin the precondition with the primed postcondition, as primed variables correspond to variables in the new state. However, as auxiliary variables are not part of TLA, some care has to be given to write the action in terms of only primed and unprimed program variables.

3.2.2 Instantiation To illustrate the approach, we instantiate it on programs written in C. We use ACSL and Frama-C to write and verify contracts for atomic blocks and to pro- 16 CHAPTER 3. METHOD

duce an outline which can be translated to TLA+. This outline is first manually translated to PlusCal, and then automatically translated to TLA+ In order to translate contracts to TLA+ actions, we have to perform some extra work. This is because it is not possible to embed ACSL contracts or TLA+ actions directly in PlusCal code. Instead, we insert labeled skip-statements as placeholders, which are translated by the TLA+ Toolbox to empty actions. It is then straightforward to translate the ACSL contracts to TLA+ and add them to these actions. In summary, to construct a representative model of a given C program, the following steps are performed.

(1) Identify blocks which can be considered as atomic.

(2) Write ACSL contracts representing the data transformation performed by each block.

(3) Use Frama-C to mechanically verify that each block adheres to its contract.

(4) Translate the C code to PlusCal, substituting each atomic block for a la- beled skip statement representing the block.

(5) Translate the PlusCal code to TLA+ automatically.

(6) Translate ACSL contracts to TLA+ and add them to the corresponding actions in the specification generated in the previous step.

The method is applied to a simple but representative example to illustrate the main principles of the approach.

3.2.3 Motivation In this section, we motivate the choices made in the instantiation of our ap- proach. The scope of this thesis is to present and illustrate the core idea of combining TLA+ with deductive reasoning. While we aim to envision how a complete toolchain may look in the future, we will not provide such a toolchain ourselves. The TLA+ Toolbox already provides automatic translation from PlusCal, which is imperative and similar to C, to TLA+ specifications. For this reason, we choose not to perform our own translation from C to TLA+; instead, we perform a manual translation to PlusCal and let TLA+ Toolbox perform the rest of the work. The reader may note that some steps of the approach are ad-hoc. These are mainly the identification of atomic blocks, and the translation from C to CHAPTER 3. METHOD 17

PlusCal. The way in which atomic blocks are identified does not change the following steps, and so could easily be replaced by a more systematic approach. The translation from C to PlusCal is mainly a question of transpiling, which is not the focus of this paper. The choice of using C for illustrating the approach has multiple reasons. C is a language familiar to most programmers prominently used in safety-critical embedded systems. It is also known to be error-prone and has long been in the focus of program verification research. In addition, deductive verification tools such as Frama-C are available for the language, which is essential when discussing possible automatization of the approach. While the verification of contracts could in principle be performed by any suitable tool for deductive verification, even TLA itself with TLAPS, Frama-C is a simple alternative specialized for C which fits our needs. The tools used, Frama-C and TLA+ Toolbox, are both offered as free soft- ware which is an advantage from a resource standpoint as well as for openness and reproducibility. Of course, the fact that the Toolbox offers automatic tran- spilation from PlusCal to TLA+ is a big enabling factor for implementing our approach. Chapter 4

Results

In this section, we instantiate the approach described in the previous section on example programs written in C.

4.1 A Server Example

First, consider the code of Figure 4.1. The program is a simple toy example modelling a server receiving and handling requests. The server continually waits for new requests, handling them as they arrive. The function server performs the main logic, looping indefinitely and running the function get request to try to receive a new request. If a request was received, the variable request is set to 1, in which case the server calls the function handle request. get request and handle request are very simple, but one can easily imagine a more com- plex implementation following the same structure and fulfilling essentially the same contracts. While the example is simple, the kind of reactive behavior it exhibits makes it interesting to study. For instance, we might be interested in showing that whenever a request is received, it is also eventually handled. This can be ex- pressed by the TLA property received ; handled, where received and handled are predicates expressing that the current request was received or handled, respectively. The formula F ; G reads “F leads to G” and is a shorthand for 2(F ⇒ ♦G), where ♦ is the temporal operator eventually. In this case, the variable request represents the status of the current request, so the simple formula request = 1 ; request = 0 expresses the de- sired property. We will now put to use the approach presented in the previous chapter, by showing that the program satisfies this property.

18 CHAPTER 4. RESULTS 19

1 #include 2 #include 3 4 int request = 0; 5 6 void get_request() { 7 int go = random() % 2; 8 if(go == 1) { 9 request = 1; 10 // Got request 11 } 12 } 13 14 void handle_request() { 15 request = 0; 16 // Handled request 17 } 18 19 void server() { 20 while (1) { 21 get_request(); 22 if (request != 0) { 23 handle_request(); 24 } 25 } 26 } 27 28 int main() { 29 server(); 30 return 0; 31 }

Figure 4.1: A simple example C program

The first step of the approach is to identify atomic blocks. Here, it is of interest to abstract away as many implementation details as possible. For the purposes of this example, we regard get request and handle request as atomic. While there is not much to abstract away in this case, an actual implementation could be more complex, while having essentially the same functionality. Note that while we chose to abstract whole function calls here, it is possible to select blocks of arbitrary size within a function to regard as atomic. The next task is to write the ACSL annotations. These reflect the result of a block without concern of how it was reached, and should represent the essential functionality of the block. In this case, handle request handles the request and sets request to 0. It is not of interest to us how it handles the request, only that it gets the job done and updates request in the end. We also require that there is a request that can be handled (that request = 1) before the function is called. Similarly, for get request, we are only 20 CHAPTER 4. RESULTS

concerned with the fact that it either gets a request or not, what determines this fact is not of interest. We also require that the previous request has been handled (that request = 0) before calling the get request function. Figure 4.2 shows the functions equipped with ACSL contracts expressing these properties.

1 /*@ requires request == 0; 2 @ ensures request ==1 || request ==\old(request); 3 @ assigns request; 4 @*/ 5 void get_request() { 6 int go = random() % 2; 7 if(go == 1) { 8 request = 1; 9 // Got request 10 } 11 } 12 13 /*@ requires request == 1; 14 @ ensures request == 0; 15 @ assigns request; 16 @*/ 17 void handle_request() { 18 request = 0; 19 // Handled request 20 }

Figure 4.2: The functions get request and handle request with ACSL contracts

The third step is to verify all the contracts using the Frama-C plugin WP. By default, WP, in addition to verifying the individual contracts, tries to prove that preconditions are always fulfilled before the respective block starts. How- ever, we only want to verify the assertion that given the precondition the block will give the specified postcondition. This is achieved by running "frama-c -wp -wp-prop="-@requires" program.c", where program.c is the file containing the code along with ACSL annotations. Run on the code in Figure 4.2, WP successfully verifies that the procedures implements their contracts. The next step is translating the code to PlusCal. As stated in Chapter 3, this was done manually. However, using PlusCal’s C syntax, the translation is almost one-to-one. Figure 4.3 shows the translated code. As the function get request was identified as an atomic block, it has been replaced by the label Get request. Since we want to replace the block with a contract in the final translation, the PlusCal block only contains the line skip;. In addition to the Get request label, the If label is added to mark the start of the next CHAPTER 4. RESULTS 21

action. The same procedure is performed for handle request. However, as the call to handle request is at the end of a block, no additional label is added. We add the flag “-label” to the PlusCal call arguments to allow us to explicitly label these actions, while the rest of the labels are inferred.

1 (* --algorithm server { 2 variables request = 0; 3 4 procedure server() 5 { 6 while (TRUE) { 7 Get_request: 8 skip; 9 If: 10 if (request /= 0) { 11 Handle_request: 12 skip; 13 }; 14 }; 15 }; 16 17 { 18 call server(); 19 } 20 }; *)

Figure 4.3: The PlusCal translation

Using TLA+ Toolbox, the code in Figure 4.3 can be automatically tran- spiled to TLA+. In the interest of space, the full TLA+ version is presented in Appendix A. Here, we present only the atomic blocks, as they are of the most interest to the approach. Having transpiled the code, the final step is to insert the contracts into the empty actions generated by the labeled skip-statements. In this case, there are two atomic blocks. We focus our attention on Get request in particular, Handle request is very similar. Figure 4.4 shows the empty action generated for Get request.

Get request =∆ ∧ pc = “Get request” ∧ true ∧ pc0 = “If” ∧ unchanged hrequest, stacki

Figure 4.4: The Get request block in TLA+, without contract

The empty action specifies that Get request corresponds to a state transition where the pc variable changes from “Get request” to “If”, keep- ing the other variables unchanged. Figure 4.5 shows the block equipped with 22 CHAPTER 4. RESULTS

a translated version of the ACSL contract. The contract uses primed and un- primed variables instead of \old to distinguish between states, but is other- wise a straightforward translation of its ACSL counterpart. It is also necessary to remove the variables that change from the unchanged predicate. The new version corresponds only to those state transitions fulfilling the contract of the get request procedure. Get request =∆ ∧ pc = “Get request” ∧ request = 0 ∧ (request 0 = 1 ∨ request 0 = 0) ∧ pc0 = “If” ∧ unchanged hstacki

Figure 4.5: The Get request block in TLA+, with contract

This concludes the translation from C to a complete TLA+ specification. However, in order to be able to verify the temporal property request = 1 ; request = 0, we must add weak fairness to prevent program behav- ior that stutters indefinitely. This is done simply by conjoining the predicate WFhpci(Next) to the specification. Running TLC to check the specified prop- erty now successfully verifies that it holds for our specification.

4.2 A Truck Example

In addition to the server example, we also implemented our approach on an- other slightly more complex example. The program models the behavior of a truck on a highway, and uses several variables to simulate the speed, gas consumption, and refuelling of the truck. We will discuss this example less extensively, as we have already illustrated the main principles of the approach. The C code and its resulting TLA+ specification are presented in Appendix A. In this case, the function run was identified as an atomic block. In this example, one property which is interesting to verify is the invariant 2(gasoline > k) for different values of k. The invariant signifies that the gasoline level of the truck never falls below a certain limit. Setting k to 0, we can verify that the truck never runs out of fuel, a property which is not immediately obvious when looking at the code. Using TLC on the generated model, this property is successfully verified. Running the C code and printing the value of the gasoline variable periodically, it seemed that the lowest fuel value assumed was 2375. However, with k = 2374, TLC failed to verify the property, showing that the variable CHAPTER 4. RESULTS 23

assumes the lower value 2025. This only occurs during the first iteration of the loop and was therefore easy to miss when only looking at the printed values. In this way, we were able to use TLC to find unexpected behavior in a corner case of execution. We initially experienced a problem with this example: after first translat- ing the C code to TLA, the model behavior did not match the behavior of the original program. The problem was solved after finding and correcting a mi- nor mistake in the manual translation of the ACSL contracts. This highlights the need for automatic translation, which will be further discussed in the next chapter. Chapter 5

Discussion

The results show that the abstract approach presented in Chapter 3 can be im- plemented relatively easily using existing tools. In particular, the translation of ACSL contracts to TLA, which is a big part of the approach, turned out to be trivial. Yet, there are several points to be discussed, especially regarding the practical use of the approach, seeing as the examples presented in Chapter 4 are toy examples rather than industrial code. As a first consideration, a limiting factor in the practicality of our approach as implemented on C programs is the reliance on manually written ACSL an- notations. While this is a compromise made to obtain representative models, it does require a larger effort as opposed to directly using TLA in an ad-hoc manner. When developing new programs the creation of these contracts could very well be integrated in the development process. This would mean slightly more work from the programmer, but could help prevent bugs by verifying blocks of code as soon as they are written. However, if one were to apply the same approach to verify existing programs of considerable size, it would be incredibly tedious, if not impossible, to write all the necessary ACSL contracts and verify them with WP. It should also be noted that the deductive verification step may not always be as straightforward as the examples indicate, as more complex code blocks may require more annotations to enable WP to prove that the contract is fulfilled. It also remains to see exactly how big of an impact the approach has on the size of the model produced. If the size of the code to be verified is very large, or if it turns out to be difficult to identify a substantial amount of atomic blocks in the code, the model size could turn out to be unmanagable even when applying our approach. The examples presented in this thesis are too simple to provide any indication on this, as they contain barely any details to abstract

24 CHAPTER 5. DISCUSSION 25

away. It is clear that if our approach is to be truly practical, there is more work to be done. Most notably, the manual translation steps would have to be auto- mated: both the translation of C code to TLA+ and the translation and insertion of ACSL contracts. As the truck example of Chapter 4 shows, it is difficult to prevent manual translation errors, even for small examples. Ideally, there would be a toolchain which could perform most steps of the approach in place of the user. The user would identify atomic blocks, write contracts for them and verify them with Frama-C. They would then use a tool which would automatically translate the C code equipped with contracts to a complete TLA+ model in a single step. The tool would first have to parse the ACSL contracts of the atomic blocks, and then translate the rest of the code, inserting the translated contracts where appropriate. As a first implementa- tion, there could be two tools: one tool translating ACSL contracts to TLA+ and one translating C code to PlusCal. However, this would still require the user to insert the contracts into the TLA+ specification, so having a single tool translating directly from C to TLA+ would be ideal. Although we have limited ourselves to sequential programs here, the method should be applicable to concurrent programs as well. However, this would surely require more thought into which blocks can safely be replaced by their contract.

5.1 Related Work

The main idea of this thesis, combining deductive verification with model checking, has been explored by a few other authors in recent years. As stated in the background, this thesis was inspired by Soleimanifard and Gurov (2016), who use a similar methodology in the setting of a flow graph-based model checker. A more recent paper by Oortwijn, Gurov, and Huisman (2020) com- bines deductive verification with a process calculus to verify properties of con- current programs. It shares our focus on producing a representative model, proving that the abstract model correctly describes the behaviour of the pro- gram. Thus, the work is similar in spirit to ours, although more ambitious. As stated above, it should be possible to use our methodology for concurrent verification as well, but that would require more work. The main contribution of our thesis as compared to the two articles above is to further illustrate the principle of enabling more scalable model checking by abstracting away blocks of code, and in particular to show how it can be performed in the context of TLA. 26 CHAPTER 5. DISCUSSION

Methni, Lemerre, et al. (2015) have written another highly related piece of work. In their paper, the authors implement a Frama-C plugin called C2TLA+ for automatic translation from C to TLA+. They use this tool to automatically translate roughly 600 lines of industrial C code to TLA+ and then verify prop- erties of the specification using TLC. Of particular interest to us is that they also produce a more abstract specification by hand and use TLA+ to verify that the translated specification is a refinement of the abstract one, thus proving that the abstract specification is representative. The authors also mention the possibility of translating ACSL contracts to TLA+ properties as future work. In a follow-up paper, the same authors implement a strategy for decreas- ing the state-space of generated models (Methni, Ben Hedia, et al. 2015). The main idea is to combine several TLA+ actions into one where possible, speci- fying a predicate which determines whether certain actions can be combined or not. This has the advantage of being systematic, but is also restrictive and does not allow for the broad sorts of abstraction possible using our approach. To our knowledge, no attempt has been made to translate large programs to TLA+ using C2TLA+. In our paper, we choose to use PlusCal in favor of C2TLA+ as the translations produced by PlusCal correspond very clearly to the original code, which allows us to easily insert the TLA+ translations of block contracts manually. When implementing a fully automated version of our approach, it would likely be suitable to refer to C2TLA+. Chapter 6

Conclusion

This thesis presents a general method for extracting scalable TLA models from program code. The main idea is to combine deductive verification with model checking to abstract away unnecessary details from the model while keep- ing a correspondence to the original code. This is done by replacing atomic blocks in the original code with contracts which are verified separately using deductive verification, producing an outline which can be translated to a more lightweight TLA model. The method is successfully instantiated on a simple C program, using Frama-C to perform deductive verification on blocks of code and performing translation to TLA+ via the PlusCal language. ACSL contracts are translated to TLA+ using a simple encoding. The complete specification is model checked using TLC to verify temporal properties. The results show promise, but to ensure the practicality of the approach, it would have to be applied to more complex industrial code samples. A central concern is the need for manually written contracts, which potentially require a lot of work from the verifier, in particular when verifying a large existing code base. Presently, parts of the method are performed manually, but excluding the process of writing contracts, it should be possible to automate it entirely. In addition, while our thesis only considers sequential programs, the method should be applicable to concurrent code as well. Thus, there is future work to be done which could further improve the generality and practicality of the method.

27 Bibliography

Hoare, C. A.R. (1969). “An Axiomatic Basis for Computer Programming”. In: Communications of the ACM 12.10, pp. 576–580. Lamport, Leslie (1994). “The Temporal Logic of Actions”. In: ACM Trans- actions on Programming Languages and Systems (TOPLAS) 16, pp. 872– 923. Lamport, Leslie (2009). “The PlusCal Algorithm Language”. In: Theoretical Aspects of Computing-ICTAC 2009 5684, pp. 36–60. Pnueli, Amir and Elad Shahar (1996). “A Platform for Combining Deduc- tive with Algorithmic Verification”. In: Computer Aided Verification. Ed. by Rajeev Alur and Thomas A. Henzinger. Berlin, Heidelberg: Springer Berlin Heidelberg, pp. 184–195. Kirchner, Florent et al. (2015). “Frama-C: A Software Analysis Perspective”. In: Formal Aspects of Computing 27, pp. 573–609. Nielson, Hanne Riis and Flemming Nielson (2007). Semantics with Applica- tions: An Appetizer. Undergraduate topics in computer science. London: Springer. Baudin, P et al. (2008). ACSL: ANSI/ISO C Specification Language. Version 1.4, retrieved May 2020. url: https : / / www . frama - c . com / download/acsl_1.4.pdf. Emerson, E. Allen (2008). “The Beginning of Model Checking: A Personal Perspective”. In: Lecture Notes in Computer Science 5000 LNCS, pp. 27– 45. Chen, Hao, Drew Dean, and David Wagner (2004). “Model Checking One Million Lines of C Code”. In: Proceedings of the 11th Annual Network and Distributed System Security Symposium (NDSS’04). Kuppe, Markus Alexander, , and Daniel Ricketts (2019). “The TLA+ Toolbox”. In: Electronic Proceedings in Theoretical Computer Sci- ence 310, pp. 50–62. url: http : / / dx . doi . org / 10 . 4204 / EPTCS.310.6.

28 BIBLIOGRAPHY 29

Yu, Yuan, Panagiotis Manolios, and Leslie Lamport (1999). “Model Check- ing TLA+ Specifications”. In: Correct Hardware Design and Verification Methods. CHARME 1999. Ed. by Laurence Pierre and Thomas Kropf, pp. 54–66. Cousineau, Denis et al. (2012). “TLA+ Proofs”. In: Proceedings of the 18th International Symposium on . FM 2012. Ed. by Dimitra Giannakopolou and Dominique Mery. Vol. 7436. Lecture Notes in Com- puter Science, pp. 147–154. Soleimanifard, Siavash and Dilian Gurov (2016). “Algorithmic Verification of Procedural Programs in the Presence of Code Variability”. In: Science of Computer Programming 127, pp. 76–102. Oortwijn, Wytse, Dilian Gurov, and Marieke Huisman (2020). “Practical Ab- stractions for Automated Verification of Shared-Memory Concurrency”. In: Verification, Model Checking, and Abstract . VMCAI 2020. Ed. by Dirk Beyer and Damien Zufferey, pp. 401–425. Methni, Amira, Matthieu Lemerre, et al. (2015). “Specifying and Verifying Concurrent C Programs with TLA+”. In: Communications in Computer and Information Science 476, pp. 206–222. Methni, Amira, Belgacem Ben Hedia, et al. (2015). “State Space Reduction Strategies for Model Checking Concurrent C Programs”. In: CEUR Work- shop Proceedings 1431, pp. 65–75. Acknowledgment

We would like to thank our supervisor Dilian, who not only proposed the cen- tral ideas for the thesis, but also provided continuous support during the whole project.

30 Appendix A

TLA+ Specifications

This chapter features the full TLA+ translations of the examples presented in Chapter 4.

A.1 The Server Example

First, the following is the specification produced by the main server example. module server extends Integers, Sequences variables request, pc, stack vars =∆ hrequest, pc, stacki

∆ Init = Global variables ∧ request = 0 ∧ stack = hi ∧ pc = “Lbl 2” Lbl 1 =∆ ∧ pc = “Lbl 1” ∧ pc0 = “Get request” ∧ unchanged hrequest, stacki Get request =∆ ∧ pc = “Get request” ∧ request = 0 ∧ (request 0 = 1 ∨ request 0 = 0) ∧ pc0 = “If” ∧ unchanged hstacki If =∆ ∧ pc = “If”

31 32 APPENDIX A. TLA+ SPECIFICATIONS

∧ if request 6= 0 then ∧ pc0 = “Handle request” else ∧ pc0 = “Lbl 1” ∧ unchanged hrequest, stacki Handle request =∆ ∧ pc = “Handle request” ∧ request = 1 ∧ request 0 = 0 ∧ pc0 = “Lbl 1” ∧ unchanged hstacki server =∆ Lbl 1 ∨ Get request ∨ If ∨ Handle request Lbl 2 =∆ ∧ pc = “Lbl 2” ∧ stack 0 = h[procedure 7→ “server”, pc 7→ “Done”]i ◦ stack ∧ pc0 = “Lbl 1” ∧ unchanged request

Allow infinite stuttering to prevent on termination. Terminating =∆ pc = “Done” ∧ unchanged vars Next =∆ server ∨ Lbl 2 ∨ Terminating

∆ Spec = Init ∧ 2[Next]vars ∧ WFhpci(Next) Termination =∆ 3(pc = “Done”) APPENDIX A. TLA+ SPECIFICATIONS 33

A.2 The Truck Example

Next, we present the TLA+ specification and corresponding C code of the truck example mentioned at the end of Chapter 4. module truck extends Integers, Sequences MAX SPEED LIMIT =∆ 70 CONSUMPTION =∆ 5 FUEL CAPACITY =∆ 8500 REFUEL INTERVAL =∆ 24

Max(x, y) =∆ if (x > y) then x else y Min(x, y) =∆ if (x > y) then y else x variables speed, gasoline, pc, stack, time to refuel vars =∆ hspeed, gasoline, pc, stack, time to refueli

∆ Init = Global variables ∧ speed = 0 ∧ gasoline = FUEL CAPACITY Procedure drive ∧ time to refuel = REFUEL INTERVAL ∧ stack = hi ∧ pc = “Lbl 3” Lbl 1 =∆ ∧ pc = “Lbl 1” ∧ pc0 = “Run” ∧ unchanged hspeed, gasoline, stack, time to refueli Run =∆ ∧ pc = “Run” ∧ speed ≤ MAX SPEED LIMIT ∧ speed 0 = Min(speed + 5, MAX SPEED LIMIT ) ∧ gasoline0 = Max(gasoline − speed 0 ∗ CONSUMPTION , 0) ∧ pc0 = “If” ∧ unchanged hstack, time to refueli If =∆ ∧ pc = “If” ∧ if time to refuel ≤ 0 then ∧ speed 0 = 0 ∧ gasoline0 = FUEL CAPACITY ∧ time to refuel 0 = REFUEL INTERVAL 34 APPENDIX A. TLA+ SPECIFICATIONS

else ∧ true ∧ unchanged hspeed, gasoline, time to refueli ∧ pc0 = “Lbl 2” ∧ stack 0 = stack Lbl 2 =∆ ∧ pc = “Lbl 2” ∧ time to refuel 0 = time to refuel − 1 ∧ pc0 = “Lbl 1” ∧ unchanged hspeed, gasoline, stacki drive =∆ Lbl 1 ∨ Run ∨ If ∨ Lbl 2 Lbl 3 =∆ ∧ pc = “Lbl 3” ∧ stack 0 = h[procedure 7→ “drive”, pc 7→ “Done”, time to refuel 7→ time to refuel]i ◦ stack ∧ time to refuel 0 = REFUEL INTERVAL ∧ pc0 = “Lbl 1” ∧ unchanged hspeed, gasolinei

Allow infinite stuttering to prevent deadlock on termination. Terminating =∆ pc = “Done” ∧ unchanged vars Next =∆ drive ∨ Lbl 3 ∨ Terminating

∆ Spec = Init ∧ 2[Next]vars Termination =∆ 3(pc = “Done”) APPENDIX A. TLA+ SPECIFICATIONS 35

1 #include 2 #define MAX_SPEED_LIMIT 70 3 #define CONSUMPTION_PER_MILE 5 4 #define FUEL_CAPACITY 8500 5 #define REFUEL_INTERVAL 24 6 7 int speed = 0; 8 int gasoline = FUEL_CAPACITY; 9 10 /*@ requires speed <= MAX_SPEED_LIMIT; 11 @ ensures speed ==\min(\old(speed)+ 5, MAX_SPEED_LIMIT); 12 @ ensures gasoline ==\max(\old(gasoline)- speed * CONSUMPTION_PER_MILE, 0); 13 @ assigns speed, gasoline; 14 @*/ 15 void run(){ 16 if (speed > MAX_SPEED_LIMIT) { 17 printf("Warning: Invalid speed encountered!"); 18 } else if (speed < MAX_SPEED_LIMIT - 5) { 19 speed += 5; 20 } else{ 21 speed = MAX_SPEED_LIMIT; 22 } 23 24 gasoline -= speed * CONSUMPTION_PER_MILE; 25 if (gasoline < 0) { 26 gasoline = 0; 27 } 28 } 29 30 void drive(){ 31 int time_to_refuel = REFUEL_INTERVAL; 32 while (1) { 33 run(); 34 if(time_to_refuel <= 0) { 35 speed = 0; 36 gasoline = FUEL_CAPACITY; 37 time_to_refuel = REFUEL_INTERVAL; 38 } 39 time_to_refuel -= 1; 40 } 41 } 42 43 int main() { 44 drive(); 45 return 0; 46 }

Figure A.1: The truck example

TRITA -EECS-EX-2020:395

www.kth.se