1 / 33

Can C++ be made as safe as SPARK?

Can C++ be made as safe as SPARK?. David Crocker, Escher Technologies. 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 required as well

elsiemiller
Download Presentation

Can C++ be made as safe as SPARK?

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Can C++ be made as safe as SPARK? David Crocker, Escher Technologies

  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 required 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? 2

  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 Can C++ be made as safe as SPARK? 3

  4. The package specification -- File BoundedQueue.ads package BoundedQueue with SPARK_Mode is capacity: constant Integer := 64;type Queue isprivate; function empty(q: in Queue) return Boolean; function full(q: in Queue) return Boolean; procedure add(q: inout Queue; val: in Character) with Pre => not full(q) ; procedure remove(q: inout Queue; val: out Character) with Pre => not empty(q) ; procedure init(q: out Queue) with Post => empty(q); privatesubtype StorageIndex is Integer range 0..capacity; type Storage isarray(StorageIndex) of Character; type Queue is record data: Storage; nextIn: StorageIndex; nextOut: StorageIndex;end record; end BoundedQueue;

  5. Let’s try that in C… // File BoundedQueue.h #define CAPACITY (64u) typedefstruct{char data[CAPACITY + 1u];unsigned int nextIn; // ranges from 0..CAPACITYunsigned 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

  6. 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 isprivate; … private…type Queue is record data: Storage; nextIn: StorageIndex; nextOut: StorageIndex;end record; typedefstruct{char data[CAPACITY + 1u];unsigned int nextIn; unsigned int nextOut; } Queue;

  7. Let’s try it in C++ // 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..CAPACITYunsigned int nextOut; // ranges from 0..CAPACITY};

  8. SPARK and C++ versions compared • Both keep the data private • Both provide empty and full functions, add and remove procedures • Parameter passing: • SPARK uses 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? 8

  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: inout Queue; val: in Character) with Pre => not full(q) ; procedure remove(q: inout 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? 9

  10. Adding function contracts 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..CAPACITYunsigned int nextOut; // ranges from 0..CAPACITY}; Can C++ be made as safe as SPARK? 10

  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? 11

  12. Adding function contracts #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..CAPACITYunsigned int nextOut; // ranges from 0..CAPACITY}; Can C++ be made as safe as SPARK? 12

  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 isarray(StorageIndex) of Character; type Queue is record data: Storage; nextIn: StorageIndex; nextOut: StorageIndex;end record; Can C++ be made as safe as SPARK? 13

  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? 14

  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; Can C++ be made as safe as SPARK? 15

  16. Using range-constrained types #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:typedefunsigned intinvariant(value <= capacity) StorageIndex; char data[capacity + 1u]; StorageIndex nextIn; StorageIndex nextOut; };

  17. What about the body? Can C++ be made as safe as SPARK? 17

  18. -- File BoundedQueue.adb package body BoundedQueue with SPARK_Mode isfunction empty(q: in Queue) return Boolean isbeginreturn q.nextIn = q.nextOutend; function full(q: in Queue) return Boolean isbeginreturn (q.nextIn + 1) mod (capacity + 1) = q.nextOut;end; procedure add(q: inout Queue; val: in Character) isbegin q.data(q.nextIn) := val; q.nextIn := (q.nextIn + 1) mod (capacity + 1);end; procedure remove(q: inout Queue; val: out Character) isbegin 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 SPARKend; end BoundedQueue;

  19. // 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));}

  20. Verification in SPARK 2012 GPL edition Can C++ be made as safe as SPARK? 20

  21. Verification of the C++ version …and so on until… Can C++ be made as safe as SPARK? 21

  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? 22

  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? 23

  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, ...) Can C++ be made as safe as SPARK? 24

  25. Retrieve function for a circular buffer • If nextIn >= nextOut, we want the elements in between them: data.take(nextIn) Queue contents are: data.take(nextIn) .drop(nextOut) data nextOut nextIn • 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: data.take(nextIn) data.drop(nextOut) Queue contents are: data.drop(nextOut) .concat(data.take(nextIn)) data nextIn nextOut

  26. const unsigned int capacity = 64u; class Queue {public:// Retrieve functionghost( ecv_seq<char> contents() constreturns( (nextIn >= nextOut) ? data.take(nextIn).drop(nextOut) : data.drop(nextOut).concat(data.take(nextIn)) ); ) bool empty() constreturns(contents().count == 0); bool full() constreturns(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: …

  27. Verification with data refinement …and so on… Can C++ be made as safe as SPARK? 27

  28. 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? 29

  29. 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)requirebooloperator<(X, X)post(…){ … } Can C++ be made as safe as SPARK? 30

  30. 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? 31

  31. 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? 32

  32. 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? Can C++ be made as safe as SPARK? 33

More Related