can c++ be made as safe as spark? david crocker, escher technologies

Post on 17-Dec-2015

215 Views

Category:

Documents

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Can C++ be made as safe as SPARK?

David Crocker, Escher Technologies

Can C++ be made as safe as SPARK? 2

Motiviation• When developing software to very high integrity levels, testing is

insufficient to show the required integrity level has been reached, so formal verification is typically requires as well

• The SPARK tool set is probably the most-used formal verification system at the programming language level

• However, critical software is increasingly written in C and C++, especially in the automotive sector

• Can development in C or C++ provide similar levels of software integrity as development in SPARK?

Can C++ be made as safe as SPARK? 3

Bounded queue example• Our example is a bounded queue of characters with fixed capacity

• I will show it might be implemented in SPARK Ada

• I will show how to implement a C++ equivalent that, like the SPARK version:– Provides a clean interface and hides the data– Has a formal proof of correctness

-- File BoundedQueue.ads

package BoundedQueue with SPARK_Mode is capacity: constant Integer := 64; type Queue is private;

function empty(q: in Queue) return Boolean;

function full(q: in Queue) return Boolean;

procedure add(q: in out Queue; val: in Character) with Pre => not full(q) ;

procedure remove(q: in out Queue; val: out Character) with Pre => not empty(q) ;

procedure init(q: out Queue) with Post => empty(q);

private subtype StorageIndex is Integer range 0..capacity;

type Storage is array (StorageIndex) of Character;

type Queue is record data: Storage; nextIn: StorageIndex; nextOut: StorageIndex; end record;

end BoundedQueue;

The package specification

// File BoundedQueue.h

#define CAPACITY (64u)

typedef struct { char data[CAPACITY + 1u]; unsigned int nextIn; // ranges from 0..CAPACITY unsigned int nextOut; // ranges from 0..CAPACITY} Queue;

bool empty(const Queue *q);

bool full(const Queue *q);

void add(Queue *q, char c); // on entry the queue must not be full

char remove(Queue *q); // on entry the queue must not be empty

void init(Queue *q); // on return the queue is empty

Let’s try that in C…

Problem: lack of encapsulation• The SPARK version declares Queue to be a private record type

• In the C version, the Queue structure is exposed– which means that clients could reads/write its field directly

• C has no adequate mechanism for data hiding

• Can we do better with C++?

type Queue is private; … private… type Queue is record data: Storage; nextIn: StorageIndex; nextOut: StorageIndex; end record;

typedef struct { char data[CAPACITY + 1u]; unsigned int nextIn; unsigned int nextOut;} Queue;

// File BoundedQueue.hpp

const unsigned int capacity = 64u;

class Queue {public: bool empty() const;

bool full() const;

void add(char c); // on entry the queue must not be full

char remove(); // on entry the queue must not be empty

Queue(); // on return the queue is empty

private: char data[capacity + 1u]; unsigned int nextIn; // ranges from 0..CAPACITY unsigned int nextOut; // ranges from 0..CAPACITY};

Let’s try it in C++

Can C++ be made as safe as SPARK? 8

SPARK and C++ versions compared

• Both keep the data private

• Both provide empty and full functions, add and remove procedures

• Parameter passing:– SPARK used in and out keywords to indicate direction– C++ uses const and absence of const to indicate whether a parameter

passed by pointer is changed or not

• How to initialize a Queue– In the SPARK version, you call the init procedure– In the C++ version, the default constructor will be called automatically

• But the C++ version is still missing something…

Can C++ be made as safe as SPARK? 9

Function contracts!• The SPARK version provides function contracts:

• How to do this in C++?

• Maybe do it the way SPARK used to, in comments?

procedure add(q: in out Queue; val: in Character) with Pre => not full(q) ;

procedure remove(q: in out Queue; val: out Character) with Pre => not empty(q) ;

procedure init(q: out Queue) with Post => empty(q);

Can C++ be made as safe as SPARK? 10

const unsigned int capacity = 64u;

class Queue {public: bool empty() const;

bool full() const;

void add(char c); //# pre !full()

char remove(); //# pre !empty()

Queue(); //# post empty()

private: char data[capacity + 1u]; unsigned int nextIn; // ranges from 0..CAPACITY unsigned int nextOut; // ranges from 0..CAPACITY};

Adding function contracts

Can C++ be made as safe as SPARK? 11

A nicer way of adding function contracts

• C++ provides a preprocessor

• The preprocessor can be used to define and expand macros

• So how about using macros:

• When the file is compiled, the compiler expands the pre(…) part to nothing, so it gets ignored

• Text editors do syntax highlighting on macro calls, just as for code

#define pre(expression) // nothing

void add(char c) pre(!full());

Can C++ be made as safe as SPARK? 12

#include <ecv.h> // for specification macro definitions

const unsigned int capacity = 64u;

class Queue {public: bool empty() const;

bool full() const;

void add(char c) pre(!full());

char remove() pre(!empty());

Queue() post(empty());

private: char data[capacity + 1u]; unsigned int nextIn; // ranges from 0..CAPACITY unsigned int nextOut; // ranges from 0..CAPACITY};

Adding function contracts

Can C++ be made as safe as SPARK? 13

What’s still missing from the C++ version?

• The SPARK version uses range-constrained types:

• It’s not essential to use range-constrained types in this example, but they can help with verification– e.g. by detecting out-of-range values earlier

• Why not add range-constrained types to C++?

subtype StorageIndex is Integer range 0..capacity;

type Storage is array (StorageIndex) of Character;

type Queue is record data: Storage; nextIn: StorageIndex; nextOut: StorageIndex; end record;

Can C++ be made as safe as SPARK? 14

Adding range-constrained types to C++

• We could use a class template:

• If we don’t need run-time checking, we can use annotations instead

class ConstrainedInt<int minVal, int maxVal> {public: operator int() const { return val; }

ConstrainedInt(int arg) pre(arg >= minVal; arg <= maxVal) { if (arg < minVal || arg > maxVal) { throw ConstraintError(arg); } val = arg; }

private: int val;}

ConstrainedInt<0, capacity> nextIn, nextOut;

Can C++ be made as safe as SPARK? 15

Extending the C++ typedef declaration

• C and C++ allow you to define synonyms for types:

• Let’s add constraints to typedef declarations:

• We also add the rule that a pointer to a constrained type is not assignment-compatible with a pointer to any other type, even if the constraint is “true”

typedef size_t StorageIndex;

#define invariant(expression) // nothing

typedef size_t invariant(value <= capacity) StorageIndex;

#include <ecv.h> // for specification macro definitions

const unsigned int capacity = 64u;

class Queue {public: bool empty() const;

bool full() const;

void add(char c) pre(!full());

char remove() pre(!empty());

Queue() post(empty());

private: typedef unsigned int invariant(value <= capacity) StorageIndex;

char data[capacity + 1u]; StorageIndex nextIn; StorageIndex nextOut;};

Using range-constrained types

Can C++ be made as safe as SPARK? 17

What about the body?

-- File BoundedQueue.adb

package body BoundedQueue with SPARK_Mode is function empty(q: in Queue) return Boolean is begin return q.nextIn = q.nextOut end;

function full(q: in Queue) return Boolean is begin return (q.nextIn + 1) mod (capacity + 1) = q.nextOut; end;

procedure add(q: in out Queue; val: in Character) is begin q.data(q.nextIn) := val; q.nextIn := (q.nextIn + 1) mod (capacity + 1); end;

procedure remove(q: in out Queue; val: out Character) is begin val := q.data(q.nextOut); q.nextOut := (q.nextOut + 1) mod (capacity + 1); end;

procedure init(q: out Queue) is begin q.nextIn := 0; q.nextOut := 0; q.data := (others => Character'Val(0)); -- for SPARK end;

end BoundedQueue;

// File BoundedQueue.cpp

#include "BoundedQueue.hpp“#include <cstring> // for memset()

bool Queue::empty() const { return nextIn == nextOut;}

bool Queue::full() const { return (nextIn + 1u) % (capacity + 1u) == nextOut;}

void Queue::add(char c) { data[nextIn] = c; nextIn = (nextIn + 1u) % (capacity + 1u);}

char Queue::remove() { char temp = data[nextOut]; nextOut = (nextOut + 1u) % (capacity + 1u); return temp;}

Queue::Queue() { nextOut = 0; nextIn = 0; memset(data, 0, sizeof(data));}

Can C++ be made as safe as SPARK? 20

Verification in SPARK 2012 GPL edition

Can C++ be made as safe as SPARK? 21

Verification of the C++ version

…and so on until…

Can C++ be made as safe as SPARK? 22

Similarities and differences• SPARK reports 5 VCs generated, 4 proved

– It appears to hide some “trivial” VCs, e.g. provable range checks

• Our tool for C++ reports 37 VCs generated, 36 proved– The number of VCs doesn’t depend on whether they succeed or not

• Neither can prove that the init function or constructor yields an empty queue– Because we haven’t provided a specification for empty()

• We have proved that the program is valid in one sense– “Exception freedom” for the Ada version– “Absence of undefined or unspecified behaviour” for the C++ version

• But we haven’t proved that it behaves like a queue!

Can C++ be made as safe as SPARK? 23

How should a queue behave?• Logically, a queue is a sequence of elements

– We add elements to one end and remove them from the other

• So we should specify the queue operations in terms of a sequence

• This calls for data refinement– The abstract data is a sequence with varying numbers of elements– The concrete data is a fixed length array and two indices into it– A retrieve relation defines the relationship between abstract and concrete

data

Remove elements from head Add elements to tail

Can C++ be made as safe as SPARK? 24

Expressing data refinement in C++

• We allow ghost functions to be declared– A ghost function is for use in specifications only

• To express the retrieve relation, we declare a ghost function that returns the abstract data– Then we can write specifications in terms of calls to that function

• For the type of the abstract data, we can use _ecv_seq<char>– This is a built-in ghost type that represents a sequence– It supports the usual sequence operations including count(), head(), tail(),

take(n), drop(n), append(c) and concat(s)– It supports quantification over the elements and a few higher order

functions (filter, map, left-fold, ...)

Retrieve function for a circular buffer

• Otherwise, we want the elements from nextOut to the end of the buffer, followed by elements from the start of the buffer up to nextIn:

nextOut nextIn

nextOutnextIn

data

data

data.take(nextIn)

data.take(nextIn) data.drop(nextOut)

• If nextIn >= nextOut, we want the elements in between them:

Queue contents are:data.drop(nextOut) .concat(data.take(nextIn))

Queue contents are:data.take(nextIn) .drop(nextOut)

const unsigned int capacity = 64u;

class Queue {public: // Retrieve function ghost( ecv_seq<char> contents() const

returns( (nextIn >= nextOut) ? data.take(nextIn).drop(nextOut) : data.drop(nextOut).concat(data.take(nextIn))

); )

bool empty() const returns(contents().count == 0);

bool full() const returns(contents().count == capacity);

void add(char c) pre(!full()) post(contents() == (old contents()).append(c));

char remove() pre(!empty()) returns(contents().head()) post(contents() == (old contents()).tail());

Queue() post(empty());

private: …

Can C++ be made as safe as SPARK? 27

Verification with data refinement

…and so on…

Can C++ be made as safe as SPARK? 29

Also in the paper…• Weaknesses in the C++ language have to be mitigated

– We apply most MISRA-C:2008 rules– We use annotations to strengthen the type system in respect of pointers– We only support those C++ constructs that we believe are safe to use

and have formalised– We add further safety rules, e.g. to restrict calls to overloaded functions

• Single inheritance with dynamic binding– We prove subtype compatibility (Liskov Substitution Principle) as required

by DO-332 objective OO-6.7.2

• C++ template declarations– Generic version of the bounded queue example

Can C++ be made as safe as SPARK? 30

Future work (1)

• Template instantiation preconditions– Declare the auxiliary operators etc. needed to instantiate a template

• Semantics of different sorts of volatile variables– Currently, we treat all volatile variables as subject to unpredictable

changes in value, so they can’t be used in specifications– But not all volatile variables can change unpredictably at all times – SPARK 2014 uses the concept of external state to handle this

template<class X> void sort(Array<X<> table) require bool operator<(X, X) post(…){ … }

Can C++ be made as safe as SPARK? 31

Future work (2)• Concurrency

– C++ 2011 has a concurrency model– Shared-variable concurrency in general is a difficult problem– Microsoft’s Vcc handles it to some degree, but the annotation is hard– Taming Concurrency project (Cliff Jones, Newcastle)

• Floating point arithmetic– If we model FP arithmetic as real arithmetic, we can do useful things, but

can also produce false proofs, e.g. 3.0 * (1.0/3.0) == 1.0– If we model FP arithmetic more accurately, a lot of “useful” things become

unprovable– In simple cases, range arithmetic may be suitable

Can C++ be made as safe as SPARK? 32

Related work• Larch/C++ project

– Defined an annotation language, but not supported by verification tools

• Several formal verification systems for C– Frama/Jessie, Vcc, Verifast, and our own eCv

Can C++ be made as safe as SPARK? 33

Conclusion• By adding selected C++ features to MISRA-C:2012 we have defined

a subset of C++ that we believe is suitable for high-integrity software– and offers substantial advantages over C

• Programs written in this subset can be verified formally in the same way as programs written in the SPARK subset of Ada

• Applications of the tool so far:– SIL 4 software in the defence industry – Medical equipment (joint work with Newcastle University)

• Questions?

top related