0% found this document useful (0 votes)
155 views

System Validation

A introductionary book about formal system validation and model checking. The topics included are FSM model building and checking using NuSMV, CTL and LTL logics; Java program verification using JML and related tools (OpenJML, JML/ESC, JML/RAC, etc..). The book begins with introducing the reader to first order logic (predicate logic) and propositional logic. Then continues on with FSM semantics and basic model building techniques using NuSMV. Higher order logics LTL and CTL are then introduced, to verify NuSMV models. A brief chapter about software abstraction into a model and verification is also included. The final chapters cover mostly JML, from semantics, to tools and techniques.
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
155 views

System Validation

A introductionary book about formal system validation and model checking. The topics included are FSM model building and checking using NuSMV, CTL and LTL logics; Java program verification using JML and related tools (OpenJML, JML/ESC, JML/RAC, etc..). The book begins with introducing the reader to first order logic (predicate logic) and propositional logic. Then continues on with FSM semantics and basic model building techniques using NuSMV. Higher order logics LTL and CTL are then introduced, to verify NuSMV models. A brief chapter about software abstraction into a model and verification is also included. The final chapters cover mostly JML, from semantics, to tools and techniques.
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 131

System Validation 192140122

Marieke Huisman University of Twente Formal Methods and Tools [email protected] 2012-2013

ii

Contents
1 Introduction 2 First-Order Logic and Set 2.1 Propositional Logic . . . 2.2 First-Order Logic . . . . 2.3 Set Theory . . . . . . . 2.4 Functions and Relations Theory . . . . . . . . . . . . . . . . . . . . 1 5 5 6 6 7 9 9 10 12 15 17 17 20 23 23 24 25 27 29 30 32 33

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

3 System Modelling 3.1 History and Background . . . . . . . . . . . . 3.2 Finite State Machines and Kripke Structures 3.3 Finite State Machines in NuSMV . . . . . . . 3.4 Composition of Finite State Machines . . . . 3.5 Using NuSMV . . . . . . . . . . . . . . . . . 3.6 Fairness . . . . . . . . . . . . . . . . . . . . . 3.7 Further Reading . . . . . . . . . . . . . . . . 4 Crash Course on Temporal Logic 4.1 History and Background . . . . . . 4.2 Safety versus Liveness Properties . 4.3 Kripke Structures . . . . . . . . . . 4.4 Linear Time Temporal Logic - LTL 4.4.1 Fairness as an LTL Formula 4.5 Computation Tree Logic - CTL . . 4.6 Comparison of LTL and CTL . . . 4.7 Further Reading . . . . . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

5 Software Model Checking 35 5.1 From Code to Model . . . . . . . . . . . . . . . . . . . . . . . 35 5.2 Challenges for Software Model Checking . . . . . . . . . . . . 35 iii

iv 5.3

CONTENTS Abstraction-Renement . . . . . . . . . . . . . . . . . . . . . 36 37 37 39 49 57 60 61 62 63 63 64 67 68 69 72 75 75 79 79 79 82 83 83 84 87 87 88 94 97 99

6 Crash Course on JML 6.1 History and Background . . . . . . . . . . . 6.2 JML Method Contracts . . . . . . . . . . . 6.3 JML Class Specications . . . . . . . . . . . 6.4 Specifying Exceptional Behaviour . . . . . . 6.5 Desugaring Multiple Method Specications 6.6 Inheritance of Method Specications . . . . 6.7 Other Features of JML . . . . . . . . . . . . 7 Run-time checking of JML Annotations 7.1 History and Background . . . . . . . . . . 7.2 Manually Validating Specications . . . . 7.3 Requirements for a Run-Time Checker . . 7.4 Executing a Run-Time Checker . . . . . . 7.5 Monitoring Safety and Security Properties 7.6 Further Reading . . . . . . . . . . . . . . 8 Abstract Specications 8.1 Model Variables . . . . . . . . . 8.2 Model Variables and Interfaces 8.3 On Spec Public Variables . . . 8.4 Model versus Ghost Variables . 8.5 Model Methods and Classes . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

9 Static Checking of JML Annotations 9.1 History and Background . . . . . . . . . . . . 9.2 A Quick Overview of Hoare Logic . . . . . . . 9.3 Mechanising Hoare Logic . . . . . . . . . . . 9.4 Automated Program Verication for Java . . 9.5 Reasoning about Method Calls . . . . . . . . 9.6 Statement Annotations - Helping the Verier 9.7 Termination . . . . . . . . . . . . . . . . . . . 9.8 Further Reading . . . . . . . . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

10 Test Case Generation from JML 101 10.1 From JML Specications to Unit Tests . . . . . . . . . . . . . 101 10.2 Test Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 10.3 Specications for Testing . . . . . . . . . . . . . . . . . . . . 107

CONTENTS 10.4 Improving Test Data 10.5 Tools . . . . . . . . . 10.5.1 JMLUnitNG 10.5.2 KeYTestGen 10.6 Further Reading . . 11 Bibliography Generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

v 109 112 112 114 114 117

vi

CONTENTS

Chapter 1

Introduction
These lecture notes describe the material for the Master course System Validation (191140122). It is not intended as a replacement for the lectures: instead it should act both as a quick reference and as a guide to further study the material discussed during the lecture. Besides these notes, on the System Validation Blackboard site several papers are mentioned as (optional) additional material for more in-depth information about the dierent topics discussed during the lectures.

Objectives of the Course


The purpose of the System Validation course is to make you acquainted with dierent formal tools and techniques that can help one to improve the quality of software applications. Ideally, if later in life you will be developing complex software, the experiences from the System Validation course will help you to choose adequate tool support to ensure the correct functioning of this software. After the course, you will be able to: describe the essential characteristics of the main formal techniques, and to describe in what situation what kind of tool is most appropriate; gather informal plain text requirements and transform these into a set of formal requirements write formal specications in dierent specication formalisms, and to choose the most appropriate formalism to write a specication; apply abstraction techniques to write specications that are not implementation-specic; 1

CHAPTER 1. INTRODUCTION predict what will happen when a validation tool is applied on a code fragment; interpret the meaning of error messages produced by dierent validation tools; and complete and correct specications, so that a validation tool will not report errors in the specication anymore.

Contents of the Course


The rst lecture gives an overview of the area of formal methods and system validation. It describes the dierent techniques that are used, and it discusses how these are complementing each other. It also briey discusses the notion of requirements, and how one can use this to establish a specication of a system. In the next lectures, some of the techniques are discussed in more detail, and dierent formal languages to specify required system properties are introduced. The rst half of this course (lectures 2 - 4, Chapters 3-5) discusses the use of model checking techniques. We rst describe how a system can be described formally as a model, essentially containing states and transitions between states. We then discuss temporal logic as a way to specify safety and liveness properties about such models. Concretely, both models and temporal logic formulas are given as input to the NuSMV model checker. We also sketch how such a model can be used to describe concrete program behaviour, and what the limitations of such an approach are when using general-purpose model checking. We then discuss a technique to overcome these limitations by constructing an appropriate abstract model of the software, such that the desired properties can be validated for this abstract model, instead of for the full code. In the second half of the course (lectures 5 - 8, Chapter 6-10), we study other formal validation techniques that require more work, but which can be used to validate arbitrary properties of software. We introduce the Java Modeling Language (JML) as a way to describe software behaviour. Then we discuss how run-time checking can be used as a form of extended testing, to validate the behaviour of the program during its execution. We also show how safety properties can be encoded and validated with JML. Next, we study techniques to write the specication in a more abstract way, not directly related to the code. We also discuss static verication techniques, with Hoare logic as their theoretical background, and we show how static

3 verication can be used to establish correctness of an implementation w.r.t. its specication without executing the code. The last lecture will discuss how JML specications also can be used to systematically generate unit test cases. This course aims to give a relatively complete overview of the area of formal methods and system validation. However, there are still many interesting topics in the area that are not covered during the course, e.g., automated static analysis, rst-order automated theorem proving, and interactive theorem proving. Feel free to contact the lecturer if you would like to know more about these.

Course Setup
Getting to know dierent tools and techniques cannot be done by attending lectures and reading the literature alone; one needs to use the dierent tools and techniques in order to get a feeling for what can be achieved with them. Therefore, this course is set up as a practical course. Besides the lectures, where the tools and techniques will be introduced, you, as a student, will be asked to use the tools yourself. To this end, there are weekly exercise sessions, where you can get help with the practical work. Exercises and their solutions will be published on Blackboard. The number of exercises is probably too much for one exercise session, you can either select a few, or solve them during self study hours. In addition, to obtain practical experience, you will have to hand in several homework assignments. One important aspect of this course is to write down requirements in a formal language. In order to get useful feedback from a tool, you need to tell it what exactly you want to know. During the course, you will become acquainted with dierent specication formalisms, and during the exercise sessions and homework assignments, you will also be encouraged to think about the appropriate formalism to write a certain property. All practical information about the course will be given via Blackboard. All exercises and homework assignments (including their deadlines) are also available via Blackboard.

About the Lecture Notes


Besides providing an overview of the course material, an additional aim of the lecture notes is to provide relevant context and background information. Therefore, several chapters start with a History and Background section, and

CHAPTER 1. INTRODUCTION

they end with a section with references to further reading material. These references are not part of the course material, but are intended as pointers for those who are interested in learning more about this area. Before the chapters covering the course material, the lecture notes start with a preliminary chapter, quickly recapitulating some essential notions of rst-order logic and set theory. These lecture notes are still relatively new. The second half (on JML) has been used since the study year 2010-2011; the rst half (on model checking) has been used since 2011-2012. Therefore, if you have any feedback (in particular, any errors and any points that are unclear), this will be highly appreciated. You can contact me during the lectures, or via email.

Acknowledgements
Many thanks go to Stefan Blom, Ronald Burgman, Roeland Kegel and Wolfgang Ahrendt who carefully read through earlier versions of these lecture notes, and provided useful feedback, references and graphical representations of Kripke structures. Enschede, August 2012 Marieke Huisman University of Twente [email protected]

Chapter 2

First-Order Logic and Set Theory


This chapter briey recapitulates the main ingredients of propositional logic, rst-order logic, set theory, and functions and relations. For more information, examples, and intuition, you should refer to an appropriate textbook.

2.1

Propositional Logic

Formulas in propositional logic consist of atomic propositions, and relations between these atomic propositions. Basically, an atomic proposition is a simple statement which can be either true or false. Relations between the atomic propositions build up the formulas, stating e.g., that both atomic propositions have to hold, one proposition implies the other etc. These relations are often called logical connectives. Denition 2.1 (Propositional Logic). Let p be an atomic proposition. A formula in propositional logic is constructed by the following grammar: ::= p | | The logical connective is typically called negation, while is called conjunction (or and ). A formula is true, if is false. A formula 1 2 is true if both 1 and 2 are true. Other logical connectives can be derived from these basic connectives. Well-known examples are disjunction (or or ), denoted , implication, de5

CHAPTER 2. FIRST-ORDER LOGIC AND SET THEORY

noted , and if-and-only-if, denoted . 1 2 1 2 1 2 = (1 2 ) def = 1 2 def = (1 2 ) (2 1 )


def

2.2

First-Order Logic

First-order logic, also known as predicate logic is an extension of propositional logic with predicates and quantications. A predicate is a function that given one or more parameters, returns a Boolean value (i.e., either true or false). Formulas in rst-order logic can range over all possible parameters of a predicate, allowing one to express for example that a predicate is true (holds) for all possible parameters, or that there exists a parameter for which the predicate is true. Formally, its syntax is based on propositional logic, with atomic propositions replaced by parametrised predicates, and extended with universal quantication : Denition 2.2 (First-Order Logic). Let P be a (parametrised) predicate, let x be a variable. A formula in rst-order logic is dened by the following grammar. ::= P (x1 , . . . , xn ) | | | x. A universally quantied formula x.P (x) holds if for all possible values of x, the predicate P returns true. Existential quantication can be derived from universal quantication: x. = (x.) Thus, x.P (x) holds if there is some value x for which P holds. In practice, one often wishes to provide a range for the variables over which is being quantied. As shortcuts, we introduce the following syntactical abbreviations: x.A(x).B (x) = x.A(x) B (x) def x.A(x).B (x) = x.A(x) B (x)
def def

2.3

Set Theory

A set is an unordered collection of objects. The elements of a set are often denoted as a sequence, surrounded by curly brackets, e.g., the set with elements 1 and 2 is written as {1, 2}.

2.4. FUNCTIONS AND RELATIONS

Given a set S , we write x S to denote that x is an element of the set S , i.e., x is a member of S . If all elements of a set S1 are also elements of a set S2 , then S1 is said to be a subset of S2 , denoted S1 S2 . Formally: S1 S2 = x S1 .x S2 The empty set is the set without any elements. The union of sets S1 and S2 , denoted S1 S2 is the set of all elements that are member of S1 , or S2 , or both. The intersection of sets S1 and S2 , denoted S1 S2 is the set of all elements that are member of S1 and S2 . The dierence of sets S1 and S2 , denoted S1 \S2 is the set of all elements that are member of S1 and not of S2 . Formally: x.x x.x S1 S2 x.x S1 S2 x.x S1 \S2 false x.x S1 x S2 x.x S1 x S2 x.x S1 x S2
def

The Cartesian product of two sets S1 and S2 , denotes S1 S2 , is a set of tuples, containing all possible combinations of members of S1 and S2 : S1 S2 = {(x, y ) | x S1 y S2 } The powerset of a set S is the set whose members are all possible subsets of S . For example, the power set of {1, 2} is {, {1}, {2}, {1, 2}}. Sometimes, the powerset is also denoted as 2S . For convenience, we sometimes write x S.P (x) to abbreviate x.x S.P (x), and x S.Q(x) to abbreviate x.x S.Q(x).

2.4

Functions and Relations

A function is a mapping from a set of elements into a (possibly dierent) set to elements. If a function f maps element in the set S1 to elements in the set S2 , we sometimes say that the type of f is from S1 to S2 , denoted f : S1 S2 . S1 is called the domain of this function, S2 the range. Sometimes, we use to denote this mapping per element. For example, given the set {1, 2, 3}, we can dene the constant mapping all elements to 1 as follows. 1 1, 2 1, 3 1

CHAPTER 2. FIRST-ORDER LOGIC AND SET THEORY

A function is partial if it is dened only for a subset of its domain, otherwise it is total. A relation R between two sets S1 and S2 is a subset of its Cartesian product S1 S2 , typically denoted as R S1 S2 . Given two elements x S1 and y S2 that are related by R, i.e., (x, y ) R, this is typically written using inx notation: x R y . A relation can also be viewed as a function mapping the Cartesian product S1 S2 to the set of booleans {true, false}. A relation is total if all elements in S1 and S2 are related. A relation R is left-total if for all elements in S1 there is a related element in S2 , i.e., x.x S1 .y S2 .x R y . A relation R is right-total if for all elements in S2 there is a related element in S1 , i.e., y.y S2 .x S1 .x R y .

Chapter 3

System Modelling
The rst formal validation technique that we will use is model checking. The basic idea of model checking is the following: (i) you dene a model that describes the behaviour of your system, (ii) you specify the properties that your system is supposed to have, and (iii) the tool analyses the state space of the model to check whether these properties are indeed satised.

3.1

History and Background

The idea of model checking was formulated by Ed Clarke and E. Allen Emerson, and independently by Jean-Pierre Queille and Joseph Sifakis in the beginning of the eighties. Clarke, Emerson and Sifakis received the Turing award for this idea in 2007. Model checking emerged initially as a technique to check properties over hardware: because of the relatively restricted nature of hardware, it was feasible to explore its whole state space. However, over time, model checking has been more and more targeting software as well (and besides, the borders between hardware and software are also becoming less and less clear). To explore the state space, dierent model checking algorithms have been developed (see e.g. [28] for an overview). Within the System Validation course, we do not discuss the implementation of these model checking algorithms, it is sucient to understand the main idea of what is model checking1 . Many dierent model checker tools are available, e.g., Spin [54], CADP [47], LTSmin [15]. In the System Validation course we use the model checker NuSMV [27]. It is a well-developed and well-maintained model
1 Model checking algorithms are discussed in the courses Principles of Model Checking, and Modelling and Analysis of Concurrent Systems 2.

10

CHAPTER 3. SYSTEM MODELLING

checker (with a long history). One of its important advantages is that it can check properties in dierent formal property languages (as will be described in the next chapter). This chapter discusses how a model to describe a system can be dened, the next chapter discusses how the intended properties can be described. We rst describe how the model can be described in an abstract way, introducing the formal notion of Kripke structures, and then we describe the concrete input format of NuSMV models. In practice, when using model checking to verify whether a property holds for a system, one quickly runs into the state space explosion problem: the state space of the system is so large that it cannot be analysed using a reasonable amount of time and memory. Moreover, in many cases the state space is innite, which makes it much harder to investigate all states. See the Further Reading section below for pointers to possible solutions of this problem.

3.2

Finite State Machines and Kripke Structures

A common way to proivde a high-level abstraction for a system is to describe it in the form of a nite state machine (FSM). This describes the system as a nite number of states and transitions between these states. The machine is in only one state at a time; the state it is in at any given time is called the current state. It can change from one state to another when initiated by a triggering event or condition, this is called a transition. A particular FSM is dened by a list of the possible transition states from each current state, and the triggering condition for each transition. Example 3.1. A simple example of a nite state machine is a system that describes a counter modulo 3. This could be modelled as a system with three states, named zero, one and two. Increment transitions make the system go from zero to one, from one to two, and from two to zero. States zero and two can be marked as even (see Figure 3.1). To dene FSMs formally, we introduce the notion of Kripke structures. This gives an abstract and completely formal denition. The rest of this chapter shows how such Kripke structures can be described in a concrete way, as NuSMV models. The next chapter discusses how properties over these models can be described in temporal logic, and how the meaning of these temporal logic formulae can be dened over the traces of a Kripke structure.

3.2. FINITE STATE MACHINES AND KRIPKE STRUCTURES

11

Denition 3.1 (Kripke Structure). A Kripke structure over a set of atomic propositions AP is a 4-tuple K = (S, I, , ) where S is a nite set of states; I S is the non-empty set of initial states; S S is a transition relation, such that is left-total, i.e., s S.s S.(s, s ) ; and : S AP is an interpretation (or labelling) function that maps each state to its set of valid atomic propositions. Typically, when states s1 , s2 S are related by , i.e., (s1 , s2 ) we write this as s1 s2 . Since is left-total, in every state there always is a transition enabled (i.e., there always is a step that can be made), and thus the system will not deadlock. Notice that the interpretation function denes when an atomic proposition holds, i.e, atomic proposition p AP is true in s S if and only if p (s). Example 3.2. Example 3.1 (the modulo 3 counter) would be described by the following Kripke structure, given that the set of atomic propositions AP = {even}. ( {zero, one, two}, {zero}, {(zero, one), (one, two), (two, zero)}, {zero {even}, one , two {even}} ) Graphically, it would be displayed as in Figure 3.1. When abstracting a system as a Kripke structure, the challenge is to nd the right level of abstraction: if there is too much detail, the model checking algorithms will not work on it anymore, because the state space gets too large; if there is not enough detail, the model checking algorithms might fail to nd problems in the system, because they have been abstracted away. Unfortunately, no formal guidelines can be given to avoid this problem; it simply requires experience. (S ) (I ) () ()

12

CHAPTER 3. SYSTEM MODELLING

even

zero

even

two

one

Figure 3.1: Graphical Representation of Kripke Structure for Modulo 3 Counter

3.3

Finite State Machines in NuSMV

Next, we give a short introduction to system modelling in the NuSMV input language. For full details, we refer to the NuSMV tutorial [23] and manual [22]. In NuSMV, a system is modelled as a collection of modules. Every module can be parametrised, to pass on information between dierent modules. Within a module, variables (of primitive type, or instances of other modules) can be dened (preceded by the keyword VAR). The primitive types can be e.g., boolean or int, but they can also be an integer range, e.g., 0 .. 4, or an enumeration type. For example, {ready,busy} denes the enumeration type with the two elements ready and busy. The variables together describe the state of the module. If we view the module as a Kripke structure, then this could be encoded by having atomic propositions for all possible values of the variables. The atomic proposition in the Kripke structure would hold exactly when the variable in the module has this value. Example 3.3. Consider the module enumerate. MODULE enumerate VAR x : {A, B, C}; To model this as a Kripke structure, we would need atomic propositions x is A, x is B, and x is C. Each atomic proposition holds exactly in the states where x is equal to A, B, or C, respectively. Next, the module has an ASSIGN clause that denes the values of the variables in the initial state, and in the next states. This is done by dening predicates init and next for all of the modules variables.

3.3. FINITE STATE MACHINES IN NUSMV MODULE counter VAR even : boolean; state : {zero, one, two}; ASSIGN init(even) := TRUE; init(state) := zero; next(even) := case state = zero : FALSE; TRUE : TRUE; esac; next(state) := case state = zero : one; state = one : two; state = two : zero; esac; Figure 3.2: Modulo 3 counter in NuSMV

13

Example 3.4. Figure 3.2 presents the NuSMV model for the modulo 3 counter from Example 3.1. The tool syntactically checks that the results of the assignments for the transition relation are within the range of the variables, otherwise it gives a warning (and the specication has to be adapted). Every module can be parametrised, and this parameter can also be used in the denition of the transition relation (see the next section for an example). Notice that the tool ensures that the ASSIGN relation is always complete, i.e., in every state, the next value of all variables always is dened. It does this by performing a syntactic check on the assignments, making sure all cases are covered. This ensures that the left-total condition of Kripke structures is fullled. The initial state denition and the transition relation can also be nondeterministic. Example 3.5. Suppose that a counter could non-deterministically choose to reset the state to zero in every possible state, then the next assignment for state would be as follows: next(state) := case

14

CHAPTER 3. SYSTEM MODELLING state = zero : {zero, one}; state = one : {zero, two}; state = two : zero; esac;

Conditions in a case expression are always evaluated from top to bottom. Thus, the rst matching case will determine the transition. Example 3.6. The following transition rule has the same behaviour as the denition of next(state) in Figure 3.2. next(state) := case state = zero : one; state = one: two; TRUE : zero; It is allowed to let conditions in a case expression depend on the next value of a variable. NuSMV checks that this relation is not-circular. Example 3.7. The transition relation for next(even) in Figure 3.2 can also be described as follows. next(even) := case next(state) = one : FALSE; TRUE : TRUE; Sometimes it is not that straightforward to describe the values of the variables as an assignment. Therefore, NuSMV also allows one to describe this as a transition relation (keyword TRANS). In that case, the module also has a special INIT section that describes a predicate over the initial state (and the ASSIGN section does not specify anything about this transition anymore). Example 3.8. Using TRANS and INIT sections, counter can be dened as follows: MODULE counter VAR even : boolean; state : {zero, one, two}; INIT even = TRUE & state = zero;

3.4. COMPOSITION OF FINITE STATE MACHINES TRANS next(even) = !(state = zero); (state = zero & next(state) = one) | (state = one & next(state) = two) | (state = two & next(state) = zero);

15

When dening modules in this way, the user has to be careful to ensure that: the initial state is non-empty; and the transition relation is left-total. Otherwise, this may result in logical absurdities, i.e., model checking may not nd any errors, while the modelling does not respect the desired property. Fortunately, NuSMV provides a check for this, by running the tool with the -ctt option.

3.4

Composition of Finite State Machines

Every system description in NuSMV should have a main module. Of course, for modularity, typically the main module will compose several modules describing parts of the system. For example, a system using multiple counters could have several variables that are (dierent) instances of the counter module. When composing modules, module parameters can be used to pass information from one module to another module. Suppose we have a very simple increase module, that increases its value when the parameter ag holds (modulo some constant value max + 1, passed as a parameter)2 . MODULE increase(flag,max) VAR cnt : 0..max; ASSIGN init(cnt) := 0; next(cnt) := case flag : (cnt + 1) mod (max + 1); TRUE : cnt; esac;
2 If we do not add a maximum value, the tool will not accept the increase of counter, because it may produce an overow.

16

CHAPTER 3. SYSTEM MODELLING

We can compose this module with our counter, and then have it count how many times the state two has been reached. To model this, we should rst solve the following restriction: modules can only pass variables as arguments, and not arbitrary expressions. Therefore, we need to introduce a variable lastState. We can do this in the VAR section, but then we introduce a new variable (and thus increase the state space). An alternative approach is to add the following specication to the counter module. DEFINE lastState := state = two; The DEFINE section denes a macro lastState, whose value depends on the expression state = two. It can be considered as the introduction of a variable whose value depends (functionally) on the value of other variables. The advantage is that this only introduces an abbreviation, and it does not increase the state space. Now we can dene the following main module. MODULE main VAR c : counter; inc : increase(c.lastState ,1023); Modules execute synchronously, i.e., at every point in time, all modules make a transition synchronously. This means in particular that the increase module never will miss the counter state being equal to two, or that a two state will be counted twice. In earlier versions of NuSMV also asynchronous composition was supported. When systems are composed asynchronously, a single module takes one step at the time, and the behaviour of the overall system is described as all possible interleavings of the behaviours of the composing modules. Support for asynchronous composition is deprecated now. If one wishes to model an asynchronous system, this has to be resolved at a higher level, i.e., the model needs to have explicit control variables that encode when a module can change state, and when it should remain unchanged3 . In concurrency theory, one also often uses the term true concurrency. This means that one or more processes can make a step at the time, and that the duration of those steps can vary. This model is not supported by NuSMV at all, and it is out of the scope of this course.
3

During the exercise session, there will be an exercise to do this.

3.5. USING NUSMV

17

3.5

Using NuSMV

Once we have a NuSMV model, we can use the tool to analyse the model. First of all, we can perform several well-formedness checks, using the -ctt option. If the transition relation is specied in INIT and TRANS sections, it is absolutely necessary to perform this check, to avoid logical inconsistencies. Once a model is well-formed, it can be passed to the tool. If NuSMV is invoked with the -int option, then it will launch an interactive mode that can be used to simulate execution of the model. In the interactive editor, one rst types go to load the model for simulation. Then the following commands are typically used: pick state -r: pick a random initial state; print current state -v: print verbose information about the current state; simulate -r -k n: simulate n random steps of the execution; and show traces -v: show the resulting trace. Consider for example the module counter in Figure 3.2. Suppose we load this, together with the following main module: MODULE main VAR ctr : counter; If we start NuSMV interactively (NuSMV -int counter.smv), we can have a session as in Figure 3.3. There are more ways to steer the simulation engine, for this we refer to the NuSMV tutorial [23] and manual [22]. NuSMV can also be called non-interactively. In the next chapter, we will see how we can specify desired properties about the model. If we execute NuSMV non-interactively, these properties are validated, and if they are violated, a counter example trace is returned.

3.6

Fairness

When studying properties of the possible behaviours of a system, one sometimes nds that a property is violated, but that it is only violated on a path that will not happen in reality, because it has a transition that is enabled, but is never executed. To prevent this situation, we introduce the notion of fairness.

18

CHAPTER 3. SYSTEM MODELLING

NuSMV > go NuSMV > pick_state -r NuSMV > print_current_state -v Current state is 1.1 ctr.even = TRUE ctr.state = zero NuSMV > simulate -r -k 3 ******** Simulation Starting From State NuSMV > show_traces -v <!-- ################### Trace number: 1 Trace Description: Simulation Trace Trace Type: Simulation -> State: 1.1 <ctr.even = TRUE ctr.state = zero -> State: 1.2 <ctr.even = FALSE ctr.state = one -> State: 1.3 <ctr.even = TRUE ctr.state = two -> State: 1.4 <ctr.even = TRUE ctr.state = zero NuSMV > simulate -r -k 2 ******** Simulation Starting From State NuSMV > show_traces -v <!-- ################### Trace number: 1 Trace Description: Simulation Trace Trace Type: Simulation -> State: 1.1 <ctr.even = TRUE ctr.state = zero -> State: 1.2 <ctr.even = FALSE ctr.state = one -> State: 1.3 <ctr.even = TRUE ctr.state = two -> State: 1.4 <ctr.even = TRUE ctr.state = zero -> State: 1.5 <ctr.even = FALSE ctr.state = one -> State: 1.6 <ctr.even = TRUE ctr.state = two NuSMV > quit

1.1

********

################### -->

1.4

********

################### -->

Figure 3.3: Example NuSMV session

3.6. FAIRNESS

19

Consider for example the counter module, with the possibility to reset the state. This means for example that in the state zero, the next state non-deterministically becomes either zero or one. However, typically, a system that resets itself all the time, and thus never leaves the state zero is not realistic, and one may wish to exclude these executions. This is done by saying that the system should behave fairly. In the literature, two notions of fairness are found. Weak fairness expresses that if a transition is enabled for an unbounded time, then it eventually must be taken. Thus in the system above, if we only consider executions that are weakly fair, then the execution that stays in the initial state forever is not part of the system. Strong fairness expresses that if a transition is enabled innitely often, then it eventually must be taken. Notice that the transition does not have to be enabled for a continuous period, it only has to be enabled over and over again. Example 3.9. Consider the following variation of a counter. MODULE conditional_counter VAR flag : {go, no_go}; state : {zero, one, two}; ASSIGN init(flag) := no_go; init(state) := zero; next(flag) := case flag = go : no_go; flag = no_go : go; esac; next(state) := case flag = no_go : state; flag = go & state = zero : {zero, one}; flag = go & state = one : {one, two}; flag = go & state = two : {two, zero}; esac; In this module, if flag = go, the next state relation may non-deterministically choose to move to the next state, or state in the same state. The ag is alternating between go and no_go. Thus, the transition to move to the next state is not enabled forever. However, it is enabled innitely often. Thus, weak fairness would not enforce that the transition is eventually taken, but strong fairness would enforce this.

20

CHAPTER 3. SYSTEM MODELLING

In the next chapter, we will see how fairness can be expressed as a temporal logic property over the system. However, in practice, it is a lot more ecient to support fairness directly in the tool, and this is exactly what NuSMV does. Concretely, NuSMV supports two notions of fairness. One explicitly adds this information as constraints to the system description. FAIRNESS expresses that NuSMV should ignore any path along which is not satised innitely often (actually in new versions of NuSMV, this constraint is called JUSTICE, but FAIRNESS can still be used for backwards compatibility). COMPASSION(, ) expresses that NuSMV only has to considers paths where the following holds: if holds innitely often on the path, then has to hold innitely often on this path as well. Notice that any path where does not hold innitely often, thus will be considered by NuSMV. Notice that FAIRNESS corresponds to the notion of weak fairness described above: where is instantiated to express: the transition is not enabled or the transition is taken. Since the model checker ignores any path where this property is not satised innitely often, it means that it will ignore those paths where the transition is enabled all the time and never taken. Strong fairness as described above can be obtained using COMPASSION, instantiated as follows: becomes the transition is enabled, and becomes the transition is taken. Thus, the model checkers considers only paths such that if the transition is enabled innitely often, it will also be taken innitely often. This instantiation coincides with the temporal logic characterisation of fairness (see Section 4.4.1). However, in many concrete cases, it is also possible to give a simpler formula to express the desired fairness constraint. For example, for the conditional counter in Example 3.9 above, we can enforce that the system cannot stay in state zero forever by adding the constraint FAIRNESS !(state = zero);.

3.7

Further Reading

Bounded Model Checking As mentioned above, state spaces are often innite. For some models, that are in a special format, dedicated model checking algorithms can be developed. However, in general this is not possible, and properties like the system will always be in a state that satises

3.7. FURTHER READING

21

some property cannot be checked for such systems. An alternative approach is to use bounded model checking. With bounded model checking, the state space will be explored up to a certain depth (called the bound ), after which it can be decided whether the property was true for this subset of the state space. For some classes of problems it can even be shown that bounded model checking is sucient: if a counter example to such a problem cannot be found within a certain bound, then no counter example will exist at all. For more information, we refer to [14]. State Space Reduction In the literature, many ways to reduce the state space are discussed (e.g., using binary decision diagrams to eciently store states [20], treating states symbolically instead of explicitly [31], using abstraction [30], and exploiting symmetry to reduce the state space [29]). Further, special classes of models (such as push-down automata [17, 46]) are dene, that are innite space, but for which model checking algorithms exists. Quantitative Model Checking If one wishes to also model quantitative aspects of a computation (e.g., time, energy consumption, memory usage, probabilities), one can describe systems using a model that is extended with such information. For many of these quantitative models, dedicated model checkers exist (for example, Uppaal can be used to verify timed models [12]; Prism can be used to verify probabilistic models [65]).

22

CHAPTER 3. SYSTEM MODELLING

Chapter 4

Crash Course on Temporal Logic


In the previous chapter, we have seen how we can dene a formal model to represent a system that we want to reason about. This chapter discusses how we can express the properties that we expect to hold for these models, so that the model checker can check this for us. It introduces several variants of temporal logic. Temporal logic allows one to express desired properties about possible executions, e.g., to express the order in which certain events must occur, or to state that a property eventually must be satised.

4.1

History and Background

A common and natural way to describe the behaviour of a system is to describe how it evolves over time. For example, if you describe the trac lights at a crossing, you might explain its behaviour by stating the following properties: Trac lights for crossing lanes are never green at the same time. If a trac light is red, it eventually becomes green. A trac light only can become orange, if it was green before. Temporal logic is a way to specify such behaviour of a system over time formally. In this case, a system is described as a transition system: for each state, the set of possible transitions describe the possible next states. System traces, i.e., executions allowed by the transition system, are sequences of states. Temporal logic formulae describe properties that have to hold for 23

24

CHAPTER 4. CRASH COURSE ON TEMPORAL LOGIC

such traces. The basic ingredients are atomic state properties, i.e., properties that have to hold in a single state, and logical and temporal connectors that describe in which states the atomic state properties should be satised. For example, in the trac light example above, atomic state properties would be properties like light 1 is green, light 2 is orange, etc. Temporal connectors are properties like: always (in any reachable state), eventually (a state is reachable), before (a property about two consecutive states). The development of temporal logic as we will study it here, is closely connected to the development of computer science. Pure logicians typically did not consider time, but with the emergence of computer science, and the need to reason about the evolving behaviour of systems, temporal logic emerged as a natural extension of classical logic. In particular, there are two variants of temporal logic that are widely used: Linear Temporal Logic (LTL), proposed by Pnueli in 1977 [82], and Computation Tree Logic (CTL), a branching time logic proposed by Clarke and Emerson in 1980 [42]. LTL constrains all possible executions of a system in a uniform way, whereas CTL also allows one to express that a property holds for one possible execution of a system. Phrased in another way: LTL expresses properties that have to hold for all traces, whereas CTL expresses properties over a (possibly innite) tree of possible computations. In particular, CTL properties can also hold if an execution is possible, it does not necessarily have to happen. The dierence between the two logics has led to erce debates for many years, with strong proponents on both sides. This discussion has to do both with the expressiveness of the logics, and the complexity of checking whether a system satises the property. LTL and CTL are incompatible in expressiveness, i.e., there are properties that can only be expressed in LTL and not in CTL, and the other way round. In the context of the System Validation course, we are pragmatic. Instead of choosing one of the two logics, we just use whichever is the most appropriate to express the property at hand. Checking whether a system respects a temporal logic property is called model checking. The previous chapter described how to dene a system; this chapter focuses on how the desired properties of the system can be expressed.

4.2

Safety versus Liveness Properties

Two important classes of temporal properties can be distinguished, namely those of safety and liveness properties. A safety property expresses that

4.3. KRIPKE STRUCTURES

25

the system operates in a safe way, and will never reach a bad state. Safety properties are often characterised as nothing bad will happen. In contrast, a liveness property expresses that a certain event must happen at some point in time: eventually something good will happen. Typical examples of safety properties are: in all reachable states, a variable x is always positive; a system will never be in a critical state, unless it has received explicit permission to do so; and a write operation on a le can only occur if the le rst has been opened. Typical examples of liveness properties are: whatever happens, the system will always reach a state where variable x is negative; when a system enters a critical state, it will also leave this critical state again after a nite number of steps; and if a le is opened, it will eventually be closed. When developing validation or verication techniques for temporal properties, the dierence between safety and liveness properties is important to understand. In particular, the sets of safety properties that can be expressed for LTL and CTL are roughly the same, but the set of liveness properties that can be expressed in LTL is very dierent from the set of liveness properties that can be expressed in CTL. Moreover, another important dierence is that if a safety property is not valid, there always is a nite counter example that illustrates why it is not valid, whereas liveness properties can only have innite counter examples. The fact that something does not happen within a bounded amount of time, does not imply that it will never happen.

4.3

Kripke Structures

In the previous chapter, we have already seen Kripke structures as the formal model underlying the notion of modules in NuSMV. The semantics of the temporal logic formulae will be dened over these Kripke structures, therefore we quickly recall the denition.

26

CHAPTER 4. CRASH COURSE ON TEMPORAL LOGIC

Denition 4.1 (Kripke Structure). A Kripke structure over a set of atomic propositions AP is a 4-tuple K = (S, I, , ) where S is a nite set of states; I S is the set of initial states; S S is a transition relation, such that is left-total, i.e., s S.s S.(s, s ) ; and : S AP is an interpretation (or labelling) function that maps each state to its set of valid atomic propositions. To describe the execution behaviours of the Kripke structure, we dene the notion of path. Denition 4.2 (Path). Given a Kripke structure K = (S, I, , ), a path in K is an innite sequence of states = s0 s1 s2 s3 . . ., such that s0 I and i 0.si si+1 . We use paths (K ) to denote the set of all paths of K , starting in s. We use i to denote the ith element of the path, and i to denote the path starting in i , i.e., i = i i+1 i+2 . . .. Notice that there always exists an innite path, because is left-total. Example 4.1. Let AP be the set {p, q }. The Kripke structure ({s0 , s1 }, {s0 }, {(s0 , s1 ), (s1 , s0 )}, {s0 {p, q }, s1 }) is graphically depicted as follows.
/ s{p,q} 0 c "

s 1

The only possible path in this structure is = s0 s1 s0 s1 s0 s1 . . . Thus for example, 0 = s0 , 1 = s1 , 2 = s0 , 3 = s1 , etc., and 1 = s1 s0 s1 . . . .

4.4. LINEAR TIME TEMPORAL LOGIC - LTL

27

4.4

Linear Time Temporal Logic - LTL

Now we are ready to dene the dierent temporal logics. First we present linear time temporal logic. Formulas in linear time temporal logic are propositional logic formulas, combined with the connectives G (Globally), F (Eventually), X (Next), U (Until) and W (Weak Until). Intuitively the formulas have the following meaning (where is an LTL formula): G means that holds in every (reachable) state; F means that there always exists a reachable state where holds; X means that in the next state holds; U means that will always hold eventually, and until that point, must hold; W means that either holds forever, or if it does not hold anymore, then must hold. The Weak Until is sometimes also called Unless. Formally, its syntax and semantics are dened as follows. Denition 4.3 (LTL). Let p AP be an atomic proposition. Then a formula in LTL is dened by the following syntax: ::= p | | | | G | F | X | U | W | Given a Kripke structure K , validity of an LTL formula for a path in K , denoted K, |= , is dened by the following inductive denition. K, |= p K, |= K, |= 1 2 K, |= 1 2 K, |= G K, |= F K, |= X K, |= 1 U 2 K, |= 1 W 2 p (0 ) K, |= K, |= 1 and K, |= 2 K, |= 1 or K, |= 2 i.K, i |= i.K, i |= K, 1 |= j.K, j |= 2 and k.k < j.K, k |= 1 j.(k.k j.K, k |= 2 ) implies K, j |= 1

Given a Kripke structure K , an LTL formula is valid, denoted K |= , if for all paths starting in an initial state s I , the formula is valid on the path, i.e., K, |= .

28

CHAPTER 4. CRASH COURSE ON TEMPORAL LOGIC

In fact, core LTL can be much smaller, using only the temporal operators X and U. (This is similar to the fact that all propositional logic formulas can be expressed using negation () and conjunction () only, and all other connectives can be expressed in terms of these connectives.) All other LTL operators can be expressed in terms of these operators, by using the following equivalences: G F() F true U 1 W 2 (1 U 2) G 1 It is important to understand the dierence between U and W, i.e., the until and the weak until operator. A formula 1 U 2 states that 2 must hold at some point in time, and up to that point, 1 has to hold. In contrast, a formula 1 W 2 states only that 2 may hold at some point. If 2 holds, then after this point 1 does not have to hold any longer, otherwise, 1 has to hold forever. Because 1 W 2 is a weaker requirement on 2 , this explains the name weak until. Any expression using an until operator can be transformed into a weak until as follows: 1 U 2 = F 2 (1 W 2 ) An alternative that is also used sometimes in the literatures is the release operator R: a formula 1 R 2 has the following meaning: 2 has to hold until and including the point where 1 rst becomes true; if 1 never becomes true, 2 must remain true forever. The release operator can be expressed in terms of the weak until operator as follows: 1 R 2 = 2 W(1 2 ) Finally, some example properties and their formalisation in LTL (from [43]). Example 4.2. p F q intuitively means if p is true now, then at some future moment q will be true. Thus, if p is not true now, q never has to be true. G(p F q ) intuitively means whenever p is true, q will be true at some subsequent moment. p G(p X p) means p is true now, and whenever p is true in a state, it also holds in the next state. This implies G p, i.e., p is always true. When we wish to check an LTL property for a module in NuSMV, the property can be written in the LTL property section of the module (preceded by keyword LTLSPEC. All boolean expressions can be used as atomic propositions.

4.4. LINEAR TIME TEMPORAL LOGIC - LTL

29

Example 4.3. For the counter in Figure 3.2, we can specify for example that whenever the counter is odd, it eventually will become even again. This is expressed as follows. LTLSPEC G (! ctr.even -> F ctr.even);

4.4.1

Fairness as an LTL Formula

As discussed in the previous chapter, when studying properties of the possible behaviours of a system, one sometimes nds that a property is violated, but that this is only violated on a path that does not correspond to any realistic behaviour. To express that a property does not have to hold for such infeasible paths, we introduced the notion of fairness. We saw how we could add requirements to a module in NuSMV by explicitly adding a fairness (or compassion) constraint. However, in some cases we want to check explicitly that all possible behaviours are fair. To this end, we express fairness as a temporal logic property, that we can model check. Both weak and strong fairness can be expressed in LTL. First we dene some appropriate atomic propositions: enabledt means that transition t is enabled; and takent means that transition t is actually taken). Weak fairness w.r.t. transition t is expressed as follows: (F G enabledt G F takent ) This expresses that if from a certain point in the computation transition t is continuously enabled, then it must be executed innitely often. An alternative but equivalent way to phrase this is the following formula. G F(enabledt takent ) This expresses that there always is a point in the future where either the transition is not enabled, or it is taken. Strong fairness w.r.t. transition t can be expressed as follows: (G F enabledt G F takent ) This expresses that if a step is innitely often enabled then it is innitely often executed.

30

CHAPTER 4. CRASH COURSE ON TEMPORAL LOGIC

4.5

Computation Tree Logic - CTL

As explained above, an alternative approach to specifying temporal properties is to use a branching time logic. This allows one to express properties that do not have to hold for all possible program executions, but only for one possible execution path. In CTL, one distinguishes between path operators and state operators. Path operators are used to express state formulas, i.e., formulas that hold for a particular state. They express properties about the paths starting in that state. State operators express properties about paths, i.e., they are used to describe path formulas. They express properties about the states on that path. CTLs state operators resemble the LTL operators. The use of path operators adds extra expressiveness to CTL (compared to LTL). However, since CTL restricts how path and state operators can be combined, not all LTL formulas can be expressed in CTL. Thus, eectively, LTL and CTL are incomparable logics (see below for concrete formulas that illustrate the incomparability of the two logics). Formulas in CTL always start with a path operator: the A operator states that a property has to hold for all path starting in a particular state; the E operator states that a property has to hold for at least one path starting in a particular state. The properties that have to hold for the paths are described using essentially the same top-level operators as for LTL. However, their arguments have to be CTL formulas starting with a path operator again. Formulas starting with a path operator (A and E) are called state formulas, because their validity is dened for a state. Formalus starting with operators G, F, X, U, W are called path formulas, because their validity is dened for a path. All top-level CTL formulas are state formulas. Formally, this is dened by the following grammar for CTL formulas. Denition 4.4 (CTL). Let AP be a set of atomic propositions. A CTL formula is dened as a state formula sf , according to the following grammar (where p AP ). sf pf ::= p | sf | sf sf | sf sf | A pf | E pf . . . ::= G sf | F sf | sf U sf | X sf . . .

4.5. COMPUTATION TREE LOGIC - CTL

31

Given a Kripke structure K and a state s validity of a CTL formula , denoted K, s |= , is dened inductively as follows. K, s |= K, s |= K, s |= 1 2 K, s |= 1 2 K, s |= A K, s |= E K, |= G K, |= F K, |= X K, |= 1 U 2 p (s) K, s |= K, s |= 1 and K, |= 2 K, s |= 1 or K, |= 2 . paths (K ) .K, |= . paths (K ) .K, |= i.K, i |= i.K, i |= K, 1 |= j.K, j |= 2 and k.k < j.K, k |= 1

Given a Kripke structure K , an CTL formula is valid, denoted K |= , if for all initial states s I , the formula is valid in s, i.e., K, s |= . An alternative way to describe the syntax of CTL formulas is using combined operators, such as AU, EU, AG etc. Then the syntax can be described as follows: ::= p | AG | AF | AU(, ) | AX | EG | EF | EU(, ) | EX | | | . . . . It is straightforward to see that this grammar produces an equivalent set of formulas. The syntax as dened in Denition 4.4 is cleaner and corresponds closer to the separation between state and path formulas in the semantics, but the alternative syntax is easier for tools. Just as for LTL, CTL can also be expressed with a smaller set of operators, by using the following equalities. AF AG AX AF EF AG EG EG EF EX AU(true, ) EU(true, ) EU(true, ) AU(true, g )

Finally, we conclude with some example properties expressed as CTL formulas.

32

CHAPTER 4. CRASH COURSE ON TEMPORAL LOGIC A property p has to hold throughout: A G p.

Example 4.4.

There is at least one execution, where p always holds: E G p. There is at least one execution, where p holds at some point: E F p. In every possible execution, p eventually holds: A F p. We can always pick a possible continuation where p holds forever: A G(E G p). When we wish to check a CTL property for a module in NuSMV, the property can be written in the CTL property section of the module (preceded by keyword SPEC. All boolean expressions can be used as atomic propositions. The temporal logic operators are written as AU, AF, EU etc. Example 4.5. For the counter in Figure 3.2, we can specify for example that at any point, it is always possible to choose a path from where an even state can be reached. This is expressed as follows. SPEC AG (EF ctr.even);

4.6

Comparison of LTL and CTL

As already mentioned above, LTL and CTL are not equivalent in expressiveness. In fact, they are incomparable: there are LTL formulas that cannot be expressed in CTL and vice versa. In particular, CTL formulas involving the existential path operator E cannot be expressed as LTL formulas: LTL formulas always have to hold for all possible paths. On the other hand, an LTL formula such as F G p, there always is a point in time such that afterwards p holds always, cannot be expressed in CTL. Consider for example the following Kripke structure.
/ s{p}
0

/ s
1

/ s{p}
2

This satises F G p, but it does not satisfy A F A G p. On the other hand, the CTL formula A F E G p is weaker than F G p, as the CTL formula holds for the following Kripke structure (whereas the LTL formula does not hold):
{p} 3 s0

! b

s 1

4.7. FURTHER READING

33

However, there is a logic that subsumes both LTL and CTL. This logic is called CTL*, dened by Emerson and Halpern in 1986 [49]. CTL and LTL both have been developed independently before CTL*. Both sublogics have become very important in the model checking community, while CTL* is not yet of practical importance. CTL* has the same operators as CTL (i.e., A, E, G, F, U, X, W), but it has no restrictions on how they can be mixed. Thus, one can have formula like A F G p which is equivalent to the LTL formula F G p and A E G p meaning that in all states there is always one possible continuation where p holds forever.

4.7

Further Reading

Many other dierent temporal logics exist. It falls out of the scope of this course to really know about all the logics, but this section mentions just a few for completeness. Past Time Logic An often used variation of LTL and CTL is using past time operators. This means that one can specify for example that an property was holding at some point in the past, a property has to hold before another property becomes true, or a property was holding upto a certain point in time. However, adding past time operators does not add any expressiveness to the logic, i.e., all past time operators can be expressed as future time temporal logic operators (see e.g., [43] for more information). Modal Logic A logic that has much in common with temporal logic is modal logic. Modal logic is an extension of propositional logic with the box ( ) and the diamond () operators. Let K be a Kripke structure. A formula holds for a state s if for all states s that are reachable via a transition, i.e., such that s s , the state s satises the formula . A formula holds for a state s if there exists a state s such that s s , s satises . Modal -calculus The main dierence between modal and temporal logic, is that modal logic restricts the next state, while temporal logic can specify a property for a whole execution (without knowing the length of the execution in advance). However, there is an extension of modal logic that has this same power. This is the modal -calculus [64]. It extends modal logic with two xed point operators: a least xed point and a greatest xed

34

CHAPTER 4. CRASH COURSE ON TEMPORAL LOGIC

point . The xed point operator basically encodes recursion: a formula X. X corresponds to LTL formula G . It holds for a state s if in the current state the formula holds, and in the next state, the property has to hold again. The dierence between least and greatest xed points is that for least xed points, the recursion has to terminate at some point, otherwise the formula is not satised, whereas for greatest xed points, the recursion never has to terminate. This means that the formula F can be encoded with a least xed point: X. X either the formula holds now, or after some nite number of steps, the formula has to hold. Since in the modal -calculus dierent xed points can be combined into a single formula, the logic is very expressive. In particular, all logics like LTL, CTL and CTL* can be encoded in the modal -calculus, see e.g., [35].

Chapter 5

Software Model Checking


So far, we have looked at systems that could be described abstractly by a nite state machine. However, sometimes we wish to verify actual implementations. This chapter 5 looks at dierent ways to do this. First we discuss how we could manually encode a program as a model.

5.1

From Code to Model

This section sketches a possible way to encode a program as a NuSMV model. Suppose that we have a program that we wish to verify with NuSMV. The rst step is to annotate all the instructions with program counters. The program counters can be considered as the states of the model. All program variables also become variables in the module. Next, we add a transition relation that encodes the possible control ows in the program. Thus, sequential composition just moves to the next program counter, and a while loop condition has two possible next states: one if the condition holds, one if the condition does not hold. In addition, the transition relation also species how the program variables can change when making a step from one program counter to the next one. This results in a NuSMV model over which simple properties can be checked. Example 5.1. Consider the following small Java program.

5.2

Challenges for Software Model Checking

This translation can also be automated, but is dicult to optimise. However in practice, this approach causes many problems for any realistic program: 35

36

CHAPTER 5. SOFTWARE MODEL CHECKING for multithreaded software, one has to make sure that the individual steps have the right granularity; each transition between two program counter states should correspond to a single atomic transition of the program; method calls have to be incorporated correctly, properly considering late binding; variable scopes have to be considered; programs often have complicated types for their variables (oats, classes etc.) and these should all be properly encoded; and for any non-trivial program, the result will be an enormous model that may quickly suer from the state space explosion problem.

Nevertheless, developing algorithmic techniques to check properties of software is an important task. In particular, for concurrent and distributed software, the possible execution set is too large for a programmer to really capture mentally, and tools are necessary to analyse all the dierent possible interactions between dierent threads.

5.3

Abstraction-Renement

Dierent solutions are explored to obtain ecient techniques for software model checking. In this chapter, we will explore one very succesuful approach, namely that of counter-example-guide abstraction-renement (CEGAR). The main idea of the approach is that a user species an abstraction of the concrete software. This abstraction results in a smaller model, that can be model checked. The abstraction is constructed in such a way that if the property holds for the abstract model, it also holds for the original program. If a property does not hold for the abstract model, based on the counter example the abstraction is renement, and the procedure is repeated.

Chapter 6

Crash Course on JML


6.1 History and Background

JML, the Java Modeling Language [67], is a Design by Contract specication language for Java programs. The term Design by Contract (DbC) was introduced by Bertrand Meyer in 1986 for the Eiel programming language [77]. DbC is a programming methodology where the behaviour of program components is described as a contract. The user of a component only has to study the components contract, and this should tell him exactly what he can expect from the component. The implementer of the component is free to choose any implementation, as long as it respects the components contract. See also this Wikipedia page: http: //en.wikipedia.org/wiki/Design_by_contract for a comprehensive description of the ideas behind Design by Contract. Design By Contract is a popular methodology for object-oriented languages. In this case, the components are the programs classes. Contracts naturally correspond with the object-oriented paradigm to hide (or encapsulate) the internal state of an object (a class instance). Basically, a class contract describes for each method under what conditions it may be called, and what it guarantees about its result. In addition, a class contract may also describe general consistency properties of the class, i.e., properties that the user always can rely upon. Method contracts are also convenient to express the idea of behavioural subtyping [73]. In an object-oriented program, any subclass may be used wherever a superclass is expected. Behavioural subtyping expresses the idea that a subclass thus should behave as the superclass (at least, when it is used in a superclass context). Contracts can be used to ensure that a subclass is 37

38

CHAPTER 6. CRASH COURSE ON JML

indeed a behavioural subtype of a superclass. In particular, every method in the subclass should respect the methods contract of the superclass. And in addition, all the consistency properties of the superclass are inherited by the subclass. Notice that this same approach applies for interfaces and classes. An interface can be specied with its desired behaviour. Every class that implements this interface should be a behavioural subtype of the interface, i.e., it should satisfy all the specications of the interface. As mentioned above, the use of Design By Contract for object-oriented languages dates back to 1986, when Bertrand Meyer introduced it for the language Eiel [77]. The Eiel compiler has a special option that can be used to check validity of the contract at run-time. Subsequently, the same ideas where applied to reason about other programming languages (including Modula III, C++, and Smalltalk, that were all handled in the Larch project, see http://www.sds.lcs.mit.edu/spd/larch/). With the growing popularity of Java, several people decided to develop a specication language for Java. Gary Leavens and his students at Iowa State University used their experience from the Larch project, and started work on JML. JML is short for Java Modeling Language (the similarity to the name UML is intentional). They proposed a specication language, and simultaneously developed the JML run-time assertion checker, which could be used to validate the contracts at run-time. At more or less the same time, Rustan Leino and his team, then at the DEC/Compaq research centre started working on a tool to reason statically about Java programs. Rustan Leino also had worked on specication of Modula III programs before. For their static verication tool, ESC/Java [71] they developed a specication language that was more or less a subset of the JML language that Gary Leavens proposed. But instead of developing a technique that would validate the contracts during execution, they developed a static verier that could check whether all possible program executions respected the classs contract. At the same time, several projects existed that targeted tool-supported verication of Java programs (for example the LOOP project [13], the Key project [11], and the Krakatoa project [76]). Quickly, people involved in these dierent projects more or less agreed that JML was the most appropriate as property specication language for their tools. And thus, as a result, JML became the de facto standard contract specication language for Java. Ever since then, the community has worked on adopting a single JML language, with a single semantics and this is still an on-going process. Over the years, JML has become a very large language, containing many

6.2. JML METHOD CONTRACTS

39

dierent, potentially useful, specication constructs. However, because of the language being so large, not for all constructs the semantics is actually understood and agreed upon, and moreover all tools that support JML in fact only support a subset of it. To solve this, a core subset of JML has been dened that contains the most common constructs of JML, for which the semantics is well-understood. Tools that support JML are expected to support at least this JML-core (and they are free to support any other language constructs). This core is dened in Section 2.9.1 of the JML Reference Manual [68]. During the System Validation course, we will mainly restrict ourselves to the language constructs that are in this core, as this already expressive enough to write non-trivial specications about Java programs. However, occasionally, we will also use features from more advanced JML levels.

6.2

JML Method Contracts

Ingredients of a Method Contract So, what is exactly a method contract? A method contract consists of two things: it describes what is expected from the code that calls the method, and it provides guarantees about what the method will actually do. The expectations on the caller are called the precondition of the method. Typically, these will be conditions on the methods parameters, e.g., the argument should be a non-null pointer, but the precondition can also describe that the method can only be called when the object is in a particular state. In JML, every precondition expression is preceded by the keyword requires. The guarantees provided by the method are called the postcondition of the method. They describe how the objects state is changed by the method, or what the expected return value of the method is. A method only guarantees its postcondition to hold whenever it is called in a state that respects the precondition. If it is called in a state that does not satisfy the precondition, then no guarantee is made at all. In JML, every postcondition expression is preceded by the keyword ensures. JML specications are written as special comments in the Java code, starting with /*@ or //@. The @ sign allows the JML parser to recognise that the comment contains a JML specication. Sometimes, JML specications are also called annotations, because they annotate the program code. The preconditions and postconditions are basically just Java expressions (of Boolean type). This is done on purpose: if the specications are written in a language that the programmer is already familiar with, they are easier for

40 him to write and to read.

CHAPTER 6. CRASH COURSE ON JML

Example 6.1. Figure 6.1 contains an example of a basic JML specication. It contains contracts for the methods in an interface Student, modeling a typical UT student. We discuss the dierent aspects of this example in full detail. For method getName, we specify that it is a pure method, i.e., it may not have any (visible) side eects. Only pure methods may be used in specication expressions, because these should not have side eects. Method getStatus is also pure. In addition, we specify that its result may only be one of two values: bachelor or master. To denote the return value of the method, the reserved JML-keyword \result is used. For method getCredits we also specify that it is pure, and in addition we specify that its return value must be non-negative; a student thus never can have a negative amount of credits. Method setName is non-pure, i.e., it may have side eects. Its postcondition is expressed in terms of the pure methods getName and equals: it ensures that after termination the result of getName is equal to the parameter n. As a side remark, method equals is dened in class Object, see http: //www.eecs.ucf.edu/~leavens/JML-release/javadocs/java/lang/ Object.html#equals%28java.lang.Object%29 for its specication. Method addCreditss precondition states a condition on the method parameters, namely that only a positive number of credits can be added. The postcondition species how the credits change. Again, this postcondition is expressed in terms of a pure method, namely getCredits. Notice the use of the keyword \old. An expression \old(E ) in the postcondition actually denotes the value of expression E in the state where the method call started, the pre-state of the method. Thus the postcondition of addCredits expresses that the number of credits only increases: after evaluation of the method, the value of getCredits is equal to the old value of getCredits, i.e., before the method was called, plus the parameter c. Method changeStatuss precondition species that this method only may be called when the student is in a particular state, namely he

6.2. JML METHOD CONTRACTS

41

package chapter2; public interface Student { public static final int bachelor = 0; public static final int master = 1; /*@ pure */ public String getName(); //@ ensures \result == bachelor || \result == master; /*@ pure */ public int getStatus(); //@ ensures \result >= 0; /*@ pure */ public int getCredits(); //@ ensures getName().equals(n); public void setName(String n); /*@ requires c >= 0; ensures getCredits() == \old(getCredits()) + c; */ public void addCredits(int c); /*@ requires getCredits() >= 180; requires getStatus() == bachelor; ensures getCredits() == \old(getCredits()); ensures getStatus() == master; */ public void changeStatus();

} Figure 6.1: First JML example specication Student

42

CHAPTER 6. CRASH COURSE ON JML has obtained a sucient amount of credits to pass from the Bachelor status to the Master status. Moreover, the method may only be called when the student is still having a Bachelor status. The postcondition expresses that the number of credits is not changed by this operation, but the status is. Notice that the two preconditions and the two postconditions of changeStatus are written as separate requires and ensures clauses, respectively. Implicitly, these are assumed to be joined by conjunction, thus the specication is equivalent to the following specication: /*@ requires getCredits() >= 180 & getStatus() == bachelor; ensures getCredits() == \old(getCredits()) & getStatus() == master; */ public void changeStatus();

Specications and Implementations Notice that the method specications are written independently of possible implementations. Classes that implement this interface may choose dierent implementations, as long as it respects the specication. (See for example Figures 7.1, and 8.4 for dierent implementations). One obvious implementation is using a eld credits that keeps track of the number of credits earned by the student. However, an alternative implementation is to keep track of a list of courses (denoted by credits) and to compute the total number of credits as the sum of the credits of the individual courses. Later, in Section 8, we will see how this implementation can be shown to respect the specication of Student. Method specications do not always have to specify the exact behaviour of a method; they give minimal requirements that the implementation should respect. Example 6.2. Considering the specication in Figure 6.1 again, the method specication for changeStatus prescribes that the credits may not be changed by this method. However, method addCredits is free to update the status of the student. So for example, an implementation that silently updates the status from Bachelor to Master whenever appropriate is according to the specication. /*@ requires c >= 0; ensures getCredits() == \old(getCredits()) + c; */

6.2. JML METHOD CONTRACTS public void addCredits(int c) { credits = credits + c; if (credits >= 180) {status = master}; }

43

Notice also that both addCredits and changeStatus would be free to change the name of the student, according to the specication, even though we would typically not expect this to happen. A way to avoid this, is to add explicitly conditions getName().equals(\old(getName())) to all postconditions. Later, in Section 9.5, we will see how assignable clauses can be used to explicitly disallow these unwanted changes in a more convenient way. Default Specications You might have wondered why not all specications in Student have a pre- and a postcondition. Implicitly though, they have. For every specication clause, there is a default. For pre- and postconditions this is the predicate true, i.e., no constraints are placed on the caller of the method, or on the methods implementation. Example 6.3. Thus for example the specication of method getStatus actually is the following: /*@ requires true; ensures status == bachelor || status == master; */ public int getStatus() { return status; } However, there is one exception to this. In JML all reference values are implicitly assumed to be non-null, except when explicitly annotated otherwise (using the keyword nullable). Example 6.4. This means that the methods getName and setName have implicit pre- and postconditions about the non-nullity of the parameter and the result. Explicitly, their specications are as follows: /*@ requires true; ensures \result != null; */ /*@ pure */ public String getName();

44

CHAPTER 6. CRASH COURSE ON JML

/*@ requires n != null; ensures getName().equals(n); */ public void setName(String n); Notice that the non-null by default also can have some unwanted eects, as illustrated by the following example. Example 6.5. Consider the following declaration of a LinkedList. public class LinkedList { private Object elem; private LinkedList next; .... Because of the non-null by default behaviour of JML, this means that all elements in the list are non-null. Thus the list must be cyclic, or innite. This is usually not the intended behaviour, and thus the next reference should be explicitly annotated as nullable. Specication Expressions Above, we have already seen that standard Java expressions can be used as predicates in the specications. These expressions have to be side-eect-free, thus for example assignments are not allowed. As also mentioned above, these predicates may contain method calls to pure methods. In addition, JML denes several specication-specic constructs. The use of the \result and \old keywords has already been demonstrated in Figure 6.1, and the ocial language specication contains a few more of these. Besides the standard logical operators, such as conjunction1 &, disjunction |and negation !, also extra logical operators are allowed in JML specications, e.g., implication ==>, and logical equivalence <==>. Also the standard quantiers and are allowed in JML specications, using keywords \forall and \exists. Example 6.6. Using these, we can specify for example that an array argument should be sorted. //@ requires (\forall int i, j; 0 <= i & i < j & j < a.length;
1 Since expressions are not supposed to have side eects or terminate exceptionally, in JML the dierence between logical operators & and &&, and | and || is not important.

6.2. JML METHOD CONTRACTS a[i] <= a[j]); public ... manipulateArray(int [] a) {...

45

The rst argument (int i, j is the declaration of the variables over which the quantication ranges. The (optional) second argument (0 <= i & i < j & j < a.length) denes the range of the values for this variable, and the third argument is the actually universally quantied predicate (a[i] <= a[j] in this case). Example 6.7. An alternative way to phrase the specication in Example 6.6 is the following: //@ requires (\forall int i, j; 0 <= i & i < j & i < a.length ==> a[i] <= a[j]); public ... manipulateArray(int [] a) {... The ocial JML syntax also allows other quantied expressions, such as \sum and \num_of, but unfortunately most tools do not support these yet. A Note on Purity Above, we have said that a method should be pure if it is to be used in a method specication, and purity was dened as having no visible side eects. No visible side eects means that the state that was allocated on the heap before the method call may not be changed. Thus, this does not exclude that a method creates a new object and initialises that. In fact, even changing a location and then restoring it to its old value before the method is nished technically speaking does not make a method non-pure. Constructors are typically considered pure: they create a new object and change only the elds of this newly created object, i.e., they only change state that was not allocated before the call to the constructor. Only if a constructor changes other state, for example a static variable of the class, it is considerd non-pure. To complicate matters even further, some methods exist that are technically speaking not pure, but from a specication point-of-view may be considered to be so. Consider for an example the function that computes a hashcode. The rst time this function is called on an object, a eld of the object will be written, so that the next calls can be evaluated by looking up this eld. Because of this, dierent variations of purity and observational purity exist in the literature, see e.g., [6, 37, 36] for more information. For the scope of this course, it is sucient to dene purity simply as not having any side eects.

46

CHAPTER 6. CRASH COURSE ON JML

Behaviours An important question is when a method specication is actually satised. And in particular, if a method does not terminate, does it then satisfy its specication? The specications as we have seen here are lightweight specications, and they specify a partial correctness condition. If method m is specied as follows: /*@ requires P; ensures Q; */ public ... m(...) { ... this means the following: if method m is executed in a pre-state where P holds, and if execution of method m from this pre-state terminates, then in the post-state the postcondition Q holds. Thus, if method m never terminates, it vacuously respects any lightweight specication. If one explicitly wishes to specify that a method has to terminate, this can be done by adding a diverges clause. A diverges clauses species under which conditions a method may not terminate. Example 6.8. Consider the following specication: /*@ requires P; ensures Q; diverges false; */ public ... m(...) { ... This species that method m has to terminate - it only may diverge if false holds, which is never the case. However, to express that a method must always terminate, JML provides more convenient ways to do this, as discussed shortly below. Nevertheless, diverges clauses can be useful, in particular if one wishes to express that for certain parameters a method might not terminate: /*@ requires P; ensures Q; diverges x < 0; */ public ... m(int x) { ...

6.2. JML METHOD CONTRACTS

47

To express directly that a method must terminate, JML also provides heavyweight specications. These are preceded by a keyword ...behavior. Standard behavior specications, preceded by the keyword behavior also specify partial correctness specications, but their default diverges clause is false. Thus, unless specied explicitly otherwise, a behavior specication species that a method must terminate. However, this does not exclude that a method may terminate because of an exception. If we also want to exclude this case, then a normal_behavior specication is to be used: this states that method execution has to terminate in a normal state, and in the post-state the postcondition has to hold2 . Thus, to summarise, consider the following specications: /*@ requires P1; ensures Q1; */ public ... m1(...) { ... } /*@ behavior requires P2; ensures Q2; */ public ... m2(...) { ... } /*@ normal_behavior requires P3; ensures Q3; */ public ... m3(...) { ... } these specications state the following. If the method mi (for i = 1, 2, 3) is executed in a pre-state where precondition Pi holds, then: m1 may not terminate, and if m1 terminates normally, then postcondition Q1 holds;
A method is said to terminate normally if either it reached the end of its body, in a normal state, or it terminated because of a return instruction. Below, in Section 6.4 we discuss how we can specify methods that terminate because of an exception.
2

48

CHAPTER 6. CRASH COURSE ON JML m2 has to terminate, either normally or with an exception, and if m2 terminates normally, then postcondition Q2 holds; and m3 has to terminate normally, and in the post-state postcondition Q3 has to hold.

A single method can be specied with several method specications, joined with also. This should be interpreted as a conjunction of method specications (see Section 6.5 for more details). Specications for Constructors Constructors can be considered as special methods. In the pre-state of a constructor, the object does not yet exist. Thus a precondition of a constructor can only put constraints on the constructor parameters, it cannot require anything about the internal state of the object as the object does not exist yet when the constructor is called. However, the postcondition of the constructor can specify constraints on the state of the object. Typically, it will relate the object state to the constructors parameters. Example 6.9. Suppose we have a class CStudent, implementing the Student interface. It could have the following constructor: /*@ requires c >= 0; ensures getCredits() == c; ensures getStatus() == bachelor; ensures getName() = n; */ CStudent (int c, String n) { credits = c; name = n; status = bachelor; } Thus, to repeat, it would be incorrect to specify e.g., requires getCredits() >= 0; or requires getStatus() == bachelor these specications are meaningless at the moment that the constructor is invoked. Defensive versus Oensive Method Implementations A last important point about method contracts is that they can be used to avoid defensive programming. Consider the specication of method addCredits in Figure 6.1.

6.3. JML CLASS SPECIFICATIONS

49

This method assumes that its argument is non-negative, and otherwise it is not going to function correctly. When one uses a defensive programming style, then one would rst test the value of the argument and throw an exception if this was negative. This clutters up the code, and in many cases it is not necessary. Instead, using specications, one can use an oensive coding style. The specication states what the method requires from its caller. It only guarantees to function correctly if the caller also fullls its part of the contract. When validating the application, one checks that every call of the method is indeed within the bounds of its specication, and thus the explicit test in the code is not necessary. Thus, making good use of specications can avoid adding many parameter checks in the code. Such checks are only necessary when the parameters cannot be controlled for example, because they are given via an external user.

6.3

JML Class Specications

Consider again the specication of Student in Figure 6.1. If we look carefully at the specications and the description that we give about the students credits, we notice that implicitly we assume some properties about the value of getCredits that hold throughout. For example, we wrote above: a student thus never can have a negative amount of credits. and also the number of credits only increases But if we would like to make explicit that we assume that these properties always hold, we would have to add this to all the specications in Student, and thus in particular also to all methods that do not relate at all to the number of credits. Thus for example, we would get the following specication: /*@ requires getCredits() >= 0; ensures \result == bachelor || \result == master; ensures getCredits() >= 0; */ /*@ pure */ public int getStatus(); Clearly, this is not desired, because specications would get very large, and besides describing the intended behaviour of that particular method, they also describe properties over the lifetime of the object.

50

CHAPTER 6. CRASH COURSE ON JML

Therefore, JML provides also class-level specications, such as invariants, constraints and initially clauses. These specify properties over the internal state of an object, and how the state can evolve during the objects lifetime. Invariants An object invariant3 is a predicate over the object state that holds in all visible states of an object. A visible state of an object is dened to be any state in which a method call to the object either starts or terminates. Thus, an invariant I is implicitly added as a precondition and a postcondition to every method in the class. In addition, also the post-states of the constructor are visible states, thus any constructor has to ensure that the invariant is established. Example 6.10. Figure 6.2 shows three possible invariants that can be added to interface Student. These specify that credits are never non-negative; a students status is always either Bachelor or Master, and nothing else; and if a students status is Master, he or she has earned more than 180 credits. Of course, instead of specifying invariants, one could also add these specications to all pre- and postconditions explicitly. However, this means that if you add a method to a class, you have to remember to add these preand postconditions yourself. Moreover, invariants are also inherited by subclasses (and by implementations of interfaces). Thus any method that overrides a method from a superclass still has to respect the invariants. And any method that one adds in the subclass also has to respect the invariants from the superclass. This leads to a very nice separation of concerns. An important point to realise is that invariants have to hold only in all visible object states, i.e., in all states in which a method is called or terminates. Thus, inside the method, the invariant may be temporarily broken. Example 6.11. The following possible implementation of addCredits is correct, even though it breaks the invariant that a student can only be studying for a Master if he or she has earned more than 180 points inside the method: if credits + c is suciently high, the status is changed to Master. After this assignment the invariant does not hold, but because of the next assignment, the invariant is re-established before the method terminates.
3 Not to be confused with loop invariants, as discussed in Programmeren 1. These will be discussed in Chapter 9.6.

6.3. JML CLASS SPECIFICATIONS package chapter2; interface StudentWithInv { public static final int bachelor = 0; public static final int master = 1; //@ invariant getCredits() >= 0; //@ invariant getStatus() == bachelor || //@ getStatus() == master; //@ invariant getStatus() == master ==> //@ getCredits() >= 180;

51

//@ initially getCredits() == 0; //@ initially getStatus() == bachelor; //@ constraint getCredits() >= \old(getCredits()); //@ constraint \old(getStatus()) == master ==> //@ getStatus() == master; //@ constraint \old(getName()) == getName();

/*@ pure */ public String getName(); /*@ pure */ public int getStatus(); /*@ pure */ public int getCredits(); //@ ensures getName().equals(n); public void setName(String n); /*@ requires c >= 0; ensures getCredits() == \old(getCredits()) + c; */ public void addCredits(int c); /*@ requires getCredits() >= 180; requires getStatus() == bachelor; ensures getCredits() == \old(getCredits()); ensures getStatus() == master; */ public void changeStatus();

} Figure 6.2: Interface Student with class-level specications

52 package chapter2; interface CallBack {

CHAPTER 6. CRASH COURSE ON JML

//@ invariant getX() > 0; //@ invariant getY() > 0; /*@ pure */ public int getX(); /*@ pure */ public int getY(); //@ ensures getX() == x; public void setX(int x); //@ ensures getY() == y; public void setY(int y); //@ ensures \result == getX() % getY(); public int remainder(); public int longComputation(); } Figure 6.3: Interface CallBack /*@ requires c >= 0; ensures getCredits() == \old(getCredits()) + c; */ public void addCredits(int c) { if (credits + c>= 180) {status = master;} // invariant broken! credits = credits + c; } However, if a method calls another method on the same object, it has to ensure that the invariant holds before this callback. Why this is necessary, is best explained with an example. Example 6.12. Consider the interface CallBack in Figure 6.3. Typically, correctness of the method remainder crucially depends on the value of getY being greater than 0. Suppose we have an implementation of

6.3. JML CLASS SPECIFICATIONS

53

the CallBack interface, where the method longComputation is sketched as follows. public int longComputation(){ ... if (getY() ....) { setY(0); // invariant broken } ... int r = remainder(); // callback ... setY(r + 1) // invariant re-established ... return ... } Naively, one could think that the fact that the invariant about getY is broken inside this method, is harmless, because the invariant is re-established by the setY(r + 1) statement. However, the call to the method remainder is a callback, and the invariant should hold at this point. In fact, correct functioning of this method call depends on the invariant holding. The invariant implicitly is part of remainders precondition. If the invariant does not hold at the point of the callback, this means that remainder is called outside its precondition, and no assumption can be made about its result as well. There is a way to avoid the requirement that the invariant has to hold upon callback: this is by specifying that a method is a helper method. Such methods cannot depend on the invariant to hold, and they do not guarantee that the invariant will hold afterwards. Typically, only private methods should be specied as helper methods, because one does not want that any other object can directly invoke a helper method. Dening a precise semantics for invariants is still an active area of research, see e.g. [83, 72, 80, 4]. An alternative approach, that is used in the Spec# framework, is to explicitly add specication statements unpack and pack for invariants. In the Spec# methodology, an invariant may only be broken if it has been explicitly unpacked. When the invariant is reestablished, it has to be explicitly be packed again, and this only succeeds if the invariant indeed holds at this point. Every method can then specify explicitly whether it assumes invariants to hold, i.e., to be packed, or not. This approach is sometimes referred to as the Boogie methodology [3].

54

CHAPTER 6. CRASH COURSE ON JML

Finally, it is important to realise that the notion of object invariant that we discussed here only makes sense in a sequential setting. In a multithreaded setting, there always may be another thread accessing the object simultaneously, and one cannot talk about visible state semantics anymore. Instead, in a multithreaded setting, one sometimes species strong invariants that may never be broken. Initially Clauses Sometimes, one explicitly wishes to specify the conditions that are satised by an object upon creation. Each (non-helper) constructor4 of the object has to establish the predicate specied by the initially clause. Example 6.13. Figure 6.2 shows some possible initially clauses for the Student interface. Again, it would be possible to specify this property as a postcondition of all constructors, instead of as a single initially clause. But in this way, any additional constructor has to respect the initially clause, and we ensure that also subclasses respect it. Constraints Invariants as we discussed above dene a predicate that every (visible) state of the object should respect. However, sometimes one also wishes to specify how an object may evolve over time, i.e., the relationship that exists between the pre-state and the post-state of a method call. This could be seen as a sort of general postcondition, that has to be respected by every method, however the denition is actually more ne-grained than that. For this, history constraints (usually constraints for short) have been introduced [73]. Example 6.14. Figure 6.2 denes several constraints for the Student interface. The rst constraint species that the amount of credits can never decrease. The second constraint species that if a student has obtained the Master status, he will remain a Master student, and cannot be downgraded to a Bachelor student again. Finally, the third constraint species that a students name can never change. When specifying constraints, it is important that they should be nonstrict, i.e., it should be possible to respect a constraint without actually changing the state. In particular, any pure method should be able to respect the constraint. Therefore, one should not specify the following strict constraint:
4

Again, typically only private constructors would be annotated as a helper constructor.

6.3. JML CLASS SPECIFICATIONS constraint \old(getCredits()) < getCredits();

55

as it is impossible to respect this constraint with a pure method. Typically, constraints will also be transitive, so that when you consecutively call two methods from the same object, you also know the relationship that holds between the pre-state of the rst method, and the post-state of the second method. Constraints are implicit postconditions, but just as invariants and initially clauses, they have the advantage that they are inherited, and immediately are required to hold for any additional methods. As for invariants, constraints are specied in terms of visible states. In particular, a constraint is a relation that has to be satised by any two consecutive visible states. Example 6.15. Consider the following possible implementation of addCredits. /*@ requires c >= 0; ensures getCredits() == \old(getCredits()) + c; */ // pre-state public void addCredits(int c) { credits = credits + c; if (credits >= 180) { // call-state changeStatus changeStatus(); // return -state changeStatus } } // post-state To show that the constraint is respected, it has to hold for the following visible state pairs: (pre-state, call-state changeStatus) (call-state changeStatus, return-state changeStatus) (return-state changeStatus, post-state) Notice that if the constraint is transitive, the relationship also holds for (pre-state, post-state), which is indeed what one would want. Again, in a multithreaded setting, the notion of constraint becomes unclearer, because all intermediate states potentially are visible states. However, a constraint such as that getName returns a constant value could still

56

CHAPTER 6. CRASH COURSE ON JML

be meaningful also in a multithreaded setting (except that the number of possible visible state pairs that have to be considered might grow exponentially). Therefore, in a concurrent setting one could imagine a notion of strong constraints, i.e., a relationship that has to hold for any pair of consecutive states. Variable Declarations So far, the specications that we have seen have not specied anything about the values of an objects instance variables. Typically, these are declared private, and private elements should not be used in (public) specications. The specications should only be written in terms of public elements that are visible outside the class5 . Therefore, in our examples, the pure get-methods were used to specify how the internal state of the object was changed. However, sometimes this is not possible, or not convenient. As an alternative, one can specify that a variable should be spec_public. This means that the variable at specication-level has public visibility, and in particular that the specications can be written in terms of these variables. Example 6.16. If we specify the instance variables of class CStudent to be spec_public, then its constructor can also be specied as follows. class CStudent implements Student { /*@ spec_public */ private String name; /*@ spec_public */ private int credits; /*@ spec_public */ private int status; .... /*@ requires c >= 0; ensures credits == c; ensures status == bachelor; ensures name = n; */ CStudent (int c, String n) { credits = c; name = n; status = bachelor;
In fact, the story is more subtle: the standard Java access modiers can be used to control visibility of the specications, see the JML reference manual [69]. However, for this course it is sucient to consider specications to be publicly visible.
5

6.4. SPECIFYING EXCEPTIONAL BEHAVIOUR } }

57

In Chapter 8, we will see that spec public variables are actually a special instance of model variables that directly represent the underlying private variables. Static Class Specications For all class-level specication constructs, static variants exists. For example, an invariant might restrict the value of a static variable, or a constraint might restrict the evolution of a static variable. All static specications have to be preceded by the keyword static. Since instance methods might change static variables, static invariants and constraints have to be respected by instance methods. In contrast, invariants and constraints that only restrict the instance variables of a method cannot be invalidated by a static method and thus this does not have to be checked explicitly.

6.4

Specifying Exceptional Behaviour

So far, we have only considered normal termination of methods. But in some cases, exceptions cannot be avoided. Therefore JML also allows one to specify explicitly under what conditions an exception may occur. The signals and signals_only clauses are introduced to specify exceptional postconditions. In addition, one can give an exceptional_behavior method specication that expresses that a method must terminate with an exception. The signals clause is part of a method specication. Its syntax is signals (E e) Predicate, and it has the following meaning: if the method terminates because of an exception that is an instance of class E thus, its dynamic type may also be a subtype of E , then the Predicate has to hold. The variable name e can be used to refer to the exception in the predicate. The signals_only clause is also part of the method specication. Its syntax is signals_only E1, E2, .., En, meaning that if the method terminates because of an exception, the dynamic type of the exception has to be a subclass of E1, E2, ..., or En. Example 6.17. Consider for example class Average in Figure 6.4. The specication of method averageCredits states that the method may only terminate normally, or with an ArithmeticException and thus, it will not

58 package chapter2; class Average {

CHAPTER 6. CRASH COURSE ON JML

/*@ spec_public */ private Student[] sl; /*@ signals_only ArithmeticException; signals (ArithmeticException e) sl.length == 0; */ public int averageCredits() { int sum = 0; for (int i = 0; i < sl.length; i++) { sum = sum + sl[i].getCredits(); }; return sum/sl.length; } } Figure 6.4: Class Average throw an ArrayIndexOutOfBoundsException. Moreover, if an ArithmeticException occurs, then in this exceptional state the length of sl is 0. Notice that it is incorrect to use an ensures clause, instead of a signals clause: an ensures clause species a normal postcondition, that only holds upon normal termination of the method. Above, in Section 6.2 we discussed normal_behavior specications. Implicitly, these state that the method has to terminate normally. Similarly, JML also has an exceptional behavior method specication. This species that the method has to terminate, because of an exception. As mentioned above, a behavior specication only enforces that a method terminates, but it does not exclude exceptional termination. Thus a behavior specication may well contain a signals or signals_only clause, whereas a normal behaviour specication may not contain these, and an exceptional behaviour specication may not contain an ensures clause. As mentioned above, a single method can be specied with several method specications, joined with also. Exceptional behaviour specications are typically used in this case. Example 6.18. Consider the more detailed specication for averageCredits

6.4. SPECIFYING EXCEPTIONAL BEHAVIOUR package chapter2; class Average { /*@ spec_public */ private Student[] sl;

59

/*@ normal_behavior requires sl.length > 0; ensures \result == (\sum int i; 0 <= i && i < sl.length; sl[i].getCredits())/sl.length; also exceptional_behavior requires sl.length == 0; signals_only ArithmeticException; signals (ArithmeticException e) true; */ public int averageCredits() { int sum = 0; for (int i = 0; i < sl.length; i++) { sum = sum + sl[i].getCredits(); }; return sum/sl.length; } } Figure 6.5: Class Average in Figure 6.5. This states that if sl.length > 0, i.e., there are students in the list, then the method terminates and the result is the average value of the credits obtained by these students. If sl.length == 0 then the method will terminate exceptionally, with a ArithmeticException. In this example, the two preconditions together cover the complete state space for the value of sl.length. If sl.length could be less than 0, the methods behaviour would not be specied. Finally, it is important to realise that invariants and constraints also should hold when a method terminates exceptionally. This might seem

60

CHAPTER 6. CRASH COURSE ON JML

strange at rst: something goes wrong during the execution, so why would it be necessary that the object stays in a good state. But in many cases, the object can recover from the exception, and normal execution can be resumed. But this means that it is necessary that also when an exception occurs, the object stays in a well-dened state, i.e., a state in which the invariants hold, and that evolves according to the constraints. A Note on False specications like: In exceptional behaviour specications, one often sees

signals (ArithmeticException e) false; This is a way to state that an exception should not occur: if the exception occurs, the property false should hold. And as this is this never the case, the exception may not occur. Similarly, if one species a postcondition ensures false; this states that a method should not terminate normally. Thus a method specication: ensures false; signals (Exception e) false; implicitly says that a method should never terminate (either normally, or exceptionally). Finally, a method can also be specied with a precondition requires false;. This means that the method may not be invoked, as no caller can fulll the precondition of the method.

6.5

Desugaring Multiple Method Specications

Combining dierent method specications with an also is convenient to obtain readable specications. However, to develop tool support, it is better to have a single specication per method. Therefore, a method specication desugaring procedure has been dened [84] that transforms specications in such a way that every method is annotated with exactly one method specication. This desugaring procedure also incorporates invariants, initially clauses and constraints into method specications, where appropriate. For the purpose of System Validation, it is not necessary to understand all the details of the transformation. However, to understand the feedback from the tools that validate the JML specications, it is important to have some idea how this procedure works. For a detailed description, we refer to the paper that describes the desugaring procedure in full detail [84].

6.6. INHERITANCE OF METHOD SPECIFICATIONS The main steps are as follows:

61

Consider the method specications that are combined with also. The complete precondition for the method, i.e., the precondition for which the methods behaviour is described, is the disjunction of the individual preconditions. The (normal or exceptional) postconditions only have to hold if the appropriate precondition was true in the pre-state. Therefore, every postcondition is preceded by an implication about the precondition in the pre-state (using \old). The requirement that the method terminates (either normally or exceptionally) is expressed using a diverges clause. Invariants are added as pre- and postconditions of methods, and as postcondition of the constructors. Constraints are added as postconditions of methods. Invariants and initially clauses are added as postconditions to constructors. Example 6.19. For method averageCredits the desugaring procedure returns the following single method specication (where some obvious simplications can be applied): /*@ behavior requires sl.length > 0 || sl.length == 0; ensures \old(sl.length > 0) ==> \result == (\sum int i; 0 <= i && i < sl.length; sl[i].getCredits())/sl.length; signals_only ArithmeticException; signals (ArithmeticException e) \old(sl.length == 0); diverges sl.length > 0 ==> false; diverges sl.length == 0 ==> false; */

6.6

Inheritance of Method Specications

Above, we have already mentioned that subclasses inherit class-level specications (invariants, initially clauses and constraints). Method specications

62

CHAPTER 6. CRASH COURSE ON JML

are also inherited. Concretely, this means the following: every class that implements an interface has to respect the specications of the interface; and every class that extends a another class has to respect the specications of these class. This means in particular that every method that overrides a method from a superclass should still respect the method specication from the superclass. Any additional specication of the subclass is implicitly combined (with also) with the specications from the superclass6 . The same applies for a method that implements a method declared in an interface. This makes the subclass a behavioural subtype of the superclass [73], i.e., in any context where an instance of the superclass is expected, an instance of the subclass can be safely used, and when you look at it from the superclass perspective, you cannot observe any dierence in behaviour. This idea is crucial for the correctness of object-oriented programs. You can specify the behaviour of a class in an abstract way. For example, if you have an array of students, as in class Average, you know that the concrete instances that may be stored in the array may have dierent implementations, but you know that they all implement the methods specied in the interface Student, respecting the behaviour that is specied for this interface.

6.7

Other Features of JML

In this chapter, we have discussed the most important features of JML. However, it is impossible to cover all of JML, as it is a large language, and not for all syntactic constructs, an appropriate semantics is dened. However, there are still some more constructs that you should know about, which will be introduced in the next chapters. In particular, Section 7.5 introduces ghost variables; Chapter 8 introduces model variables; Section 9.5 introduces assignable clauses; and Section 9.6 introduces assert statements and loop invariants.

6 And the desugaring procedure will explicitly repeat the specications from the superclass.

Chapter 7

Run-time checking of JML Annotations


Now that we have seen how we can specify software using JML, the next question is how to validate that a program indeed respects its specication. We will look at dierent techniques for this. This chapter discusses how the program can be instrumented in such a way that during execution, validity of the specication can be checked. The next chapter discusses how this validity check can be done at compile time, without actually executing the program.

7.1

History and Background

When work on JML started, its initial goal was to support run-time checking of specications. The idea of run-time checking is that every precondition and postcondition is checked by simply executing the predicate and testing whether the outcome of this evaluation is true. Execution of the precondition should be done at an appropriate point in the code: a precondition should be evaluated before the method body is executed; and after the method body has terminated, the postcondition should be evaluated, before control is given back to the caller. It is important to realise that to make this approach work in practice, one needs a desugaring procedure that can translate a bunch of method specications into a single method specication. In this way, for every method a single precondition and a single postcondition test can be implemented. 63

64

CHAPTER 7. RUN-TIME CHECKING OF JML ANNOTATIONS

This approach is often advocated as a good way to test your program. For example, in the text book on object-oriented programming by Ni no and Hosch [81] (used in Programmeren 1 and 2), it is shown how precondition specications can be translated systematically into assert-statements at the beginning of a method body.

7.2

Manually Validating Specications

One of the examples that we will use in this chapter is the class CStudent. This implements the interface Student introduced in the previous chapter by dening an explicit credit eld. In addition, it also contains several extra methods, discussed below. The implementation of class CStudent is given in Figures 7.1, 7.2. Notice that we do not have to repeat the specications that are inherited from Student. A remark on notation: {| ... |} is used for nested specications: the two specications of activityBonus conjoined with also have a common precondition 0 <= bonus && bonus <= 5 && active;. Example 7.1. Consider the method addCredits with the following (inherited) specication. /*@ requires c >= 0; ensures getCredits() == \old(getCredits()) + c; */ Using the approach as advocated by e.g., Ni no and Hosch, a programmer would have to transform this method implementation manually into the following. public void addCredits(int c) { assert c >= 0; credits = credits + c; } Ni no and Hosch argue that one does not have to test for the postcondition, as this is the programmers responsibility. In their view, a programmer would have to use other means to ensure that their own implementation is correct (such as static checking for example, as discussed in the next chapter). However, one can never be sure that the method is called under the right conditions, thus it is important to test for the precondition. If one would wish to test for the postcondition as well, this could be done by adding an additional assert at the end of the method body.

7.2. MANUALLY VALIDATING SPECIFICATIONS package chapter3; import chapter2.Student; class CopyOfCStudent implements Student { private String name; private int credits; private int status; /*@ spec_public */ private boolean active; /*@ requires c >= 0; ensures getCredits() == c; ensures getStatus() == bachelor; ensures getName() == n; */ CopyOfCStudent (int c, String n) { credits = c; name = n; status = bachelor; } public String getName() { return name; } public int getStatus() { return status; } /*@ pure */ public int getCredits() { return credits; } public void setName(String n) { name = n; } Continued in Fig. 7.2 Figure 7.1: Class CStudent with explicit credit eld (1/2)

65

66

CHAPTER 7. RUN-TIME CHECKING OF JML ANNOTATIONS

Continued from Fig. 7.1 public void becomeActive() { active = true; } public void addCredits(int c) { credits = credits + c; } public void changeStatus() { status = master; } /*@ requires 0 <= bonus && bonus <= 5 && active; {|requires getStatus() == bachelor; ensures getCredits() == (\old(getCredits()) + bonus > 180 ? 180 : \old(getCredits()) + bonus); also requires getStatus() == master; ensures getCredits() == \old(getCredits() + bonus); |} // also: other specification for students that are not active */ public void activityBonus(int bonus) { if (active) { addCredits(bonus);} }

} Figure 7.2: Class CStudent with explicit credit eld (2/2)

7.3. REQUIREMENTS FOR A RUN-TIME CHECKER

67

However, inserting all these checks manually is cumbersome and error prone. Besides the amount of work, there are are also more important problems, such as: a method might have multiple exit points, and the postcondition has to be checked at every exit point; a method might terminate exceptionally, to evaluate the (exceptional) postcondition, the state temporarily has to be brought back to normal again; specication-only expressions cannot be used in a Java assert, and encoding them is often quite complex; values of expressions in the pre-state have to be stored explicitly; and class-level specications would give rise to asserts in many dierent places.

7.3

Requirements for a Run-Time Checker

Therefore, it is a logical idea to have tool support for doing this. Bertrand Meyer was the rst to introduce this idea as an integral part of a programming language. To support the Design by Contract approach [77], the Eiel compiler came with a special option to insert these run-time checks in the code. Also JML has a special tool that inserts these run-time checks [25]. Initially, the run-time checker was implemented by inserting explicit checks in the source code. Later, this was adapted, and now explicit tests are added to the bytecode. A run-time checker has to satisfy three important requirements. Run-time checking should be transparent : if all specications are respected, then execution with or without run-time checking enabled should be identical1 . Run-time checking should isolate the source of the problem. A specication violation should be reported when it occurs, and it should relate back to the point where the problem occurred, so that the user can actually trace the problem.
1

Apart from performance, of course.

68

CHAPTER 7. RUN-TIME CHECKING OF JML ANNOTATIONS

package chapter3; class ExecuteCStudent2 { public static void main (String [] args) { CStudent s = new CStudent(0, "marieke"); s.becomeActive(); s.addCredits(178); s.activityBonus(5); System.out.println(s.getCredits()); System.out.println(s.getStatus()); } } Figure 7.3: Method main to execute class CStudent Run-time checking should be trustworthy : if a specication violation is signalled, it should indeed be a real specication violation. Adding run-time checking for a large part is engineering. To ensure that in case of exceptional termination the appropriate checks are executed, the code is wrapped in the bytecode equivalent of a try-catch-finally statement. In the catch block, the appropriate exceptional postconditions (including invariants and constraints) are tested, and if these succeed, then the appropriate exception is rethrown.

7.4

Executing a Run-Time Checker

This section shows how to use the run-time checker to nd a problem in a program. In particular, it is important to make an executable program that steers the program to the point where the specication is violated. Example 7.2. Consider the method activityBonus of class CStudent. If a student is active (for example as a member of the Inter-Actief board), the university can provide him a maximum of 5 bonus credits for this. However, as specied, a Bachelor student can never obtain Master credits with this bonus, i.e., the new credits can never be more than 180. Unfortunately, the programmer did not correctly implement this rule. As mentioned above, we have to make an executable program. Thus, we write a main method, as displayed in Figure 7.3. This rst creates a

7.5. MONITORING SAFETY AND SECURITY PROPERTIES

69

new student object. Then we have to bring the object into a state where the annotation violation will be detected. We rst make the student active by executing becomeActive. Now, if we just call activityBonus once with argument 5, then the annotation violation will not be detected. Instead, we call addCredits, where there is no bound on the credits that can be added. We use this to give the student 178 credits. Then we call activityBonus. If we now execute the program with the run-time checker, this will produce the following error message (with a few line breaks added for readability). Exception in thread "main" org.jmlspecs.jmlrac.runtime. JMLInternalNormalPostconditionError: by method CStudent.activityBonus at chapter3.CStudent.activityBonus (ExecuteCStudent.java:1895) at chapter3.ExecuteCStudent.internal$main (ExecuteCStudent.java:2116) at chapter3.ExecuteCStudent.main (ExecuteCStudent.java:2390) When using the run-time checker to detect annotation violations, it is important that many dierent corner cases are checked. Just as with more traditional testing techniques, the corner cases are the most likely to cause problems. Also, it is important to try to get a good code coverage. By testing executions that cover all possible execution paths, one can increase the condence that the implementation respects the specications. The JML annotations typically guide one in developing these test cases2 . This idea is also exploited in the tool JMLUnit [90]; an extension of JUnit that generates test cases based on the JML annotations, see Chapter 10.

7.5

Monitoring Safety and Security Properties

Many common safety and security properties can be expressed as a simple automaton that describe the legal behaviours, not violating security. Examples of such properties are: at most one le can be opened at the same time; every send message should be preceded by a read message;
2 Assuming that the specications are indeed correctly describing the intended behaviour

70

CHAPTER 7. RUN-TIME CHECKING OF JML ANNOTATIONS protected data may only be accessed by an authorised user; an SMS should only be sent after asking authorisation; and if a pincode is entered incorrectly three times consecutively, a pay card gets blocked.

A commonly used way to guarantee that an application respects such a property is monitoring of the automaton. The idea, originally introduced by Schneider [86], is that the application is run in parallel with the automaton that encodes the desired property. The automaton describes the abstract state of the program. Upon certain dedicated events in the program, typically calls and returns to methods, the automaton makes a transition to a new state. If the automaton reaches a special error state (or is unable to make a transition, depending on how the property is precisely modeled), this means that the property that is being monitored is violated. Thus, to avoid any problems, the application has to be stopped immediately. Notice that this approach in this basic form only works for safety properties. However, in the literature one can nd approaches for monitoring liveness properties, assuming some bounds on when the events eventually have to happen, see e.g., [50]. With JML, a similar monitoring approach can be achieved. A safety property can be encoded as a collection of JML annotations, and violation of the JML annotations during program execution forces the application to terminate. During this course, we will study how we can encode a security property manually. In the literature, also some more systematic translations are developed, see e.g., the AutoJML tool [55] and the work of Huisman and Tamalet [57], who dene a formal translation from security automata to JML annotations and prove this translation correct. Consider the class Card in Figure 7.4. It has a method initializeCode that be used to initialize the pincode. After initializing the card, before a payment can be made, a pincode has to be entered. The enterPincode method returns a boolean, denoting whether this was the correct pincode. If there are three consecutive wrong attempts to enter the pincode, the card gets blocked. Properties such as this one are often called life cycle properties. They are typical examples of the properties that can be encoded with this approach. To encode this, we add ghost variables to the interface. These are specication-only variables that can be updated in the specication, via a special set annotation. Ghost variables are typically used to encode some

7.5. MONITORING SAFETY AND SECURITY PROPERTIES package chapter3; interface Card { void initialiseCard(); boolean enterPin(int pin); void makePayment(); } Figure 7.4: Class Card

71

control information about the state. First, we add (public static final) constants to encode the dierent states (see Figures 7.5 and 7.6 for the complete encoding). We also dene a special state BLOCKED that we actually never want to reach. Then we add a ghost variable state that is used to keep track of the card state. It is initialised to FRESH. We add an invariant that states which values we allow state to have. Notice that here we explicitly do not allow the state to be BLOCKED: if the card enters a blocked state, an annotation violation will be signalled. Next, we add a constraint that species how state transitions can happen: from the FRESH state, one only can move to a READY_FOR_USE state, from READY_FOR_USE, one can move to READY_FOR_PAYMENT and BLOCKED, and when a card is BLOCKED, it will stay blocked forever. Notice that the constraint also allows in all cases not to change state: this is the non-strictness condition on constraints: it should be possible to call a pure method, and to still respect the constraint (even though in this very simple example, it is not strictly necessary). Further, we add an extra invariant, stating that the state can only become blocked if the number of wrong pin attempts is 3. This actually could have been part of the constraint as well, but we felt it was clearer to specify this separately. Finally, to make the implementation respect the specication, state transitions have to be inserted at appropriate places. These correspond to the events that make the automaton transition in the monitoring approach. The state transitions are added here as statement annotations, i.e., annotations that are part of the code, instead of the documentation, and that only serve

72

CHAPTER 7. RUN-TIME CHECKING OF JML ANNOTATIONS

to show correctness of the class documentation. The statement annotations start with the set keyword. Basically, they describe an assignment to a ghost variable. (It is also possible to write more complex statements, using e.g., if provided the statement only has a side eect on the ghost variables. Example 7.3. As mentioned above, the complete result of the annotation process is depicted in Figures 7.5, and 7.6 (remember that method specications that extend a method declared in the interface should start with also, to indicate that they also inherit the method specication from the interface). An advantage of this approach over the traditional monitoring approach is that the JML annotations can be validated in dierent ways. In Chapter 9 we will discuss how annotations can be validated without actually executing the code. Monitoring often has a signicant performance overhead. If we can use static methods to guarantee that certain parts of the code never violate the security or safety properties, then the run-time checks can be avoided in these places thus reducing execution overhead.

7.6

Further Reading

Run-time checking is a commonly used validation technique. In this course, we focus on run-time checking of annotations. Some tools have been developed that apply similar approaches (e.g., Jass for Java applications [61, 7], and Code Contracts for Microsofts C# [32]). Also aspect-oriented and other compositional approaches are used to introduce run-time checks into code by weaving (see e.g., [75, 85]). Most work on monitoring is focusing on security automata (and variants thereof) [86]. Automata are either inlined as run-time checks [2, 45], or a dedicated framework is developed for monitoring them at run-time [63, 62]. Much work on run-time checking also exists for temporal properties, for example the tool JavaPath Explorer has been developed for run-time checking techniques for safety and bounded liveness properties [50]. Finally, another approach using run-time checking is the checking of dedicated properties. If you want to check a single property of your program, e.g., the program does not use too many resources, condential information does not ow to publicly visible variables, or response time of certain actions is not too long, a possible way to guarantee this is to use run-time checking and to break o program execution if the desired property is (or might be) violated, see e.g., [66].

7.6. FURTHER READING

73

package chapter3; class CardImpl implements Card { /*@ ghost ghost ghost ghost ghost public public public public public static final static final static final static final int state; int int int int FRESH = 0; READY_FOR_USE = 1; READY_FOR_PAYMENT = 2; BLOCKED = 3;

ghost public int attempts; */ /*@ invariant state == FRESH || state == READY_FOR_USE || state == READY_FOR_PAYMENT; */ /*@ initially state == FRESH; */ /*@ constraint (\old(state) == FRESH ==> state == FRESH || state == READY_FOR_USE) & (\old(state) == READY_FOR_USE ==> state == READY_FOR_USE || state == READY_FOR_PAYMENT || state == BLOCKED) && (\old(state) == READY_FOR_PAYMENT ==> state == READY_FOR_PAYMENT || state == READY_FOR_USE) && (\old(state) == BLOCKED ==> state == BLOCKED); */ /*@ invariant state == BLOCKED <==> attempts == 3; */ Continued in Fig. 7.6 Figure 7.5: Class CardImpl with an encoding of the live cycle property (1/2)

74

CHAPTER 7. RUN-TIME CHECKING OF JML ANNOTATIONS

Continued from Fig. 7.5 /*@ also requires state == FRESH; ensures state == READY_FOR_USE && attempts == 0; */ public void initialiseCard() { // .. //@ set state = READY_FOR_USE; //@ set attempts = 0; } /*@ also requires state == READY_FOR_USE && attempts < 3; ensures \result ==> state == READY_FOR_PAYMENT && attempts == 0; ensures !\result ==> attempts == \old(attempts) + 1; ensures !\result && attempts < 3 ==> state == READY_FOR_USE; ensures !\result && attempts == 3 ==> state == BLOCKED; */ public boolean enterPin(int pin) { boolean result = false; // ... //@ set attempts = (result? 0 : attempts + 1); //@ set state = (result? //@ READY_FOR_PAYMENT : //@ (attempts == 3 ? BLOCKED : READY_FOR_USE)); return result; } /*@ also requires state == READY_FOR_PAYMENT; ensures state == READY_FOR_USE; */ public void makePayment() { // ... //@ set state = READY_FOR_USE; } } Figure 7.6: Class CardImpl with an encoding of the live cycle property (2/2)

Chapter 8

Abstract Specications
8.1 Model Variables

An important feature of specications is that they provide abstraction over the concrete implementations. In the previous chapters, we have seen interface Student with its (obvious) implementation CStudent. However, a more complicated implementation can be imagined using a list of individual results. Figures 8.1, 8.2 contains such an implementation. Notice that it is on purpose that this class does not implement interface Student. If we would do that, the class would inherit the specication in terms of the accessor methods, but in this chapter we want to illustrate a dierent specication technique in terms of abstract state. This implementation might be correct, but we would typically do not want to expose it to the users of the class. Therefore, we dene model variables that dene an abstract state for the class: they are credits and status. Internally, a dierent implementation is used, but the specications of the methods are given in terms of the abstract state and thus is sufciently abstract for the user to understand. This idea has originally been introduced under the name data abstraction by Hoare [52]. Of course, to make sure that the concrete implementation corresponds to the abstract specication, a link between the two has to be made. For this purpose, the represents clause denes how the value of the abstract variable is dened in terms of the values of the concrete variables. Internally, whenever a specication in terms of abstract variables is encountered, the tools translate this in a specication in terms of the concrete variables, using the represents clause, and then adherence of the implementation w.r.t. this concrete specication is validated. 75

76

CHAPTER 8. ABSTRACT SPECIFICATIONS

class CCStudent { public final static int bachelor = 0; public final static int master = 1; private List<Integer > credit_list; /*@ model public int status; model public int credits; */ /*@ represents credits <- sum(); represents status <- (sum() < 180 ? bachelor : master); */ /*@ ensures credits == 0; ensures status == bachelor; */ CCStudent () { credits = new; status = bachelor; } //@ ensures \result == status; /*@ pure */ public int getStatus() { if (sum() >= 180) { return master; } else { return bachelor; } } //@ ensures \result == credits; /*@ pure */ public int getCredits() { return sum(); } public void addCredits(int c) { credit_list.add(c); } Continued in Fig. 8.2 Figure 8.1: Class CCStudent with an alternative implementation for credits(1/2)

8.1. MODEL VARIABLES Continued from Fig. 8.1 public int sum() { int sum = 0; foreach int i:credit_list { sum = sum + credit_list.get(i); } return sum; } }

77

Figure 8.2: Class CCStudent with an alternative implementation for credits(2/2) Example 8.1. Consider the specication for method getStatus: ensures \result == status, and the represents clause for the model status. Internally, the tool will work with the following specication for getStatus: //@ ensures \result == (sum() < 180 ? bachelor : master); public int getStatus() ... Similarly, the specication for getCredits is translated into the following concrete specication: //@ ensures \result == sum(); /*@ pure */ public int getCredits() ... Sometimes, a represents clause cannot be dened directly as a translation into concrete variables; sometimes a (non-functional) relation between the abstract and the concrete state can be expressed, sometimes only a dependency relation. JML provides a way to dene non-functional represents clauses, as illustrated by the following example (dependency clauses are out of the scope of this course). Example 8.2. Suppose in class CCStudent we add a model variable to represent the list of credits of the student. //@ model public int[] _credit_list; If we want to link this to the concrete state, it means that we have to link the elements of the model variable _credit_list to the elements of the concrete variable credits_list. To do this, we dene the following non-functional represents clause.

78

CHAPTER 8. ABSTRACT SPECIFICATIONS

/*@ represents _credit_list \such_that (\forall int i; 0 <= i && i < credit_list.length; (\exists int j; 0 <= j && j < _credit_list.length; credit_list[i] == _credit_list[j])) & (\forall int j; 0 <= j && j < _credit_list.length; (\exists int i; 0 <= i && i < credit_list.length; _credit_list[j] == credit_list[i])); */ This represents clause expresses that the two arrays have exactly the same elements1 . Example 8.3. Suppose we would add a model variable _credit_list in class CStudent (Figures 7.1 and 7.2). Then we would a represents clause stating that the sum of all elements in the array _credit_list is equal to the concrete variable credits: /*@ represents _credit_list \such_that (\sum i; 0 <= i && i < _credit_list.length; _credit_list[i]) = credits; */ Model variables are useful in many cases. Typical examples are as in the example above, where a non-standard implementation is used (often for performance reasons), for the specication of interfaces (as discussed below), and when dening classes that encode complex values. Example 8.4. A well-known example in the literature is the specication of a class Decimal [18]. The class implements decimal variables using an intPart and decPart variable, but the specication is given in terms of a single model variable that represents the value of the composed decimal number. public static final short PRECISION = (short) 1000; /*@ spec_public */ private short intPart = (short) 0; /*@ spec_public */ private short decPart = (short) 0; //@ model int value; //@ represents value = intPart * PRECISION + decPart;
Admittedly, in this case a functional represents clause //@ represents _credit_list = credit_list would also have worked, but the specication above nicely illustrates the denition of a non-functional represents clause.
1

8.2. MODEL VARIABLES AND INTERFACES

79

8.2

Model Variables and Interfaces

As already mentioned above, the specication approach using model variables is also a convenient way to specify interfaces. The behaviour of an interface is specied in terms of model variables, and the classes implementing the interface dene represents clauses for these model variables, relating them to their own concrete implementation. Because of the exible connection between concrete and abstract state using the represents clause, this does not impose any restriction on the internal state of a class implementing the interface. Example 8.5. Figure 8.3 gives an alternative specication for interface Student, using model variables. Figure 8.4 shows the specication for class CCStudent when it implements this interface. Note that compared with the implementation in Figures 8.1 and 8.2, it does not declare the model variables, but only denes the represents clause. Also, the specications of getStatus, getCredits and addCredits are inherited from the interface, and do not have have to be repeated.

8.3

On Spec Public Variables

In Chapter 6 spec public variables have been introduced, in order to use private variables in specications. In fact, declaring a variable x as spec_public implicitly is equivalent to declaring a model variable, say _x, and a represents clause represents _x <- x. Thus in particular, from the class users point of view, the two specications should be equivalent, and the specication only gives an abstract view on the implementation.

8.4

Model versus Ghost Variables

It is important to understand the dierence between model and ghost variables. Both are variables that are used for specication-purposes only, and they do not occur during the execution of the program. However, model variables provide an abstract representation of the state. If the underlying state changes, implicitly the model variable also changes. Often it is possible to dene this relationship explicitly as a translation, but sometimes it can only be given in a non-constructive manner (or even as a dependency relation).

80

CHAPTER 8. ABSTRACT SPECIFICATIONS

public interface Student { /*@ model public int status; model public int credits; */ /*@ invariant status == bachelor || status == master; invariant credits >= 0; public static final int bachelor = 0; public static final int master = 1; /*@ pure */ public String getName(); //@ ensures \result == status; /*@ pure */ public int getStatus(); //@ ensures \result == credits; /*@ pure */ public int getCredits(); //@ ensures getName().equals(n); public void setName(String n); /*@ requires c >= 0; ensures credits == \old(credits) + c; */ public void addCredits(int c); /*@ requires credits >= 180; requires status == bachelor; ensures credits == \old(credits); ensures status == master; */ public void changeStatus(); } Figure 8.3: Interface Student with model variables

8.4. MODEL VERSUS GHOST VARIABLES

81

class CCStudent implements Student { private List<Integer > credit_list; /*@ represents credits <- sum(); represents status <- (sum() < 180 ? bachelor : master); */ /*@ ensures credits == 0; ensures status == bachelor; */ CCStudent () { credits = new; status = bachelor; } public int getStatus() { if (sum() >= 180) { return master; } else { return bachelor; } } public int getCredits() { return sum(); } public void addCredits(int c) { credit_list.add(c); } // rest of class continued... }

} Figure 8.4: Class CCStudent implementing the Student interface of Figure 8.3

82

CHAPTER 8. ABSTRACT SPECIFICATIONS

In contrast, ghost variables extend the state. They provide some additional information that cannot be directly related to the object state. Ghost variables are often used to keep track of the events that have happened on an object, e.g., which methods have been called, how often have these methods been called etc. There also exists work where ghost variables have been used to keep track of the resources used by the program: every time a new object is created, there is an associated set-annotation that increases a resource counter, modelled as a ghost variable [9]. In this way, the specication can state something about the number of objects that have been created by the program. This information allows then to dene a resource analysis over the application.

8.5

Model Methods and Classes

Besides model variables, JML has some more advanced specication-only constructs. In particular, one can dene model methods and model classes. For completeness, we give a brief description of those constructs. Sometimes, to complete a specication, one needs a method that only is intended for specication. To support this, JML provides model methods. A model method is dened as part of the specication. It can be implemented, but it may also be abstract. And the behaviour of a model method is typically dened in terms of its pre- and postconditions again. Typical usages for model methods are: if the specication needs a method that is not related to the code, for example to sum all the elements in an array; if the specication needs a method that cannot be implemented easily, but that can be specied without any problem. Further, JML also allows to dene model classes, i.e., classes that are used in the specication only. In particular, JML comes with a library of model classes. These dene classes that encode typical mathematical objects, such as sets, functions and bags. They are intended to allow the user to give nice mathematical specications of other classes. In particular, since they are dened as Java (JML) classes, it is possible to use them as type for a model variable, and still relate them to concrete state. Support to reason about them in mathematically easy way, however, is still a subject of research (see e.g., [24, 38] for some work in this direction).

Chapter 9

Static Checking of JML Annotations


Run-time checking is a useful technique to get quick feedback on whether an application respects its (JML) annotations. However, the drawback of it is that in general it cannot give a 100 % correctness guarantee. For almost all realistic programs, it is impossible to get complete coverage by exploring all possible program execution paths. And even when the programs state space is nite, and exploring all paths might be possible in principle, the performance overhead will be enormous. If we want to have such a high correctness guarantee, we need to use different validation techniques. Program logics have been developed to reason about programs, without actually executing them.

9.1

History and Background

The idea of using a program logic and pre- and postconditions to reason about programs dates back to the sixties. Floyd was the rst to introduce the concepts of pre- and postconditions, and thinking about program behaviour in an non-executional way. This led Hoare in 1969 to come up with a concrete set of rules to reason about programs [53]. These rules (and any variations thereof) are often called Hoare logic. However, Hoare logic as it is, is not directly suited for developing tool support, because one needs to invent certain intermediate predicates. However, in 1976, Dijkstra observed that it was actually not necessary to invent these intermediate predicates [40]; instead one could compute a weakest precondition, which would be sucient to ensure the required postcondition. 83

84

CHAPTER 9. STATIC CHECKING OF JML ANNOTATIONS

This idea to use weakest precondition computation forms the basis for several verication tools for Java, such as Loop [13], Krakatoa [76], Key [11]1 , Everest [21], and ESC/Java [33]. Also for other languages similar tools exist, e.g., Code Contracts [74] and Spec# [5]. In particular, at the end of the nineties, several people were working on verication of Java programs. Java was a nice target language to reason about, because it had a reasonably well-dened semantics, described in the language specication [48]. Initially, the tools that were being developed used dierent specication languages, but quickly JML emerged as the language of choice for all these tools. Eort has been put in unifying the tools, so that they all use the same specication language (possibly with some tool-specic extensions, preceded by a special keyword).

9.2

A Quick Overview of Hoare Logic

Before discussing a static verication tool, we rst give a short overview of the theoretical background, discussing the most important aspects of Hoare logic and Dijkstras weakest precondition calculus. Given a precondition P , a postcondition Q and a statement S , a Hoare triple {P }S {Q} has the following meaning: if statement S is executed in a state x satisfying precondition P , and if execution of statement S terminates in some state y , then this nal state satises postcondition Q. Notice the correspondence with the meaning of requires and ensures clauses in JML: these are just a more verbose way to write Hoare triples for methods. The main idea of Hoare logic is to break down the correctness guarantee of complex statements into correctness guarantees for the components of these statements. Concretely, suppose we compose two statements S1 and S2 , and we want to show that {P }S1 ; S2 {Q}, i.e., if execution starts in a state satisfying P , then after termination of S1 ; S2 , Q should hold. Then it is sucient to nd an intermediate predicate R and to show {P }S1 {R} and {R}S2 {Q}. Thus, in this way correctness of the composed statement is reduced to a correctness problem of the components of the composed statement.
1 In fact, this uses an extension of Hoare logic, called dynamic logic, but the underlying principles are basically the same.

9.2. A QUICK OVERVIEW OF HOARE LOGIC As a proof rule, this is expressed as follows: {P }S1 {R} {R}S2 {Q}

85

{P }S1 ; S2 {Q} This should be read as follows: in order to prove the correctness triple below the line, it is sucient to prove the correctness triples above the line. Another example of a Hoare logic proof rule is the rule for the conditional statement (if-then-else). {P c}S1 {Q} {P c}S2 {Q}

{P }if c then S1 else S2 {Q} I.e., if the condition c holds, then provided the precondition P holds, the then-branch S1 has to guarantee the postcondition Q, if the condition c does not hold, then the else-branch S2 has to guarantee the postcondition. A more interesting rule is the assignment axiom. This states that an assignment x:=E guarantees postcondition Q, if before the assignment Q[x\E ] was true, i.e., Q where any occurrence of x replaced by the expression E was true. This is easiest understood with an example. Suppose that you have an assignment x := y + 2;. If afterwards you want that x > 0, then this only can be ensured if the value that is assigned to x was greater than 0, i.e., the precondition has to be y + 2 > 0. Formally, this is expressed by the following rule (where [x\E] denotes substitution of x by E formal denition of substitution falls out of the scope of this course). {Q[x\E ]}x := E {Q} Finally, there are rules for method declarations that state that to prove correctness of a method specication w.r.t. the method implementation, one has to prove correctness of the method body2 . {P }body {Q} {P }void m() {body }{Q} Key feature of Hoare logic is that it allows one to prove something about a method for all possible input states and all possible arguments, without executing the code.
2 This rule comes in many variations, for rules with and without return values, parameters etc., but the basic idea is always the same.

86

CHAPTER 9. STATIC CHECKING OF JML ANNOTATIONS

Example 9.1. As an example, we specify that the body of a method swap always swaps the values of the variables x and y, whatever their initial values. In JML, the specication for this method would be the following: //@ ensures x == \old(y) && y == \old(x); void swap () { int t = x; x = y; y = t; } In classical Hoare logic, this would be specied as follows: {x = A y = B }swap(){x = B y = A} A and B are often called logical variables. Implicitly, the correctness triple holds for all possible values of A and B , and as demonstrated by the specication above, their typical use is what is expressed using the \old keyword in JML. Example 9.2. The correctness proof for method swap in Example 9.1 roughly looks as follows3 :

{x = A y = B t = A}x = y {x = B y = B t = A} {x = A y = B }int t = x{x = A y = B t = A}

{x = B y = B t = A}y = t{x = B y = A}

{x = A y = B t = A}x = y ; y = t{x = B y = A} {x = A y = B }swap(){x = B y = A}

The Hoare triples as we have seen so far describe partial correctness relations : if a method terminates, its postcondition will be established. Sometimes one wishes to specify explicitly that a method must terminate. For this purpose, a total correctness relation is dened. For this we use the notation [P ]S [Q]. Such a total correctness triple should be read as follows: if we execute statement S from a state x in which precondition P holds, then execution of statement S will terminate in a state y , and in this state y , postcondition Q holds.
In fact, a completely formal proof would also require the use of weakening and strengthening rules. That falls out of the scope of this course; for this we refer to the course on Program Verication.
3

9.3. MECHANISING HOARE LOGIC

87

9.3

Mechanising Hoare Logic

As mentioned above, Hoare logic as it is, is not directly suited for developing tool support, because one needs to invent the intermediate predicates that hold between the composition of two statements. However, as Dijkstra observed [40], it is actually not necessary to invent this intermediate predicate. Instead one can compute the weakest predicate that ensures the required postcondition. It is then sucient to show that the specied precondition implies this weakest precondition. This computation of the weakest precondition is expressed by the rules from the weakest precondition calculus, where wp(S, Q) denotes the weakest predicate such that {wp(S, Q)}S {Q} is a correct triple: wp(S1 ; S2 , Q) = wp(S1 , wp(S2 , Q)) wp(x:=E, Q) = Q[x\E ] wp(if c then S1 else S2 , Q) = c wp(S1 , Q) c wp(S2 , Q) Thus, instead of inventing the intermediate predicate for a statement composition, the weakest precondition calculus computes it. Finally, to show that a method implementation respects its specication, one has to do the following: given precondition P , postcondition Q and method body B , compute wp(B, Q) and show that P wp(B, Q). Notice that both the predicate P and the weakest precondition of the body and the postcondition are predicates in rst-order logic. For proving properties in rst-order logic, many dierent automated theorem provers exist (e.g., Z3 [89], CVC3 [34], and Simplify [39] are all used as backend provers for program veriers). Thus, by implementing the rules of the weakest precondition calculus, and using an (or multiple) automated rst-order theorem prover(s) for the generated proof obligations, an automated program verication tool can be built that allows one to prove that for any possible input state and any possible input parameters, a method respects its specication.

9.4

Automated Program Verication for Java

To apply this approach on a realistic programming language, such as Java, the rules have to be adapted for side eects, exceptions and other sources of abrupt termination, dynamic method binding etc., following the Java semantics, see e.g., [56, 59, 78]. During the System Validation course, we will use the ESC/Java tool, because it is one of the most well-established tools to reason about Java,

88

CHAPTER 9. STATIC CHECKING OF JML ANNOTATIONS

with a high degree of automation, and a reasonable coverage of the language. However, the issues discussed in this chapter in general also apply to the other program verication tools mentioned above. ESC/Java initially started as a tool that would give fast feedback, by compromising on soundness and completeness. Soundness means that anything that can be derived with the tool, is indeed correct (i.e., if the tool says that all methods respect their specication, then this should indeed by the case). Completeness means that a tool will not give false warnings: if a method respects its specication, the tool should be able to establish this. By giving up on soundness and completeness, it becomes much easier to develop an automatic and ecient verication tool that works correctly in almost all most cases. Concretely, instead of applying a full weakest precondition calculus, at some points some shortcuts were taken, to be able to give quick feedback, without making large demands on the user. In particular, initially there was no support for reasoning about loops every loop would simply be approximated by a single iteration in the verier. However, development of ESC/Java has continued, and now the tool is mostly sound and complete. Some known sources of incompleteness are the handling of the ranges of primitive types, and a not fully sound and complete semantics of class invariants. However, in practice the results of the tool are very good, and if ESC/Java does not produce warnings for a Java application, then it is highly likely to respect its specication.

9.5

Reasoning about Method Calls

An important feature of Hoare logic-like approaches is that the verication is modular. Each method is veried in isolation, and any method call inside a body is approximated by its method specication. Example 9.3. Suppose we verify method activityBonus from class CStudent (in Figure 7.1)4 . /*@ requires 0 <= bonus && bonus <= 5 && active; {|requires getStatus() == bachelor; ensures getCredits() == (\old(getCredits()) + bonus > 180 ? 180 : \old(getCredits()) + bonus); also requires getStatus() == master;
4

Recall that the {|...|} notation is briey explained in Chapter 7.

9.5. REASONING ABOUT METHOD CALLS

89

ensures getCredits() == \old(getCredits() + bonus); |} */ public void activityBonus(int bonus) { if (active && getCredits() + bonus <= 180) { addCredits(bonus); } else { setCredits(180); } } This implementation calls method addCredits. Instead of inlining the implementation of addCredits, the verication uses the specication of addCredits. /*@ requires c >= 0; ensures getCredits() == \old(getCredits()) + c; */ public void addCredits(int c); Suppose that we are verifying the case where getStatus() == bachelor && active && getCredits() + bonus <= 180. To guarantee the postcondition, one has to show the following: 0 <= bonus && bonus <= 5 && active && (precondition ) getStatus() == bachelor && \old(getCredits()) + bonus <= 180 ==> (precondition ) getCredits() == \old(getCredits()) + bonus ==> (specication of addCredits) getCredits() == \old(getCredits()) + bonus (postcondition ) In addition, one also has to show that the conjunction of the precondition of activityBonus and the if condition implies the precondition of addCredits (credits >= 0), which is clearly the case. As a proof rule, this can be expressed as follows (where we use Pre m (x) and Post m (x) to denote the pre- and the postcondition of method m with the actual parameters x correctly instanstiated, respectively. P Pre m (x) P Post m (x) Q

{P }m(x){Q} However, things are not always that simple, as is illustrated by the following example.

90

CHAPTER 9. STATIC CHECKING OF JML ANNOTATIONS

Example 9.4. Consider class Point in Figures 9.1 and 9.2, and the class Line in Figure 9.35 . For convenience, only a minimal amount of specications has been given, as this is sucient to illustrate the problem. When we verify method stretchLine in class Line, we use the specication of method moveHorizontal, as explained above. This seems to be sucient but it is not! Because the specication of moveHorizontal does not state anything about what happens with the value of the y eld and thus, we cannot assume anything about y after the call to moveHorizontal. As we cannot assume anything, we cannot be sure anymore that the line is horizontal, and thus the postcondition of stretchLine cannot be formally established. This problem is known as the frame problem [16, 79]. Basically, the point is that for modular verication one needs to know what is the frame of a method, i.e., what are the variables that may be changed by the method, and what is the anti-frame, i.e., which variables may not be changed by the method. To specify this, JML uses assignable clause. This provides a list of variable locations that may be modied by a method (thus, it may be an overapproximation of the actual set of locations that is modied by the method). By default, any pure method should have an empty assignable clause. An assignable clause can also denote a set of locations; typical examples are \nothing (the empty set, thus basically a pure method), \everything (any location might be changed), and a[i..j], all elements in the array between indices i and j. Example 9.5. In Example 9.4 above, we of course should add a clause assignable x; to the specication of moveHorizontal, thus implicitly saying that y may not be changed by this method. Notice that specications such as for getX and getY are really necessary in this case. The specications in Line for the points begin and end use these methods. The specications of getX and getY specify how these methods relate to the variables x and y in class Point. Without this specications, the verication of stretchLine cannot deduce that the outcome of getY has not changed6 . Again, it is important to realise that ESC/Java
In class Line, we explicitly add non_null annotations. This should not be necessary, as non-nullity is the default in JML (as explained in Section 6.2). Unfortunately, ESC/Java does not support this. 6 Of course, the specications in class Line also could be written in terms of begin.x, begin.y etc. In that case, the specications for getX would not be necessary.
5

9.5. REASONING ABOUT METHOD CALLS

91

package chapter5; public class Point { private /*@ spec_public */ int x; private /*@ spec_public */ int y; Point(int x, int y){ this.x = x; this.y = y; } //@ ensures \result == x; /*@ pure */ int getX() { return x; } //@ ensures \result == y; /*@ pure */ int getY() { return y; } void setX(int x) { this.x = x; } void setY(int y){ this.y = y; } void move(int dx, int dy) { x = x + dx; y = y + dy; } Continued in Fig. 9.2 Figure 9.1: Class Point

92

CHAPTER 9. STATIC CHECKING OF JML ANNOTATIONS

Continued from Fig. 9.1 assignable x; ensures x == \old(x) + dx; */ void moveHorizontal(int dx) { x = x + dx; } /*@ requires dy >= 0; ensures y == \old(y) + dy; */ void moveVertical(int dy) { y = y + dy; }

} Figure 9.2: Class Point uses only the specications of getX and getY for verication, it does not look at their implementation. Of course, in Example 9.4, it would be possible to add a postcondition getY() == \old(getY()). But in general this is not a satisfactory solution: a class might have many variables and only a few are typically changed by a method. Moreover, when a new variable is added, for every method that does not change it, an additional postcondition about this variable not being changed would have to be added. As one can imagine, this is error-prone, and leads to overly verbose specications. Using assignable clauses, we can formulate a weakest precondition rule to verify method calls7 . Suppose that the call x.m() resolves to a method with precondition Pre m , postcondition Post m and assignable clause Am , then roughly the rule looks as follows. wp(x.m(), Q) = Pre m (v Am .Post m Q) The easiest way to remember is that every method call to m gives rise to two proof obligations:
7

Similar rules exist of course for rules with return value, parameters etc.

9.5. REASONING ABOUT METHOD CALLS

93

package chapter5; public class Line { /*@ non_null */ Point begin; /*@ non_null */ Point end; //@ requires b != null && e != null; Line(Point b, Point e){ begin = b; end = e; } //@ ensures \result == (begin.getY() == end.getY()); /*@ pure */ boolean isHorizontal () { return (begin.getY() == end.getY()); } /*@ requires dx >= 0 && isHorizontal(); ensures isHorizontal(); */ void stretchLine (int dx) { end.moveHorizontal(dx); } } Figure 9.3: Class Line

94

CHAPTER 9. STATIC CHECKING OF JML ANNOTATIONS The postcondition of the method call has to ensure the postcondition Q, where for all variables in the assignable clause of the method, nothing is known about their values, except what is specied in the postcondition of m. The weakest precondition of the code preceding the method call w.r.t. the precondition of m has to be implied by the precondition P . This ensures that when the method m is called, its precondition will indeed hold.

9.6

Statement Annotations - Helping the Verier

Sometimes, the program verier has to get some guidance. In exceptional cases, a complex intermediate predicate has to be given explicitly, using an @assert P ; annotation. This is for example necessary when complex calculations are made, and the automated theorem provers need some guidance on how to reason about them. Every @assert P annotation gives rise to two proof obligations: the precondition of the method that is being veried has to imply the weakest precondition of the code preceding the assertion and postcondition P ; P has to imply the weakest precondition of the code following the assertion and the postcondition. For straight-line code, such annotations are almost never necessary. One exception in the literature is the verication of addition and multiplication of class Decimal [18]. However, for code that contains loops, such annotations are almost always necessary. To explain this, we rst present the Hoare logic rule for while-loops. {c I }S {I } {I }while c do S {C I }

This rule features a loop invariant I : a predicate that is preserved by every iteration of the loop. To show that the invariant is preserved by every iteration of the loop, one has to show that: if the condition holds and thus the loop body will be executed once more and if the invariant holds before the loop body

9.6. STATEMENT ANNOTATIONS - HELPING THE VERIFIER is executed, then it will also hold after the loop body has terminated.

95

From this we can conclude that if the whole loop is executed in a state in which the loop invariant holds, then after termination of the loop, the loop invariant still holds, and in addition the negation of the loop condition holds8 . In general, such a loop invariant cannot be found automatically. Instead the user is supposed to specify it (nevertheless, for simple loops ESC/Java can make some good guesses). Specifying a loop invariant basically gives rise to three proof obligations: the precondition of the method has to be sucient to guarantee that when the loop starts, the loop invariant holds, e.g., if the loop is preceded by a statement S1 , then P wp(S1 , I ) has to hold; the loop body has to preserve the loop invariant, thus c I wp(S, I ); c I has to imply the weakest precondition of the code after the loop body and the postcondition of the method, i.e., if the loop is followed by a statement S2 , then c I wp(S2 , Q) has to hold. To make sure that ESC/Java correctly checks the loop invariant, it should be called with the -loopSafe option. Example 9.6. Figure 9.4 shows two examples of non-trivial loop invariants. The rst method (third_power) computes n3 without actually using the power function. Its loop invariant describes the intermediate values for all local variables. The second method (search) checks whether a given value occurs in an array. The loop invariant expresses that found is true if and only if the value was among the elements in the array that have been examined so far. Loop invariants as for the method search show a very common loop invariant pattern for methods iterating over an array. All the elements that have been examined so far respect a certain property, and since the loop terminates when all the elements in the array have been examined, from this loop invariant and the negation of the loop condition we can conclude a property for all the elements in the array. The loop invariant restricting
8 In fact, to reason about Java, variations of this rule exist, allowing to reason about loops that terminate abruptly e.g., because of an exception or a return statement.

96

CHAPTER 9. STATIC CHECKING OF JML ANNOTATIONS

package chapter5; public class LoopExamples { /*@ requires n >= 0; ensures \result == n * n * n; */ public int third_power(int n) { int u = 0; int v= 1; int w = 6; int k = 0; //@ loop_invariant 0 <= k && k <= n; //@ loop_invariant u == k * k * k; //@ loop_invariant v == 3 * k * k + 3 * k + 1; //@ loop_invariant w == 6 * (k + 1); while (k < n) { u = u + v; v = v + w; w = w + 6; k = k + 1; } return u; }

/*@ requires a != null; ensures \result == (\exists int i; 0 <=i && i < a.length; a[i] == val); */ public boolean search(int [] a, int val) { boolean found = false; int i = 0; /*@ loop_invariant found == (\exists int j; 0 <= j && j < i; a[j] == val); loop_invariant 0 <= i && i <= a.length; loop_invariant a != null; */ while (i < a.length & !found) { if (a[i] == val) {found = true;} i++; } return found; } } Figure 9.4: Loops with non-trivial loop invariants

9.7. TERMINATION

97

the range of the value of the loop variable i (0 <= i && i <= a.length) is typically always needed, but not sucient alone. Notice that a loop invariant could contain an \old(E ) expression. This refer to the value of the expression E before the method started, not to the value of E at the previous iteration of the loop. Example 9.7. Consider the class Resources in Figure 9.5. The ghost variable countThings counts how many instances of class Thing have been created. The expression \old(Things.countThings) in the loop invariant of method makeThings refers to the value of Things.countThings before method execution started. As a side remark, if a loop invariant is specied, the JML run-time checker will also validate this loop invariant during its execution. However, only when using a static checker it is always necessary to specify a loop invariant. The run-time checker validates a concrete execution, and will thus iterate over the loop as long as necessary for that particular execution. In contrast, a static checker does not know in advance how many times a loop will be iterated. Therefore, it abstracts the loop by the loop invariant, and independently checks that it respects the loop invariant.

9.7

Termination

Finally, static checking can also be used to prove termination of methods. One can consider termination as a liveness property, and thus this is completely out of reach for a run-time assertion checker. In Section 6.2 we have discussed that methods could be specied with a normal_behavior keyword, implicitly saying that the method would have to terminate. For straight-line code without loops, proving termination is obvious. However, for code with loops, again the user has to provide some information. Above we saw that a variant of Hoare logic exists, dening total correctness. For most statements, the rules for partial correctness and total correctness are identical. However, for loops the total correctness rule is dierent. In addition to the invariant I , it also uses a variant expression V . This variant expression has to be well-founded, i.e., it cannot decrease forever. For every iteration of the loop, one has to show that the variant is strictly decreasing (for this purpose, in the rule a logical variable v is used to remember the old value of the variant). Since the variant is a well-founded expression, it cannot decrease forever, and thus the loop has to terminate.

98

CHAPTER 9. STATIC CHECKING OF JML ANNOTATIONS

class Thing { //@ public static ghost int countThings= 0; //@ ensures countThings == \old(countThings) + 1; public Thing() { //@ set countThings = countThings + 1; } } public class Resources { public static final int MAX = 26; //@ invariant Thing.countThings <= MAX; /*@ requires p >= 0; requires \old(Thing.countThings) + p <= 26; ensures Thing.countThings == \old(Thing.countThings) + p; */ public void makeThings(int p) { Thing[] storage = new Thing[p]; //@ loop_invariant 0 <= i && i <= storage.length; /*@ loop_invariant Thing.countThings == \old(Thing.countThings) + i; */ for (int i = 0; i < storage.length; i++) { new Thing(); } } public static void main (String [] args) { Resources r= new Resources(); r.makeThings(13); } } Figure 9.5: Class Resources

9.8. FURTHER READING

99

[c I V = v ]S [I V < v ] [I ]while c do S [c I ] Just as for the invariant, the loop variant in general cannot be found automatically: it has to be specied explicitly by the annotater/developer (although there are also several tools available that can suggest variants). Example 9.8. Consider again the loops in Figure 9.4. If we wish to show in addition that the loops terminate, we have to add variants, using the JML decreasing keyword. For third_power an appropriate choice would be: decreasing n - k;, for search, decreasing a.length - i; would work. A similar approach as for loops is also used to reason about correctness and termination of recursive calls.

9.8

Further Reading

The static verication approach so far focuses on sequential programs. To reason about multithreaded programs one needs techniques to specify that one threads behaviour cannot inuence the behaviour of another thread. A possible approach for this is the use of concurrent separation logic, another extension of Hoare logic, that allows to explicitly talk about which parts of the heap are accessed by a code fragment, see e.g. [58, 60, 70, 41].

100

CHAPTER 9. STATIC CHECKING OF JML ANNOTATIONS

Chapter 10

Test Case Generation from JML Specications


Chapter contributed by Wojciech Mostowski. Both formal specications, in particular JML ones, as well as the verication tools can be utilised for generating and executing unit tests for Java programs. As for formal specications, the Runtime Assertion Checking can be promoted to proper unit testing. In this case, tests are generated automatically from the JML specications freeing the developer from tedious test writing. However, in most cases, it is still the developers responsibility to provide the actual test data. Here, the tools for static checking and verication of Java code can provide further help. Such tools recognise all possible execution paths of a program. In turn, they provide concrete test data for each of the paths ensuring high test coverage. The following sections explain the details and discuss the corresponding tool support.

10.1

From JML Specications to Unit Tests

A unit test is a dedicated code fragment to test some element/unit of a Java program, for example a single method. Such a unit test prepares suitable test data, executes the program (method) under test on this data and then evaluates the outcome to check the correct behaviour of the program. This last step is often called executing the test oracle. To ease the process of writing the tests and executing them in an organised way, a few unit test frameworks and libraries have been developed for Java. The two most 101

102

CHAPTER 10. TEST CASE GENERATION FROM JML

popular ones are JUnit1 and TestNG2 . The process of running a unit test is very resemblant of the RAC procedure described in Chapter 7. In RAC each call to a method is proceeded by a check where the methods precondition is evaluated and followed by a check where the methods postcondition is evaluated. A failed precondition check indicates that the method is called in an incorrect setting/environment. A failed postcondition check indicates that the method implementation itself is not compliant with the specication. The two keywords here are environment and compliant. In the unit testing terminology the rst one refers to the preparation of the test data, the second one to the execution of the test oracle. The procedure for generating tests is now quite straightforward. A test generated for a single method annotated with JML essentially performs the following steps for each single specication case: Given the provided test data, the precondition is evaluated. A false evaluation indicates that the test data is not suitable for the test run (remember, according to JML semantics the behaviour of the method outside of a precondition is undened). In this case the rest of the test is considered meaningless and is simply skipped. A true evaluation of the precondition indicates that the test data is indeed suitable for the test. The method is then executed on the provided test data. In the nal step the postcondition is evaluated, that is, the test oracle is executed. A true result indicates a test success, a false result a test failure. Example 10.1. Consider the following (rather articial) JML specied method for modifying an integer: /*@ public normal_behavior requires param >= 0; ensures \result >= 10; also public normal_behavior requires param < 0; ensures \result < -10; @*/ public int makeHole(int param) {
1 2

http://www.junit.org http://testng.org

10.2. TEST DATA if(param >= 0) { return param + 10; }else{ return param - 10; } }

103

Two disjoint specication cases are provided with simple postconditions. The outcome of the method only depends on the method parameter param. Assume the generated test is run with param equal to 0. In the second specication case the precondition evaluates to false, the remaining test for this specication case is considered meaningless. For the rst specication case the precondition is true, the method is executed with param set to 0 and then the postcondition \result >= 10 is evaluated to true giving a successful test run. For any given JML annotated method the described procedure can be easily generated automatically by a tool. One such tool is JMLUnitNG [90]. Later in Section 10.5 some more technical details about this tool are given, in the following its general features are described. The easiest part in the test generation process is the actual specication checking the tests generated by JMLUnitNG employ a RAC library for pre- and postcondition checking. There are, however, still two problems remaining. The rst one is how to actually also generate the test data (in the example above trivial test data was proposed, but it was not explained where it came from). The second one is the suitability of certain JML constructs for run-time checking complex JML expressions cannot be eciently checked at run-time and are often ignored by the RAC library, in turn the resulting test might be rather poor. These two issues are addressed in the corresponding sections that follow.

10.2

Test Data

JML specications explicitly state what conditions have to be met for a successful run of the method, but they do not state what the test data should be, at least not explicitly. In example 10.1 value 0 for param was chosen rather arbitrarily (although sensibly too). Without analysing neither the specication nor the code under test the other sensible test values for an integer parameter would be 1, 1, and the border minimal and maximal values for the integer data type, Integer.{MIN, MAX} VALUE. In the example this is more than sucient. The test for 0 and 1 fully exercises the rst

104

CHAPTER 10. TEST CASE GENERATION FROM JML

specication case and the respective if branch in the code. Correspondingly, the test for 1 covers the other possible case. Moreover, tests for the minimum and maximum integer value reveal a problem. Because of overow the corresponding postconditions evaluate to false. Whatever the intention of the specier and the implementer of this method was, either the code or the specication has to be altered to treat these borderline cases. For example, one could state that the method only works for relatively small integers: requires param > -1000 && param < 1000; A rerun of the test with the altered specication would give three test cases (param equal to 1, 0, or 1) all of which should pass. For primitive data types, like integers or booleans, such arbitrary choice of xed values already produces reasonable tests. By default this is what JMLUnitNG does, the predened set of integer test values in the current version is {Integer.MIN VALUE, 0, Integer.MAX VALUE}3 . In many testing scenarios, however, these default xed values are not enough to provide decent test coverage. For this reason one of the outputs of the test generation process as performed by JMLUnitNG are stub classes where user provided test data can be entered to extend the default set of test values. A separate stub is provided for each single test input. For the makeHole example the stub for the param values is provided in the le makeHole int param 0 param.java with the following placeholder for user provided values: public RepeatedAccessIterator <?> localValues() { return new ObjectArrayIterator <Object > (new Object[] { /* add local -scope int values or generators here */ }); } To test makeHole with values 1, 2, 3, 4 the stub simply becomes: public RepeatedAccessIterator <?> localValues() { return new ObjectArrayIterator <Object > (new Object[] { 1, 2, 3, 4 }); }
3 It is not entirely clear why 1 and 1 are not included in this default set as they used to be in the older versions of JMLUnitNG.

10.2. TEST DATA

105

JMLUnitNG is also capable of providing some simple guesses as to which values might be useful to test based on the contents of the specication and the code. Two command line options can be used for that, --literals and --spec-literals. Correspondingly, they scan the code and the specication for any occurrence of primitive data type literals and include the identied values in the generated data stubs. For the makeHole example running the test generation process with both options results in the following data stub for param: public RepeatedAccessIterator <?> localValues() { return new ObjectArrayIterator <Object > (new Object[] { -10, 0, 10, /* add local -scope int values or generators here */ }); } (The default minimum and maximum integer values are always included behind the scenes.) However, this improvement is still not ideal. It merely provides a text based recognition of the specication and the code, but it does not provide any structural analysis of either. Employing powerful code analysis can improve this further, as discussed below in Section 10.4. So far only generation of test data for primitive types have been discussed. All kinds of reference types, for example generating arrays or object instances of some complex Java type, pose further challenges. The core problems here are the following: The code sequence to generate a given object might be far from trivial or unequivocal. For example, generating an ArrayList (see java.util package) of objects can be done in several ways, either by using one of the dedicated constructors that also initialise the list, or by using a default constructor to create an empty list and calling the method add several times to add single objects to the list. Furthermore, some classes may not oer constructors at all, for example classes whose instances are singleton or are controlled by factory classes. Even if an instance of a given class can be in general produced by some code, it may be required that the result fulls a certain property, so that e.g., some condition is (not) satised. This is best illustrated with the following examples. Example 10.2. Consider two classes, Person and Date with a method

106

CHAPTER 10. TEST CASE GENERATION FROM JML for checking if a given date is the given persons birth date, specied by: /*@ public normal_behavior requires true; ensures \result == p.getBirthDate().equals(d); @*/ public boolean checkBirthDate(Person p, Date d); To provide a meaningful test for this method an instance of Date needs to be provided to start with, and then two Person objects, one with birth date on the indicated date, and one with birth date on some other day. On top of that both classes may have some additional structure (like a persons gender or possible spouse) that is irrelevant for this particular method, but nevertheless needs to be initialised before the test. Example 10.3. A binary search method for integer arrays may have the following specication: /*@ public normal_behavior requires (\forall int i; i>0 && i<a.length ==> a[i-1] <= a[i]); ensures \result == -1 || (\result >=0 && \result < a.length); @*/ public int binarySearch(int[] a, int elem); By default JMLUnitNG only generates trivial test arrays of length up to 1. To properly test this method a longer and sorted array needs to be provided as input.

The above issues make automated generation of reference type data practically impossible. For examples 10.2 and 10.3 a code to generate the Date and Person objects or sorted arrays, respectively, has to be provided manually. In many cases the test generation process has to rely on such user input. However, JMLUnitNG has one more feature that can support testing of methods that involve reference type objects. In short, every time a constructor of some class A is tested the object created for that purpose is stored in the test suite repository and later reused as test data for any methods that reference the class A.

10.3. SPECIFICATIONS FOR TESTING

107

Example 10.4. Assume that the Date and Person classes from example 10.2 would have the following constructors specied with JML: /*@ public normal_behavior ensures getYear() == 2000; ensures getMonth() == 1; ensures getDay() == 1; @*/ public Date() {...} /*@ public normal_behavior ensures getYear() == year; ensures getMonth() == month; ensures getDay() == day; @*/ public Date(int year, int month , int day) {...}

/*@ public normal_behavior requires birthDate != null; ensures getBirthDate().equals(birthDate); @*/ public Person(Date birthDate) {...} For each of the two Date constructors at least one test is generated. Running of these two tests produces two Date objects that are stored in JMLUnitNG test suite repository during every test run. The two date objects very likely represent distinct dates. Then the test for the constructor of the Person class is executed with both of these dates resulting in two Person objects. At this point the internal object repository of JMLUnitNG test suite contains at least four objects, two Persons and two Dates. From this repository all possible combinations of Person and Date are going to be used to test the checkBirthDate method from example 10.2 resulting in a series of meaningful tests that cover the two possible outcomes of the method a person born or not on a given date.

10.3

Specications for Testing

Apart from providing the test data and/or guiding JMLUnitNG to generate the test data one should also spend a short while considering what kind of JML specications are actually good for generating unit tests. Assume the following trivial JML specication for an arbitrary method:

108

CHAPTER 10. TEST CASE GENERATION FROM JML

public normal_behavior requires true; ensures true; The test generated by JMLUnitNG for this method would accept any test data as input (because of the trivial precondition) and accept any output of the method (because of the trivial postcondition). In fact, the only actual property that would be checked is the absence of exceptions as this specication prohibits any. Otherwise no functional behaviour of this method would be checked in any way. Clearly this is not good. In an ideal situation the JML specication should contain enough specication cases to cover the possible execution paths of the method in question, in other words, the specication should reect the necessary test data partitioning for the method. In more technical terms one should strive for the following. Assume prei is the precondition for the i-th specication case, where there are n specication cases in total. Then for any single set of input test data only a few of the prei -s should be true and ideally there should be some input for every i that would make only the i-th precondition true and all the other false. This indicates that the dierent specication cases are relatively disjoint and hence provide n test data partitions. Then the disjunction of all preconditions: pre1 . . . pren should be universally true, regardless of the input test data. This indicates that the set of all possible inputs is completely covered by the specication for each possible input there is at least one matching specication case with a true precondition. Example 10.5. Recall the specication for makeHole in example 10.1. There are two specication cases with the following preconditions: pre1 : pre2 : param >= 0 param < 0

Here, only one of the preconditions becomes true for any single value of param. Furthermore, the disjunction of the precondition is universally true every integer is always either negative or non-negative. Overall this specication provides two test data partitions, which for the implementation of the makeHole method is just sucient. The corresponding postconditions posti for the dierent specication cases should simply provide a non-trivial condition to be checked that reect the intended behaviour of the method. However, one has to be careful

10.4. IMPROVING TEST DATA GENERATION

109

here not to overspecify, or at least do so consciously. This is because not all of JML expressions are easily checked during run-time and also the checkability of the expressions may depend on the underlying RAC library used in the test generation process. This problem is illustrated with the following example. Example 10.6. The method divide performs integer division, exactly as the / primitive operator when applied to integer values (and in fact, the method is simply implemented with this operator). The behaviour is fully specied in JML like this: /*@ public normal_behavior requires divident >= 0 && divisor > 0; ensures (\exists int remainder; remainder >= 0 && remainder < divisor && divident == \result * divisor + remainder); @*/ public int divide(int divident , int divisor) { return divident / divisor; } The postcondition contains an existential quantier that cannot be RAC checked. Modifying the formula under the quantier to something obviously invalid, for example: ensures (\exists int remainder; remainder >= 0 && remainder < divisor && divident == remainder); makes all the JMLUnitNG generated tests still pass awlessly when executed. This indicates that this particular postcondition is not checked and silently assumed to be true by the underlying RAC library.

10.4

Improving Test Data Generation

JMLUnitNG tries its best to support the user in providing good test data. However, as noted in previous sections, the techniques for doing this are rather shallow. That is, no deep analysis of the code or the specications is performed to extract detailed data dependencies and the resulting test data. To improve this processes even more, static verication tools can be employed. The core feature of static verication tools in this context is that they perform deep analysis of the code by considering all possible execution

110

CHAPTER 10. TEST CASE GENERATION FROM JML

paths through the veried code. For each such path the verication tool keeps an associated set of formulae that describe the conditions (also called constraints) on the variables that lead to the execution of the particular path. For example, a simple if statement: if (x > y) {...} else {...} causes two execution paths to be considered by the tool, one with a path condition x > y and one with x <= y. Now, to provide test data it is sucient to present two sets of test data that satises the rst and the second condition, respectively. For example, {x = 1, y = 0} and {x = 0, y = 1}. This idea is the basis for verication-based test generation [44]. The program/method under test and its specication are rst processed by a static verication tool and the symbolic conditions for each execution path are collected. In the second step these conditions are solved to provide concrete test data. That is, witnesses are found for the symbolic formulae as exemplied above. Having this test data, the rest of the test generation process follows similar to the one of JMLUnitNG and the nal test cases are produced. The important added value of this approach to test generation is that such processes in general guarantee very strong test coverage criteria by construction, like e.g., the Modied Condition/Decision Criterion (MCDC) [51] required for safety critical software [88]. One particular tool that employs this technique is KeYTestGen [1]. The static verier that collects the path conditions is the KeY system [11] mentioned earlier in Chapter 9. The concrete test values are then solved by the Simplify tool [39]. In the nal step, KeYTestGen produces unit tests for the JUnit testing framework. The whole process is encapsulated in a one-click tool realised as an Eclipse plug-in. Example 10.7. Consider a modied version of the makeHole method from Example 10.1, where an additional code branch is added that depends on an instance eld state: public int makeHole(int param) { if(param >= 0) { if(state > param) { return param + 21; } return param + 10; }else{ return param - 10; }

10.4. IMPROVING TEST DATA GENERATION

111

Figure 10.1: Fragment of the KeY proof tree for Example 10.7.

} The static analysis of the code performed by KeYTestGen produces two path conditions, namely state > param and state <= param. The corresponding piece of the actual KeY proof tree with this information is shown in Figure 10.1. After solving these path requirements with Simplify the following stubs are created for the corresponding unit tests. Here only the two cases where param is positive are shown, two additional cases are produced for negative values of param. The test case for state > param: // param int[] testData0 = new int[]{ 0, 1, 1, 0, 1, 0 }; // this.state int[] testData1 = new int[]{ 1, 2, 3, 10, 12, 1000 }; ... self = ReflectiveInstantiator_1.newExample(); param = testData0[testDataCounter]; ReflectiveInstantiator_1._set_int( Example.class , self, "state", testData1[testDataCounter] ); ... result=self.makeHole(param); ...

112

CHAPTER 10. TEST CASE GENERATION FROM JML

and for state <= param: // param int[] testData0 = new int[]{ 0, 1, 1, 0, 1, 0 }; // this.state int[] testData1 = new int[]{ 0, 1, 0, -1, -9, -10 }; ... The rest of the unquoted code is responsible for evaluating the result variable with respect to makeHoles postcondition and reporting the test result. Furthermore, note that KeYTestGen produces several dierent randomly looking values for each path constraint. KeYTestGen is currently in the development stage and many features are still missing. In particular, similarly to JMLUnitNG, KeYTestGen also has diculty to automatically produce Java code for generating test data for reference types as described Section 10.2.

10.5

Tools

Two test generation tools were discussed in this chapter. In the following some concrete technical pointers are provided.

10.5.1

JMLUnitNG

JMLUnitNG produces test suites in the TestNG format. The necessary TestNG libraries are already included in the JMLUnitNG distribution that can be found at:
http://formalmethods.insttech.washington.edu/software/jmlunitng/

The tool does, however, need some JML related libraries: The JML4C RAC compiler and library, to be found at http://www. cs.utep.edu/cheon/download/jml4c/download.php, For some versions of Java Development Kit, the OpenJML libraries are required, available at http://jmlspecs.sourceforge.net/openjml. tar.gz. The mentioned sites should provide all the necessary JAR les mentioned in the command line examples to follow. The typical workow with JMLUnitNG is the following:

10.5. TOOLS

113

1. First the code under test should be annotated with suitable JML specications, 2. Then the source code for the test suite can be generated with the following command (for Linux running OpenJDK 1.6): java -Xbootclasspath/p:openjmlboot.jar \ -jar jmlunitng.jar --dest tests --reflection src/ where src is the directory where the Java les to be tested reside. It is also at this point that additional options to JMLUnitNG can be given, like --literals or --spec-literals. The generated test suite will be placed in the tests directory, 3. In this step the developer can ll any additional test data in the stub les prepared by JMLUnitNG. For some class named ClassName and some method method the les with the following name pattern should be located and edited: tests/ClassName_JML_Data/method__....java 4. Then the whole project can be compiled. The program under test should be compiled with a RAC enabled compiler, the test suite itself with the regular compiler: java -jar jml4c.jar -cp jml4c.jar:jmlunitng.jar src/ javac -cp jml4rt.jar:jmlunitng.jar:src/ tests/*.java 5. Finally, the test can be run, for a class ClassName the command line is: java -cp jml4rt.jar:jmlunitng.jar:./tests/:./src/ \ ClassName_JML_Test Every time the code under test is changed, only steps 4 and 5 have to be repeated. In case the JML specication is also changed then all steps from 2 to 5 have to be repeated. The test data lled in step 3 will not be overwritten during the repeated runs of test generation, so this step can be skipped. Currently, the authors of JMLUnitNG do not provide a stable Eclipse plug-in to simplify the described process.

114

CHAPTER 10. TEST CASE GENERATION FROM JML

Figure 10.2: JUnit tests project produced by KeYTestGen.

10.5.2

KeYTestGen

The KeYTestGen is entirely realised as an Eclipse plug-in, with the update site at http://www.cse.chalmers.se/~gabpag/eclipse. The plug-in provides a context menu for Java projects with just one command to simply generate tests for all JML annotated methods in the project. When invoked the user is prompted for a few conguration parameters for the KeY verier to guide the test generation process. For simple examples it is best to leave the default values. Then all the necessary tools are run in the background and after completion a separate Eclipse project with generated JUnit tests is created, see Figure 10.2. At this point it is as simple as running the generated project as a JUnit project from the Eclipse Run... menu to perform the actual tests.

10.6

Further Reading

JMLUnitNG is nicely described in more detail in a paper by JMLUnitNG authors, Zimmerman and Nagmoti [90]. Earlier work on JML based unit testing can be found in [26]. For more advanced verication based

10.6. FURTHER READING

115

test generation techniques that employ static analysis several papers can be recommended [19, 87, 44, 10]. In particular [44, 10] discuss the rst work on KeYTestGen, the most recent one is presented in [1].

116

CHAPTER 10. TEST CASE GENERATION FROM JML

Chapter 11

Bibliography
[1] W. Ahrendt, W. Mostowski, and G. Paganelli. Real-time Java API specications for high coverage test generation. In The 10th International Workshop on Java Technologies for Real-time and Embedded Systems, JTRES 12. ACM, 2012. To appear. [2] I. Aktug, M. Dam, and D. Gurov. Provably correct runtime monitoring. Journal of Logic and Algebraic Programming, 78:304339, 2009. [3] M. Barnett, B.-Y. E. Chang, R. DeLine, B. Jacobs, and K. R. M. Leino. Boogie: A modular reusable verier for object-oriented programs. In Formal Methods for Components and Objects, volume 4111 of Lecture Notes in Computer Science. Springer-Verlag, 2005. [4] M. Barnett, R. DeLine, M. F ahndrich, K. R. M. Leino, and W. Schulte. Verication of object-oriented programs with invariants. Journal of Object Technology, 3(6):2756, 2004. [5] M. Barnett, K. R. M. Leino, and W. Schulte. The Spec# programming system: An overview. In Barthe et al. [8], pages 151171. [6] M. Barnett, D. Naumann, W. Schulte, and Q. Sun. 99.44% pure: Useful abstractions in specications. In E. Poll, editor, Workshop on Formal Techniques for Java Programs, pages 1119, 2004. [7] D. Bartetzko, C. Fischer, M. M oller, and H. Wehrheim. Jass Java with Assertions. In K. Havelund and G. R. su, editors, ENTCS, volume 55(2). Elsevier Publishing, 2001. 117

118

CHAPTER 11. BIBLIOGRAPHY

[8] G. Barthe, L. Burdy, M. Huisman, J.-L. Lanet, and T. Muntean, editors. Proceedings, Construction and Analysis of Safe, Secure and Interoperable Smart devices (CASSIS04) Workshop, volume 3362 of Lecture Notes in Computer Science. Springer-Verlag, 2005. [9] G. Barthe, M. Pavlova, and G. Schneider. Precise analysis of memory consumption using program logics. In Software Engineering and Formal Methods, pages 8695. IEEE Press, 2005. [10] B. Beckert and C. Gladisch. White-box testing by combining deductionbased specication extraction and black-box testing. In B. Meyer and Y. Gurevich, editors, Proceedings, International Conference on Tests and Proofs (TAP), Z urich, Switzerland, volume 4454 of LNCS. Springer, February 2007. [11] B. Beckert, R. H ahnle, and P. Schmitt, editors. Verication of ObjectOriented Software: The KeY Approach, volume 4334 of Lecture Notes in Computer Science. Springer, 2007. [12] G. Behrmann, A. David, K. G. Larsen, P. Pettersson, and W. Yi. Developing uppaal over 15 years. Softw., Pract. Exper., 41(2):133142, 2011. [13] J. v. d. Berg and B. Jacobs. The LOOP compiler for Java and JML. In T. Margaria and W. Yi, editors, Tools and Algorithms for the Construction and Analysis of Systems (TACAS 2001), volume 2031 of Lecture Notes in Computer Science, pages 299312. Springer, 2001. [14] A. Biere, A. Cimatti, E. M. Clarke, and Y. Zhu. Symbolic model checking without BDDs. In R. Cleaveland, editor, Tools and Algorithms for Construction and Analysis of Systems, 5th International Conference (TACAS 99), volume 1579 of Lecture Notes in Computer Science, pages 193207. Springer-Verlag, 1999. [15] S. Blom, J. v. d. Pol, and M. Weber. Ltsmin: Distributed and symbolic reachability. In T. Touili, B. Cook, and P. Jackson, editors, Computer Aided Verication (CAV 2010), volume 6174 of Lecture Notes in Computer Science, pages 354359. Springer, 2010. [16] A. Borgida, J. Mylopoulos, and R. Reiter. On the frame problem in procedure specications. IEEE Transactions on Software Engineering, 21(10):785798, 1995.

119 [17] A. Bouajjani, J. Esparza, and O. Maler. Reachability analysis of pushdown automata: Application to model-checking. In International Conference on Concurrency Theory (CONCUR 97), volume 1243 of LNCS, pages 135150, 1997. [18] C. Breunesse, N. Cata no, M. Huisman, and B. Jacobs. Formal methods for smart cards: an experience report. Science of Computer Programming, 55:5380, 2005. [19] A. D. Brucker and B. Wol. Interactive testing with HOL-TestGen. In W. Grieskamp and C. Weise, editors, Proceedings, Workshop on Formal Aspects of Testing, FATES, volume 3997 of LNCS, pages 87 102. Springer, 2005. [20] R. E. Bryant. Symbolic boolean manipulation with ordered binarydecision diagrams. ACM Comput. Surv., 24(3):293318, 1992. [21] L. Burdy, A. Requet, and J.-L. Lanet. Java applet correctness: A developer-oriented approach. In D. M. K. Araki, S. Gnesi, editor, Formal Methods Europe, volume 2805 of LNCS, pages 422439. SpringerVerlag, 2003. [22] R. Cavada, A. Cimatti, C. Jochim, G. Keighren, E. Olivetti, M. Pistore, M. Roveri, and A. Tchaltsev. NuSMV 2.5 user manual, 2010. Available online from http://nusmv.fbk.eu. [23] R. Cavada, A. Cimatti, G. Keighren, E. Olivetti, M. Pistore, and M. Roveri. NuSMV 2.5 tutorial, 2010. Available online from http: //nusmv.fbk.eu. [24] J. Charles. Adding native specications to JML. In Workshop on Formal Techniques for Java Programs, 2006. [25] Y. Cheon. A Runtime Assertion Checker for the Java Modeling Language. PhD thesis, Iowa State University, 2003. [26] Y. Cheon and G. T. Leavens. A simple and practical approach to unit testing: The JML and JUnit way. In B. Magnusson, editor, Proc. Object-Oriented Programming, 16th European Conference ECOOP, Malaga, Spain, volume 2374 of LNCS, pages 231255. Springer, 2002. [27] A. Cimatti, E. M. Clarke, E. Giunchiglia, F. Giunchiglia, M. Pistore, M. Roveri, R. Sebastiani, and A. Tacchella. NuSMV 2: An opensource

120

CHAPTER 11. BIBLIOGRAPHY tool for symbolic model checking. In E. Brinksma and K. G. Larsen, editors, CAV, volume 2404 of LNCS, pages 359364. Springer, 2002.

[28] E. Clarke. The birth of model checking. In O. Grumberg and H. Veith, editors, 25 Years of Model Checking, volume 5000 of Lecture Notes in Computer Science, pages 126. Springer Berlin / Heidelberg, 2008. [29] E. M. Clarke, E. A. Emerson, S. Jha, and A. P. Sistla. Symmetry reductions in model checking. In A. J. Hu and M. Y. Vardi, editors, CAV, volume 1427 of Lecture Notes in Computer Science, pages 147 158. Springer, 1998. [30] E. M. Clarke, O. Grumberg, and D. E. Long. Model checking and abstraction. In POPL, pages 342354, 1992. [31] E. M. Clarke, K. L. McMillan, S. V. A. Campos, and V. HartonasGarmhausen. Symbolic model checking. In R. Alur and T. A. Henzinger, editors, CAV, volume 1102 of Lecture Notes in Computer Science, pages 419427. Springer, 1996. [32] Code contracts. us/projects/contracts/. http://research.microsoft.com/en-

[33] D. Cok and J. R. Kiniry. ESC/Java2: Uniting ESC/Java and JML: Progress and issues in building and using ESC/Java2 and a report on a case study involving the use of ESC/Java2 to verify portions of an internet voting tally system. In Barthe et al. [8], pages 108128. [34] Cvc3 webpage. Via http://www.cs.nyu.edu/acsys/cvc3/. [35] M. Dam. CTL* and ECTL* as fragments of the modal -calculus. In CAAP 92, 17th Colloquium on Trees in Algebra and Programming, Rennes, France, February 26-28, 1992, Proceedings, volume 581 of Lecture Notes in Computer Science, pages 145164. Springer, 1992. Darvas and R. Leino. Practical reasoning about invocations and [36] A. implementations of pure methods. In FASE, volume 4422 of LNCS, pages 336351. Springer-Verlag, 2007. [37] A. Darvas and P. M uller. Reasoning about method calls in JML specications. In Workshop on Formal Techniques for Java Programs, 2005. [38] A. Darvas and P. M uller. Faithful mapping of model classes to mathematical structures. IET Software, 2(6):477499, Dec. 2008.

121 [39] D. Detlefs, G. Nelson, and J. Saxe. Simplify: A theorem prover for program checking. Technical Report HPL-2003-148, HP Research, 2003. [40] E. Dijkstra. A Discipline of Programming. Prentice-Hall, 1976. [41] T. Dinsdale-Young, M. Dodds, P. Gardner, M. Parkinson, and V. Vafeiadis. Concurrent abstract predicates. In T. DHondt, editor, ECOOP, volume 6183 of LNCS, pages 504528. Springer-Verlag, 2010. [42] E. Emerson and E. Clarke. Characterizing correctness properties of parallel programs using xpoints. In J. W. de Bakker and J. van Leeuwen, editors, ICALP, volume 85 of Lecture Notes in Computer Science, pages 169181. Springer-Verlag, 1980. [43] E. A. Emerson. Temporal and modal logic. In J. van Leeuwen, editor, Theoretical Computer Science, chapter 16. Elsevier, 1990. [44] C. Engel and R. H ahnle. Generating unit tests from formal proofs. In B. Meyer and Y. Gurevich, editors, Proceedings, International Conference on Tests and Proofs (TAP), Z urich, Switzerland, volume 4454 of LNCS. Springer, February 2007. [45] U. Erlingsson. The Inlined Reference Monitor Approach to Security Policy Enforcement. PhD thesis, Department of Computer Science, Cornell University, 2003. Available as Technical Report 2003-1916. [46] J. Esparza, D. Hansel, P. Rossmanith, and S. Schwoon. Ecient algorithms for model checking pushdown systems. In Computer Aided Verication (CAV 00), volume 1855 of LNCS, pages 232247. Springer, 2000. [47] H. Garavel, R. Mateescu, F. Lang, and W. Serwe. CADP 2006: A toolbox for the construction and analysis of distributed processes. In W. Damm and H. Hermanns, editors, CAV, volume 4590 of LNCS, pages 158163. Springer, 2007. [48] J. Gosling, B. Joy, G. Steele, and G. Bracha. The Java Language Specication, third edition. The Java Series. Addison-Wesley Publishing Company, 2005. [49] E. E. J. Halpern. sometimes and not never. In On branching verus linear time temporal logic, volume 33(1) of Journal of the ACM, pages 151178, 1986.

122

CHAPTER 11. BIBLIOGRAPHY

[50] K. Havelund and G. Rosu. An overview of the runtime verication tool Java PathExplorer. Formal Methods in System Design, 24, 2004. [51] K. J. Hayhurst, D. S. Veerhusen, J. J. Chilenski, and L. K. Rierson. A practical tutorial on modied condition/decision coverage. Technical report, Hampton NASA Langley Research Center, 2001. [52] C. Hoare. Proof of correctness of data representations. Acta Informatica, 1:271281, 1972. [53] C. A. R. Hoare. An axiomatic basis for computer programming. Communications of the ACM, 12(10):576580, 1969. [54] G. Holzmann. The model checker SPIN. Transactions on Software Engineering, 23(5):279295, 1997. [55] E. Hubbers and M. Oostdijk. Generating JML specications from UML state diagrams. In Forum on specication & Design Languages, pages 263273. University of Frankfurt, 2003. Proceedings appeared as CDRom with ISSN 1636-9874. [56] M. Huisman. Reasoning about Java programs in higher order logic using PVS and Isabelle. PhD thesis, Computing Science Institute, University of Nijmegen, 2001. [57] M. Huisman and A. Tamalet. A formal connection between security automata and JML annotations. In Fundamental Approaches to Software Engineering, volume 5503 of Lecture Notes in Computer Science, pages 340354. Springer-Verlag, 2009. [58] C. Hurlin. Specication and Verication of Multithreaded ObjectOriented Programs with Separation Logic. PhD thesis, Universit e Nice Sophia Antipolis, 2009. [59] B. Jacobs. Weakest pre-condition reasoning for Java programs with JML annotations. Journal of Logic and Algebraic Programming, 58(1 2):6188, 2003. [60] B. Jacobs and F. Piessens. The verifast program verier. Technical Report CW520, Katholieke Universiteit Leuven, Aug. 2008. [61] The JASS project. http://semantik.informatik.uni-oldenburg.de/~*jass/. [62] D. Jin, P. Meredith, C. Lee, and G. Rosu. JavaMOP: Ecient parametric runtime monitoring framework. In ICSE12. IEEE, 2012.

123 [63] M. Kim, M. Viswanathan, S. Kannan, I. Lee, and O. Sokolsky. JavaMaC: a run-time assurance approach for java programs. Formal Methods System Design, 24, 2004. [64] D. Kozen. Results on the propositional -calculus. Theoretical Computer Science, 27:333354, 1983. [65] M. Kwiatkowska, G. Norman, and D. Parker. PRISM: Probabilistic model checking for performance and reliability analysis. ACM SIGMETRICS Performance Evaluation Review, 36(4):4045, 2009. [66] G. Le Guernic, A. Banerjee, T. Jensen, and D. Schmidt. Automatabased Condentiality Monitoring. In Proceedings of the Annual Asian Computing Science Conference, volume 4435 of Lecture Notes in Computer Science, pages 7589. Springer, 2006. [67] G. Leavens, A. Baker, and C. Ruby. Preliminary Design of JML: a Behavioral Interface Specication Language for Java. Technical Report 98-06, Iowa State University, Department of Computer Science, 1998. http://www.cs.iastate.edu/~*leavens/JML/prelimdesign/. [68] G. Leavens, E. Poll, C. Clifton, Y. Cheon, and C. Ruby. JML reference manual. Preliminary draft http://www.cs.iastate.edu/~leavens/JML/jmlrefman/jmlrefman_toc.html. [69] G. T. Leavens, E. Poll, C. Clifton, Y. Cheon, C. Ruby, D. Cok, and J. Kiniry. JML Reference Manual, July 2005. In Progress. Department of Computer Science, Iowa State University. Available from http:// www.jmlspecs.org. [70] K. Leino, P. M uller, and J. Smans. Verication of concurrent programs with Chalice. In Lecture notes of FOSAD, volume 5705 of Lecture Notes in Computer Science. Springer, 2009. [71] K. Leino, G. Nelson, and J. Saxe. ESC/Java users manual. Technical Report SRC 2000-002, Compaq System Research Center, 2000. [72] K. R. M. Leino and P. M uller. Object invariants in dynamic contexts. In M. Odersky, editor, European Conference on Object-Oriented Programming, volume 3086 of Lecture Notes in Computer Science, pages 491516. Springer-Verlag, 2004. Available from www.sct.inf.ethz. ch/publications/index.html.

124

CHAPTER 11. BIBLIOGRAPHY

[73] B. Liskov and J. M. Wing. A behavioral notion of subtyping. ACM Transactions on Programming Languages and Systems, 16(6), 1994. [74] F. Logozzo and M. F ahndrich. On the relative completeness of bytecode analysis versus source code analysis. In L. Hendren, editor, International Conference on Compiler Construction, volume 4959 of Lecture Notes in Computer Science, pages 197212. Springer, 2008. [75] S. Malakuti Khah Olun Abadi, C. Bockisch, and M. Aksit. Applying the composition lter model for runtime verication of multiple-language software. In The 20th annual International Symposium on Software Reliability Engineering,ISSRE 2009, pages 3140. ieee, 2009. [76] C. March e, C. Paulin-Mohring, and X. Urbain. The Krakatoa tool for JML/Java program certication, 2003. Manuscript. [77] B. Meyer. Object-Oriented Software Construction. Prentice Hall, 2nd revised edition, 1997. [78] P. M uller and A. Poetzsch-Heter. Universes: A type system for controlling representation exposure. In A. Poetzsch-Heter and J. Meyer, editors, Programming Languages and Fundamentals of Programming, pages 131140. Fernuniversit at Hagen, 1999. Technical Report 263, Available from sct.inf.ethz.ch/publications. [79] P. M uller, A. Poetzsch-Heter, and G. T. Leavens. Modular specication of frame properties in Jml. Technical Report 01-03, Department of Computer Science, Iowa State University, Ames, Iowa, 50011, April 2001. [80] P. M uller, A. Poetzsch-Heter, and G. T. Leavens. Modular invariants for layered object structures. Science of Computer Programming, 62:253286, 2006. [81] J. Ni no and F. Hosch. An introduction to Programming and ObjectOriented Design Using JavaTM . Wiley, 2008. Third edition. [82] A. Pnueli. The temporal logic of programs. In FOCS, pages 4657. IEEE Computer Society, 1977. [83] A. Poetzsch-Heter. Specication and verication of object-oriented programs. Habilitation thesis, Technical University of Munich, 1997.

125 [84] A. D. Raghavan and G. T. Leavens. Desugaring JML method specications. Technical Report TR #00-03e, Department of Computer Science, Iowa State University, 2000. Current revision from May 2005. [85] H. Reb elo, R. Lima, G. Leavens, M. Corn elio, A. Mota, and C. Oliveira. Optimizing generated aspect-oriented assertion checking code for jml using programming laws: An empirical study, 2010. Submitted. [86] F. B. Schneider. Enforceable security policies. ACM Transactions on Information and System Security, 3:3050, 2000. [87] N. Tillmann and J. de Halleux. Pex-white box test generation for .NET. In Proceedings, International Conference on Tests and Proofs (TAP), volume 4966 of LNCS, pages 134153. Springer, 2008. [88] J. Wlad. DO-178B and safety-critical software: Technical overview, 2000. [89] Z3 webpage. Via http://research.microsoft.com/projects/z3. [90] D. Zimmerman and R. Nagmoti. JMLUnit: The Next Generation. In B. Beckert and C. March e, editors, 1st International Conference on Formal Verication of Object-Oriented Software (FoVeOOS 2010), volume 6528 of Lecture Notes in Computer Science. Springer, 2010.

You might also like