SPARK For The MISRA C Developer
SPARK For The MISRA C Developer
Developer
Release 2024-03
Yannick Moy
1 Preface 3
i
8 Detecting Unreachable Code and Dead Code 65
9 Conclusion 69
10 References 71
10.1 About MISRA C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
10.2 About SPARK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
10.3 About MISRA C and SPARK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
ii
SPARK for the MISRA-C Developer
This book presents the SPARK technology — the SPARK subset of Ada and its supporting
static analysis tools — through an example-driven comparison with the rules in the widely
known MISRA C subset of the C language.
This document was prepared by Yannick Moy, with contributions and review from Ben Bros-
gol.
Note: The code examples in this course use an 80-column limit, which is a typical limit
for Ada code. Note that, on devices with a small screen size, some code examples might
be difficult to read.
Note: Each code example from this book has an associated "code block metadata", which
contains the name of the "project" and an MD5 hash value. This information is used to
identify a single code example.
You can find all code examples in a zip file, which you can download from the learn website2 .
The directory structure in the zip file is based on the code block metadata. For example, if
you're searching for a code example with this metadata:
• Project: Courses.Intro_To_Ada.Imperative_Language.Greet
• MD5: cba89a34b87c9dfa71533d982d05e6ab
you will find it in this directory:
projects/Courses/Intro_To_Ada/Imperative_Language/Greet/
cba89a34b87c9dfa71533d982d05e6ab/
In order to use this code example, just follow these steps:
1. Unpack the zip file;
2. Go to target directory;
3. Start GNAT Studio on this directory;
4. Build (or compile) the project;
5. Run the application (if a main procedure is available in the project).
1 http://creativecommons.org/licenses/by-sa/4.0
2 https://learn.adacore.com/zip/learning-ada_code.zip
CONTENTS: 1
SPARK for the MISRA-C Developer
2 CONTENTS:
CHAPTER
ONE
PREFACE
3
SPARK for the MISRA-C Developer
deviation of rule 10.6 "The value of a composite expression shall not be assigned to an
object with wider essential type":
Here, the multiplication of two unsigned 16-bit values and assignment of the result to an
unsigned 32-bit variable constitutes a violation of the aforementioned rule, which gets justi-
fied for efficiency reasons. What the authors seem to have missed is that the multiplication
is then performed with the signed integer type int instead of the target unsigned type
uint32_t. Thus the multiplication of two unsigned 16-bit values may lead to an overflow
of the 32-bit intermediate signed result, which is an occurrence of an undefined behavior.
In such a case, a compiler is free to assume that the value of prod cannot exceed 231 - 1
(the maximal value of a signed 32-bit integer) as otherwise an undefined behavior would
have been triggered. For example, the undefined behavior with values 65535 for qty and
time_step is reported when running the code compiled by either the GCC or LLVM compiler
with option -fsanitize=undefined.
The MISRA C checkers that detect violations of undecidable rules are either unsound tools
that can detect only some of the violations, or sound tools that guarantee to detect all
such violations at the cost of possibly many false reports of violations. This is a direct
consequence of undecidability. However, static analysis technology is available that can
achieve soundness without inundating users with false alarms. One example is the SPARK
toolset developed by AdaCore, Altran and Inria, which is based on four principles:
• The base language Ada provides a solid foundation for static analysis through a well-
defined language standard, strong typing and rich specification features.
• The SPARK subset of Ada restricts the base language in essential ways to support static
analysis, by controlling sources of ambiguity such as side-effects and aliasing.
• The static analysis tools work mostly at the granularity of an individual function, mak-
ing the analysis more precise and minimizing the possibility of false alarms.
• The static analysis tools are interactive, allowing users to guide the analysis if neces-
sary or desired.
In this document, we show how SPARK can be used to achieve high code quality with guar-
antees that go beyond what would be feasible with MISRA C.
An on-line and interactive version of this document is available at AdaCore's
learn.adacore.com site7 .
7 https://learn.adacore.com/courses/SPARK_for_the_MISRA_C_Developer
4 Chapter 1. Preface
CHAPTER
TWO
Many consistency properties that are taken for granted in other languages are not enforced
in C. The basic property that all uses of a variable or function are consistent with its type is
not enforced by the language and is also very difficult to enforce by a tool. Three features
of C contribute to that situation:
• the textual-based inclusion of files means that every included declaration is subject to
a possibly different reinterpretation depending on context.
• the lack of consistency requirements across translation units means that type incon-
sistencies can only be detected at link time, something linkers are ill-equipped to do.
• the default of making a declaration externally visible means that declarations that
should be local will be visible to the rest of the program, increasing the chances for
inconsistencies.
MISRA C contains guidelines on all three fronts to enforce basic program consistency.
The text-based inclusion of files is one of the dated idiosyncracies of the C programming
language that was inherited by C++ and that is known to cause quality problems, especially
during maintenance. Although multiple inclusion of a file in the same translation unit can
be used to emulate template programming, it is generally undesirable. Indeed, MISRA C
defines Directive 4.10 precisely to forbid it for header files: "Precautions shall be taken in
order to prevent the contents of a header file being included more than once".
The subsequent section on "Preprocessing Directives" contains 14 rules restricting the use
of text-based inclusion through preprocessing. Among other things these rules forbid the
use of the #undef directive (which works around conflicts in macro definitions introduced by
text-based inclusion) and enforces the well-known practice of enclosing macro arguments
in parentheses (to avoid syntactic reinterpretations in the context of the macro use).
SPARK (and more generally Ada) does not suffer from these problems, as it relies on se-
mantic inclusion of context instead of textual inclusion of content, using with clauses:
Listing 1: hello_world.adb
1 with Ada.Text_IO;
2
3 procedure Hello_World is
4 begin
5 Ada.Text_IO.Put_Line ("hello, world!");
6 end Hello_World;
5
SPARK for the MISRA-C Developer
Project: Courses.SPARK_For_The_MISRA_C_Dev.Program_Consistency.Hello_World
MD5: 5ed9609dd61bbcee252bb8529a6d3479
Runtime output
hello, world!
Note that with clauses are only allowed at the beginning of files; the compiler issues an
error if they are used elsewhere:
Listing 2: hello_world.adb
1 procedure Hello_World is
2 with Ada.Text_IO; -- Illegal
3 begin
4 Ada.Text_IO.Put_Line ("hello, world!");
5 end Hello_World;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Program_Consistency.Hello_World
MD5: afa19e8e2c114a5832b49e9efcbe675e
Listing 3: hello_world.adb
1 with Ada.Text_IO;
2 with Ada.Text_IO; -- Legal but useless
3
4 procedure Hello_World is
5 begin
6 Ada.Text_IO.Put_Line ("hello, world!");
7 end Hello_World;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Program_Consistency.Hello_World
MD5: 270928968d7beb4809af9e62df530722
Runtime output
hello, world!
The order in which units are imported is irrelevant. All orders are valid and have the same
semantics.
No conflict arises from importing multiple units, even if the same name is defined in several,
since each unit serves as namespace for the entities which it defines. So we can define our
own version of Put_Line in some Helper unit and import it together with the standard
version defined in Ada.Text_IO:
Listing 4: helper.ads
1 package Helper is
2 procedure Put_Line (S : String);
3 end Helper;
Listing 5: helper.adb
1 with Ada.Text_IO;
2
Listing 6: hello_world.adb
1 with Ada.Text_IO;
2 with Helper;
3
4 procedure Hello_World is
5 begin
6 Ada.Text_IO.Put_Line ("hello, world!");
7 Helper.Put_Line ("hello, world!");
8 end Hello_World;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Program_Consistency.Hello_World
MD5: 5fa012cc996e24e3b1f604e35bbba44f
Runtime output
hello, world!
Start helper version
hello, world!
End helper version
The only way a conflict can arise is if we want to be able to reference Put_Line directly,
without using the qualified name Ada.Text_IO.Put_Line or Helper.Put_Line. The use
clause makes public declarations from a unit available directly:
Listing 7: helper.ads
1 package Helper is
2 procedure Put_Line (S : String);
3 end Helper;
Listing 8: helper.adb
1 with Ada.Text_IO;
2
Listing 9: hello_world.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Helper; use Helper;
3
4 procedure Hello_World is
5 begin
6 Ada.Text_IO.Put_Line ("hello, world!");
7 Helper.Put_Line ("hello, world!");
8 Put_Line ("hello, world!"); -- ERROR
9 end Hello_World;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Program_Consistency.Hello_World
MD5: 405e138d78e0dc869e8a340681d87e61
Build output
Here, both units Ada.Text_IO and Helper define a procedure Put_Line taking a String as
argument, so the compiler cannot disambiguate the direct call to Put_Line and issues an
error.
Note that it helpfully points to candidate declarations, so that the user can decide which
qualified name to use as in the previous two calls.
Issues arising in C as a result of text-based inclusion of files are thus completely prevented
in SPARK (and Ada) thanks to semantic import of units. Note that the C++ committee
identified this weakness some time ago and has approved8 the addition of modules to
C++20, which provide a mechanism for semantic import of units.
An issue related to text-based inclusion of files is that there is no single source for declaring
the type of a variable or function. If a file origin.c defines a variable var and functions
fun and print:
3 int var = 0;
4 int fun() {
5 return 1;
6 }
7 void print() {
8 printf("var = %d\n", var);
9 }
8 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4720.pdf
Project: Courses.SPARK_For_The_MISRA_C_Dev.Program_Consistency.Origin
MD5: 3395f1e43408d5bc5c1e6b8431c959d6
and the corresponding header file origin.h declares var, fun and print as having external
linkage:
Project: Courses.SPARK_For_The_MISRA_C_Dev.Program_Consistency.Origin
MD5: e8e880a16f5099dc1e0a75ffeeeb9468
then client code can include origin.h with declarations for var and fun:
3 int main() {
4 var = fun();
5 print();
6 return 0;
7 }
Project: Courses.SPARK_For_The_MISRA_C_Dev.Program_Consistency.Origin
MD5: 3d4582d3956897b657778ae355d0ef1b
Runtime output
var = 1
5 int main() {
6 var = fun();
7 print();
8 return 0;
9 }
Project: Courses.SPARK_For_The_MISRA_C_Dev.Program_Consistency.Origin
MD5: 4b25aa011b580f92f2831a48008fbef6
Runtime output
var = 1
Then, if an inconsistency is introduced in the type of var of fun between these alternative
declarations and their actual type, the compiler cannot detect it. Only the linker, which has
access to the set of object files for a program, can detect such inconsistencies. However, a
linker's main task is to link, not to detect inconsistencies, and so inconsistencies in the type
of variables and functions in most cases cannot be detected. For example, most linkers
cannot detect if the type of var or the return type of fun is changed to float in the decla-
rations above. With the declaration of var changed to float, the above program compiles
and runs without errors, producing the erroneous output var = 1065353216 instead of var
=1. With the return type of fun changed to float instead, the program still compiles and
runs without errors, producing this time the erroneous output var = 0.
The inconsistency just discussed is prevented by MISRA C Rule 8.3 "All declarations of an ob-
ject or function shall use the same names and type qualifiers". This is a decidable rule, but
it must be enforced at system level, looking at all translation units of the complete program.
MISRA C Rule 8.6 also requires a unique definition for a given identifier across translation
units, and Rule 8.5 requires that an external declaration shared between translation units
comes from the same file. There is even a specific section on "Identifiers" containing 9 rules
requiring uniqueness of various categories of identifiers.
SPARK (and more generally Ada) does not suffer from these problems, as it relies on se-
mantic inclusion of context using with clauses to provide a unique declaration for each
entity.
Many problems in C stem from the lack of encapsulation. There is no notion of namespace
that would allow a file to make its declarations available without risking a conflict with
other files. Thus MISRA C has a number of guidelines that discourage the use of external
declarations:
• Directive 4.8 encourages hiding the definition of structures and unions in implemen-
tation files (.c files) when possible: "If a pointer to a structure or union is never deref-
erenced within a translation unit, then the implementation of the object should be
hidden."
• Rule 8.7 forbids the use of external declarations when not needed: "Functions and
objects should not be defined with external linkage if they are referenced in only one
translation unit."
• Rule 8.8 forces the explicit use of keyword static when appropriate: "The static stor-
age class specifier shall be used in all declarations of objects and functions that have
internal linkage."
The basic unit of modularization in SPARK, as in Ada, is the package. A package always has
a spec (in an .ads file), which defines the interface to other units. It generally also has a
body (in an .adb file), which completes the spec with an implementation. Only declarations
from the package spec are visible from other units when they import (with) the package. In
fact, only declarations from what is called the "visible part" of the spec (before the keyword
private) are visible from units that with the package.
3 procedure Hello_World is
4 begin
5 Public_Put_Line ("hello, world!");
6 Private_Put_Line ("hello, world!"); -- ERROR
7 Body_Put_Line ("hello, world!"); -- ERROR
8 end Hello_World;
Build output
hello_world.adb:6:04: error: "Private_Put_Line" is not visible
hello_world.adb:6:04: error: non-visible (private) declaration at helper.ads:4
hello_world.adb:7:04: error: "Body_Put_Line" is undefined
gprbuild: *** compilation phase failed
Note the different errors on the calls to the private and body versions of Put_Line. In
the first case the compiler can locate the candidate procedure but it is illegal to call
it from Hello_World, in the second case the compiler does not even know about any
Body_Put_Line when compiling Hello_World since it only looks at the spec and not the
body.
SPARK (and Ada) also allow defining a type in the private part of a package spec while
simply declaring the type name in the public ("visible") part of the spec. This way, client
code — i.e., code that with's the package — can use the type, typically through a public
API, but have no access to how the type is implemented:
3 package Information_System is
4 Archive : Vault.Data;
5 end Information_System;
4 procedure Hacker is
5 V : Integer := Vault.Get (Information_System.Archive);
6 begin
7 Vault.Set (Information_System.Archive, V + 1);
8 Information_System.Archive.Val := 0; -- ERROR
9 end Hacker;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Program_Consistency.Hacker
MD5: 065ed34dc727e2eb0bdc50a667cb1f78
Build output
THREE
C's syntax is concise but also very permissive, which makes it easy to write programs whose
effect is not what was intended. MISRA C contains guidelines to:
• clearly distinguish code from comments
• specially handle function parameters and result
• ensure that control structures are not abused
The problem arises from block comments in C, starting with /* and ending with */. These
comments do not nest with other block comments or with line comments. For example,
consider a block comment surrounding three lines that each increase variable a by one:
/*
++a;
++a;
++a; */
Now consider what happens if the first line is commented out using a block comment and
the third line is commented out using a line comment (also known as a C++ style comment,
allowed in C since C99):
/*
/* ++a; */
++a;
// ++a; */
The result of commenting out code that was already commented out is that the second line
of code becomes live! Of course, the above example is simplified, but similar situations
do arise in practice, which is the reason for MISRA C Directive 4.1 "Sections of code should
not be 'commented out'". This is reinforced with Rules 3.1 and 3.2 from the section on
"Comments" that forbid in particular the use of /* inside a comment like we did above.
These situations cannot arise in SPARK (or in Ada), as only line comments are permitted,
using --:
-- A := A + 1;
-- A := A + 1;
-- A := A + 1;
So commenting again the first and third lines does not change the effect:
-- -- A := A + 1;
-- A := A + 1;
-- -- A := A + 1;
13
SPARK for the MISRA-C Developer
It is possible in C to ignore the result of a function call, either implicitly or else explicitly by
converting the result to void:
f();
(void)f();
This is particularly dangerous when the function returns an error status, as the caller is then
ignoring the possibility of errors in the callee. Thus the MISRA C Directive 4.7: "If a function
returns error information, then that error information shall be tested". In the general case
of a function returning a result which is not an error status, MISRA C Rule 17.7 states that
"The value returned by a function having non-void return type shall be used", where an
explicit conversion to void counts as a use.
In SPARK, as in Ada, the result of a function call must be used, for example by assigning it to
a variable or by passing it as a parameter, in contrast with procedures (which are equivalent
to void-returning functions in C). SPARK analysis also checks that the result of the function
is actually used to influence an output of the calling subprogram. For example, the first two
calls to F in the following are detected as unused, even though the result of the function
call is assigned to a variable, which is itself used in the second case:
Listing 1: fun.ads
1 package Fun is
2 function F return Integer is (1);
3 end Fun;
Listing 2: use_f.ads
1 procedure Use_F (Z : out Integer);
Listing 3: use_f.adb
1 with Fun; use Fun;
2
8 Y := F;
9 X := Y;
10
11 Z := F;
12 end Use_F;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Syntactic_Guarantees.Func_Return
MD5: 4fc78b4136677d6338984ab8ccfa5cd1
Prover output
Only the result of the third call is used to influence the value of an output of Use_F, here
the output parameter Z of the procedure.
In C, function parameters are treated as local variables of the function. They can be mod-
ified, but these modifications won't be visible outside the function. This is an opportunity
for mistakes. For example, the following code, which appears to swap the values of its
parameters, has in reality no effect:
MISRA C Rule 17.8 prevents such mistakes by stating that "A function parameter should not
be modified".
No such rule is needed in SPARK, since function parameters are only inputs so cannot be
modified, and procedure parameters have a mode defining whether they can be modified or
not. Only parameters of mode out or ada:in out can be modified — and these are prohibited
from functions in SPARK — and their modification is visible at the calling site. For example,
assigning to a parameter of mode in (the default parameter mode if omitted) results in
compilation errors:
Listing 4: swap.ads
1 procedure Swap (X, Y : Integer);
Listing 5: swap.adb
1 procedure Swap (X, Y : Integer) is
2 Tmp : Integer := X;
3 begin
4 X := Y; -- ERROR
5 Y := Tmp; -- ERROR
6 end Swap;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Syntactic_Guarantees.Swap
MD5: 187927c610e202f2e1eee6a602fda25e
Build output
5. Y := Tmp; -- ERROR
|
>>> assignment to "in" mode parameter not allowed
6. end Swap;
Listing 6: swap.ads
1 procedure Swap (X, Y : in out Integer);
Listing 7: swap.adb
1 procedure Swap (X, Y : in out Integer) is
2 Tmp : constant Integer := X;
3 begin
4 X := Y;
5 Y := Tmp;
6 end Swap;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Syntactic_Guarantees.Swap
MD5: c983a229fc5a69db5dbb85f49a91b325
Prover output
The previous issue (ignoring the result of a function call) is an example of a control structure
being abused, due to the permissive syntax of C. There are many such examples, and MISRA
C contains a number of guidelines to prevent such abuse.
Because a semicolon can act as a statement, and because an if-statement and a loop accept
a simple statement (possibly only a semicolon) as body, inserting a single semicolon can
completely change the behavior of the code:
int func() {
if (0)
return 1;
while (1)
return 0;
return 0;
}
As written, the code above returns with status 0. If a semicolon is added after the first line
(if (0);), then the code returns with status 1. If a semicolon is added instead after the
third line (while (1);), then the code does not return. To prevent such surprises, MISRA C
Rule 15.6 states that "The body of an iteration-statement or a selection-statement shall be
a compound statement" so that the code above must be written:
int func() {
if (0) {
return 1;
}
while (1) {
return 0;
}
return 0;
}
Note that adding a semicolon after the test of the if or while statement has the same
effect as before! But doing so would violate MISRA C Rule 15.6.
In SPARK, the semicolon is not a statement by itself, but rather a marker that terminates
a statement. The null statement is an explicit null;, and all blocks of statements have
explicit begin and end markers, which prevents mistakes that are possible in C. The SPARK
(also Ada) version of the above C code is as follows:
Listing 8: func.ads
1 function Func return Integer;
Listing 9: func.adb
1 function Func return Integer is
2 begin
3 if False then
4 return 1;
5 end if;
6 while True loop
7 return 0;
8 end loop;
9 return 0;
10 end Func;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Syntactic_Guarantees.Semicolon
MD5: 34fc5967c41d337aada17429ee5f44e9
Prover output
Switch statements are well-known for being easily misused. Control can jump to any case
section in the body of the switch, which in C can be before any statement contained in the
body of the switch. At the end of the sequence of statements associated with a case, exe-
cution continues with the code that follows unless a break is encountered. This is a recipe
for mistakes, and MISRA C enforces a simpler well-formed syntax for switch statements
defined in Rule 16.1: "All switch statements shall be well-formed".
The other rules in the section on "Switch statements" go on detailing individual conse-
quences of Rule 16.1. For example Rule 16.3 forbids the fall-through from one case to the
next: "An unconditional break statement shall terminate every switch-clause". As another
example, Rule 16.4 mandates the presence of a default case to handle cases not taken into
account explicitly: "Every switch statement shall have a default label".
The analog of the C switch statements in SPARK (and in Ada) is the case statement. This
statement has a simpler and more robust structure than the C switch, with control auto-
matically exiting after one of the case alternatives is executed, and the compiler checking
that the alternatives are disjoint (like in C) and complete (unlike in C). So the following code
is rejected by the compiler:
18 end Sign_Domain;
12 end Sign_Domain;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Syntactic_Guarantees.Case_Statement
MD5: d345a4d23b5b2402f8bd103e5e550a3b
Build output
sign_domain.adb:7:15: error: the choice "others" must appear alone and last
sign_domain.ads:6:07: error: missing case value: "Zero"
sign_domain.ads:14:15: error: duplication of choice value: "Positive" at line 13
gprbuild: *** compilation phase failed
The error in function Opposite is that the when choices do not cover all values of the target
expression. Here, A is of the enumeration type Sign, so all three values of the enumeration
must be covered.
The error in function Multiply is that Positive is covered twice, in the second and the
third alternatives. This is not allowed.
The error in procedure Get_Sign is that the others choice (the equivalent of C default
case) must come last. Note that an others choice would be useless in Opposite and Mul-
tiply, as all Sign values are covered.
Here is a correct version of the same code:
19 end Sign_Domain;
12 end Sign_Domain;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Syntactic_Guarantees.Case_Statement
MD5: 1c99fc53d2d2c0dddbea5e5b0a6c5746
Prover output
Similarly to C switches, for-loops in C can become unreadable. MISRA C thus enforces a sim-
pler well-formed syntax for for-loops, defined in Rule 14.2: "A for loop shall be well-formed".
The main effect of this simplification is that for-loops in C look like for-loops in SPARK (and
in Ada), with a loop counter that is incremented or decremented at each iteration. Section
8.14 defines precisely what a loop counter is:
1. It has a scalar type;
2. Its value varies monotonically on each loop iteration; and
3. It is used in a decision to exit the loop.
In particular, Rule 14.2 forbids any modification of the loop counter inside the loop body.
Here's the example used in MISRA C:2012 to illustrate this rule:
The equivalent SPARK (and Ada) code does not compile, because of the attempt to modify
the value of the loop counter:
7 if C then
8 Flag := True;
9 end if;
10
11 I := I + 3; -- ERROR
12 end loop;
13 end Well_Formed_Loop;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Syntactic_Guarantees.Well_Formed_Loop
MD5: 842564c961aa018e03e03f81439995ec
Build output
Removing the problematic line leads to a valid program. Note that the additional condition
being tested in the C for-loop has been moved to a separate exit statement at the start of
the loop body.
SPARK (and Ada) loops can increase (or, with explicit syntax, decrease) the loop counter by
1 at each iteration.
SPARK loops can iterate over any discrete type; i.e., integers as above or enumerations:
C does not provide a closing symbol for an if-statement. This makes it possible to write the
following code, which appears to try to return the absolute value of its argument, while it
actually does the opposite:
13 int main() {
14 printf("absval(5) = %d\n", absval(5));
15 printf("absval(0) = %d\n", absval(0));
16 printf("absval(-10) = %d\n", absval(-10));
17 }
Project: Courses.SPARK_For_The_MISRA_C_Dev.Syntactic_Guarantees.Dangling_Else_C
MD5: c180a948dd8bed4e3b97efde1522214c
Runtime output
absval(5) = -5
absval(0) = 0
absval(-10) = -10
The warning issued by GCC or LLVM with option -Wdangling-else (implied by -Wall) gives
a clue about the problem: although the else branch is written as though it completes the
outer if-statement, in fact it completes the inner if-statement.
MISRA C Rule 15.6 avoids the problem: "The body of an iteration-statement or a selection-
statement shall be a compound statement". That's the same rule as the one shown earlier
for Preventing the Semicolon Mistake (page 16). So the code for absval must be written:
15 int main() {
16 printf("absval(5) = %d\n", absval(5));
17 printf("absval(0) = %d\n", absval(0));
18 printf("absval(-10) = %d\n", absval(-10));
19 }
Project: Courses.SPARK_For_The_MISRA_C_Dev.Syntactic_Guarantees.Dangling_Else_
↪MISRA_C
MD5: 2b76377aca52ff45ed6b19fa1f367473
Runtime output
absval(5) = 5
absval(0) = 0
absval(-10) = 10
Project: Courses.SPARK_For_The_MISRA_C_Dev.Syntactic_Guarantees.Dangling_Else_Ada
MD5: e867b6354ef7bdd89bae1673e888153a
Prover output
Interestingly, SPARK analysis detects here that the negation operation on line 9 might over-
flow. That's an example of runtime error detection which will be covered in the chapter on
Detecting Undefined Behavior (page 59).
FOUR
Pointers in C provide a low-level view of the addressable memory as a set of integer ad-
dresses. To write at address 42, just go through a pointer:
Listing 1: main.c
1 int main() {
2 int *p = 42;
3 *p = 0;
4 return 0;
5 }
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Pointers_C
MD5: 005183ada50cb6642f38a3640d77efff
25
SPARK for the MISRA-C Developer
In an attempt to rule out issues that come from direct addressing of memory with pointers,
MISRA C states in Rule 11.4 that "A conversion should not be performed between a pointer
to object and an integer type". As this rule is classified as only Advisory, MISRA C completes
it with two Required rules:
• Rule 11.6: "A cast shall not be performed between pointer to void and an arithmetic
type"
• Rule 11.7: "A cast shall not be performed between pointer to object and a non-integer
arithmetic type"
In Ada, pointers are not addresses, and addresses are not integers. An opaque standard
type System.Address is used for addresses, and conversions to/from integers are provided
in a standard package System.Storage_Elements. The previous C code can be written as
follows in Ada:
Listing 2: pointer.adb
1 with System;
2 with System.Storage_Elements;
3
4 procedure Pointer is
5 A : constant System.Address := System.Storage_Elements.To_Address (42);
6 M : aliased Integer with Address => A;
7 P : constant access Integer := M'Access;
8 begin
9 P.all := 0;
10 end Pointer;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Pointers_Ada
MD5: 32ac91ade61a39d3505d155d7b97a8a5
Passing parameters by reference is critical for efficient programs, but the absence of refer-
ences distinct from pointers in C incurs a serious risk. Any parameter of a pointer type can
be copied freely to a variable whose lifetime is longer than the object pointed to, a problem
known as "dangling pointers". MISRA C forbids such uses in Rule 18.6: "The address of
an object with automatic storage shall not be copied to another object that persists after
the first object has ceased to exist". Unfortunately, enforcing this rule is difficult, as it is
undecidable.
In SPARK, parameters can be passed by reference, but no pointer to the parameter can be
stored past the return point of the function, which completely solves this issue. In fact, the
decision to pass a parameter by copy or by reference rests in many cases with the compiler,
but such compiler dependency has no effect on the functional behavior of a SPARK program.
In the example below, the compiler may decide to pass parameter P of procedure Rotate_X
either by copy or by reference, but regardless of the choice the postcondition of Rotate_X
will hold: the final value of P will be modified by rotation around the X axis.
Listing 3: geometry.ads
1 package Geometry is
2
10 end Geometry;
Listing 4: geometry.adb
1 package body Geometry is
2
10 end Geometry;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Geometry
MD5: d3801cf1413887ffd5fff8b6b86b7742
Prover output
SPARK's analysis tool can mathematically prove that the postcondition is true.
The greatest source of vulnerabilities regarding pointers is their use as substitutes for ar-
rays. Although the C language has a syntax for declaring and accessing arrays, this is just
a thin syntactic layer on top of pointers. Thus:
• Array access is just pointer arithmetic;
• If a function is to manipulate an array then the array's length must be separately
passed as a parameter; and
• The program is susceptible to the various vulnerabilities originating from the confusion
of pointers and arrays, such as buffer overflow.
Consider a function that counts the number of times a value is present in an array. In C,
this could be written:
Listing 5: main.c
1 #include <stdio.h>
2
13 int main() {
14 int p[5] = {0, 3, 9, 3, 3};
15 int c = count(p, 5, 3);
16 printf("value 3 is seen %d times in p\n", c);
17 return 0;
18 }
Runtime output
value 3 is seen 3 times in p
Function count has no control over the range of addresses accessed from pointer p. The
critical property that the len parameter is a valid length for an array of integers pointed to
by parameter p rests completely with the caller of count, and count has no way to check
that this is true.
To mitigate the risks associated with pointers being used for arrays, MISRA C contains eight
rules in a section on "Pointers and arrays". These rules forbid pointer arithmetic (Rule 18.4)
or, if this Advisory rule is not followed, require pointer arithmetic to stay within bounds (Rule
18.1). But, even if we rewrite the loop in count to respect all decidable MISRA C rules, the
program's correctness still depends on the caller of count passing a correct value of len:
Listing 6: main.c
1 #include <stdio.h>
2
13 int main() {
14 int p[5] = {0, 3, 9, 3, 3};
15 int c = count(p, 5, 3);
16 printf("value 3 is seen %d times in p\n", c);
17 return 0;
18 }
Runtime output
value 3 is seen 3 times in p
The resulting code is more readable, but still vulnerable to incorrect values of parameter
len passed by the caller of count, which violates undecidable MISRA C Rules 18.1 (pointer
arithmetic should stay within bounds) and 1.3 (no undefined behavior). Contrast this with
the same function in SPARK (and Ada):
Listing 7: types.ads
1 package Types is
2 type Int_Array is array (Positive range <>) of Integer;
3 end Types;
Listing 8: count.ads
1 with Types; use Types;
2
Listing 9: count.adb
1 function Count (P : Int_Array; V : Integer) return Natural is
2 Count : Natural := 0;
3 begin
4 for I in P'Range loop
5 if P (I) = V then
6 Count := Count + 1;
7 end if;
8 end loop;
9 return Count;
10 end Count;
5 procedure Test_Count is
6 P : constant Int_Array := (0, 3, 9, 3, 3);
7 C : constant Integer := Count (P, 3);
8 begin
9 Put_Line ("value 3 is seen" & C'Img & " times in p");
10 end Test_Count;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Arrays_Ada
MD5: 82e9d18d4b8ad8aa87ca8520bd7b830c
Runtime output
The array parameter P is not simply a homogeneous sequence of Integer values. The com-
piler must represent P so that its lower and upper bounds (P'First and P'Last) and thus
also its length (P'Length) can be retrieved. Function Count can simply loop over the range
of valid array indexes P'First .. P'Last (or P'Range for short). As a result, function
Count can be verified in isolation to be free of vulnerabilities such as buffer overflow, as
it does not depend on the values of parameters passed by its callers. In fact, we can go
further in SPARK and show that the value returned by Count is no greater than the length
of parameter P by stating this property in the postcondition of Count and asking the SPARK
analysis tool to prove it:
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Arrays_Ada
MD5: 4c9a34614d53c4d268cbff787c9b73e6
Prover output
Phase 1 of 2: generation of Global contracts ...
Phase 2 of 2: flow analysis and proof ...
count.adb:6:30: info: loop invariant preservation proved
count.adb:6:30: info: loop invariant initialization proved
count.adb:6:41: info: overflow check proved
count.adb:8:25: info: overflow check proved
count.ads:4:11: info: postcondition proved
count.ads:4:28: info: range check proved
The only help that SPARK analysis required from the programmer, in order to prove the
postcondition, is a loop invariant (a special kind of assertion) that reflects the value of
Count at each iteration.
The C language defines a special pointer type void* that corresponds to an untyped pointer.
It is legal to convert any pointer type to and from void*, which makes it a convenient
way to simulate C++ style templates. Consider the following code which indirectly applies
assign_int to integer i and assign_float to floating-point f by calling assign on both:
17 int main() {
18 int i;
19 float f;
20 assign((assign_fun)&assign_int, &i);
21 assign((assign_fun)&assign_float, &f);
22 printf("i = %d; f = %f\n", i, f);
23 }
Runtime output
i = 42; f = 42.000000
The references to the variables i and f are implicitly converted to the void* type as a
way to apply assign to any second parameter p whose type matches the argument type
of its first argument fun. The use of an untyped argument means that the responsibility
for the correct typing rests completely with the programmer. Swap i and f in the calls to
assign and you still get a compilable program without warnings, that runs and produces
completely bogus output:
i = 1109917696; f = 0.000000
i = 42; f = 42.000000
Generics in SPARK (and Ada) can implement the desired functionality in a fully typed way,
with any errors caught at compile time, where procedure Assign applies its parameter
procedure Initialize to its parameter V:
4 procedure Apply_Assign is
5 procedure Assign_Int (V : out Integer) is
6 begin
7 V := 42;
8 end Assign_Int;
9
18 I : Integer;
19 F : Float;
20 begin
21 Assign_I (I);
22 Assign_F (F);
23 Put_Line ("I =" & I'Img & "; F =" & F'Img);
24 end Apply_Assign;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Typed_Pointers_Ada
MD5: af23d6f8a742676139aac38a385c7bf7
Runtime output
I = 42; F = 4.20000E+01
The generic procedure Assign must be instantiated with a specific type for T and a specific
procedure (taking a single out parameter of this type) for Initialize. The procedure
resulting from the instantiation applies to a variable of this type. So switching I and F
above would result in an error detected by the compiler. Likewise, an instantiation such as
the following would also be a compile-time error:
In C, all scalar types can be converted both implicitly and explicitly to any other scalar type.
The semantics is defined by rules of promotion and conversion, which can confuse even
experts. One example was noted earlier, in the Preface (page 3). Another example appears
in an article introducing a safe library for manipulating scalars9 by Microsoft expert David
LeBlanc. In its conclusion, the author acknowledges the inherent difficulty in understanding
scalar type conversions in C, by showing an early buggy version of the code to produce the
minimum signed integer:
The issue here is that the literal 1 on the left-hand side of the shift is an int, so on a 64-bit
machine with 32-bit int and 64-bit type T, the above is shifting 32-bit value 1 by 63 bits.
This is a case of undefined behavior, producing an unexpected output with the Microsoft
compiler. The correction is to convert the first literal 1 to T before the shift:
Although he'd asked some expert programmers to review the code, no one found this prob-
lem.
To avoid these issues as much as possible, MISRA C defines its own type system on top of
C types, in the section on "The essential type model" (eight rules). These can be seen as
additional typing rules, since all rules in this section are decidable, and can be enforced at
the level of a single translation unit. These rules forbid in particular the confusing cases
mentioned above. They can be divided into three sets of rules:
• restricting operations on types
• restricting explicit conversions
• restricting implicit conversions
Apart from the application of some operations to floating-point arguments (the bitwise,
mod and array access operations) which are invalid and reported by the compiler, all oper-
ations apply to all scalar types in C. MISRA C Rule 10.1 constrains the types on which each
operation is possible as follows.
9 https://msdn.microsoft.com/en-us/library/ms972705.aspx
Adding two Boolean values, or an Apple and an Orange, might sound like a bad idea, but it
is easily done in C:
4 int main() {
5 bool b1 = true;
6 bool b2 = false;
7 bool b3 = b1 + b2;
8
16 return 0;
17 }
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Pointer_Arith_C
MD5: 30e28b34f616f8e6d35233a4ce698c23
Runtime output
b3 = 1; f3 = 1
No error from the compiler here. In fact, there is no undefined behavior in the above code.
Variables b3 and f3 both end up with value 1. Of course it makes no sense to add Boolean or
enumerated values, and thus MISRA C Rule 18.1 forbids the use of all arithmetic operations
on Boolean and enumerated values, while also forbidding most arithmetic operations on
characters. That leaves the use of arithmetic operations for signed or unsigned integers
as well as floating-point types and the use of modulo operation % for signed or unsigned
integers.
Here's an attempt to simulate the above C code in SPARK (and Ada):
12 end Bad_Arith;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Pointer_Arith_Ada
MD5: 984381fdcf1a682e1998f7881c0532f9
Build output
3 procedure Ok_Arith is
4
14 begin
15 pragma Assert (B1 = B3);
16 pragma Assert (F1 = F3);
17
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Pointer_Arith_Ada
MD5: 6ad400913a48fd815845b6a99d90ec2d
Runtime output
FALSE
TRUE
APPLE
ORANGE
4 int main() {
5 typedef enum {Ape, Bee, Cat} Animal;
6 bool answer = (2 * Bee) || ! (2 * Bee);
7 printf("two bee or not two bee? %d\n", answer);
8 return 0;
9 }
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Boolean_C
MD5: a9d4886827c983df51c9285fe3fd6c77
Runtime output
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Boolean_Ada
MD5: 9089114f9cc6495dabd6957b54b33bd2
Build output
As expected, the compiler rejects this code. There is no available * operation that works
on an enumeration type, and likewise no available or or not operation.
Here's a genetic engineering example that combines a Bee with a Dog to produce a Cat,
by manipulating the atomic structure (the bits in its representation):
4 int main() {
5 typedef enum {Ape, Bee, Cat, Dog} Animal;
6 Animal mutant = Bee ^ Dog;
7 assert (mutant == Cat);
8 return 0;
9 }
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Bitwise_C
MD5: 645b0b6155f1cb17d02c7bcbb976993c
This algorithm works by accessing the underlying bitwise representation of Bee and Dog
(0x01 and 0x03, respectively) and, by applying the exclusive-or operator ^, transforming it
into the underlying bitwise representation of a Cat (0x02). While powerful, manipulating the
bits in the representation of values is best reserved for unsigned integers as illustrated in
the book Hacker's Delight10 . MISRA C Rule 18.1 thus forbids the use of all bitwise operations
on anything but unsigned integers.
Below is an attempt to do the same in SPARK (and Ada). The bitwise operators are
and, or, xor, and not, and the related bitwise functions are Shift_Left, Shift_Right,
Shift_Right_Arithmetic, Rotate_Left and Rotate_Right:
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Bitwise_Ada
MD5: 3f7c3dd616f065016590d574200cf1db
Build output
The declaration of Mutant is illegal, since the xor operator is only available for Boolean
and unsigned integer (modular) values; it is not available for Animal. The same restriction
applies to the other bitwise operators listed above. If we really wanted to achieve the effect
of the above code in legal SPARK (or Ada), then the following approach will work (the type
Unsigned_8 is an 8-bit modular type declared in the predefined package Interfaces).
10 http://www.hackersdelight.org/
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Bitwise_Ada_2
MD5: 359439d40740fe2d99e6f334ed3500f9
Prover output
Note that and, or, not and xor are used both as logical operators and as bitwise operators,
but there is no possible confusion between these two uses. Indeed the use of such operators
on values from modular types is a natural generalization of their uses on Boolean, since
values from modular types are often interpreted as arrays of Booleans.
A simple way to bypass the restrictions of Rule 10.1 is to explicitly convert the arguments
of an operation to a type that the rule allows. While it can often be useful to cast a value
from one type to another, many casts that are allowed in C are either downright errors or
poor replacements for clearer syntax.
One example is to cast from a scalar type to Boolean. A better way to express (bool)x is
to compare x to the zero value of its type: x != 0 for integers, x != 0.0 for floats, x !=
'0' for characters, x != Enum where Enum is the first enumerated value of the type. Thus,
MISRA C Rule 10.5 advises avoiding casting non-Boolean values to Boolean.
Rule 10.5 also advises avoiding other casts that are, at best, obscure:
• from a Boolean to any other scalar type
• from a floating-point value to an enumeration or a character
• from any scalar type to an enumeration
The rules are not symmetric, so although a float should not be cast to an enum, casting an
enum to a float is allowed. Similarly, although it is advised to not cast a character to an
enum, casting an enum to a character is allowed.
The rules in SPARK are simpler. There are no conversions between numeric types (integers,
fixed-point and floating-point) and non-numeric types (such as Boolean, Character, and
other enumeration types). Conversions between different non-numeric types are limited
to those that make semantic sense, for example between a derived type and its parent
type. Any numeric type can be converted to any other numeric type, with precise rules for
rounding/truncating values when needed and run-time checking that the converted value
is in the range associated with the target type.
Rules 10.1 and 10.5 restrict operations on types and explicit conversions. That's not enough
to avoid problematic C programs; a program violating one of these rules can be expressed
using only implicit type conversions. For example, the Shakespearian code in section
Boolean Operations on Boolean (page 36) can be reformulated to satisfy both Rules 10.1
and 10.5:
4 int main() {
5 typedef enum {Ape, Bee, Cat} Animal;
6 int b = Bee;
7 bool t = 2 * b;
8 bool answer = t || ! t;
9 printf("two bee or not two bee? %d\n", answer);
10 return 0;
11 }
Project: Courses.SPARK_For_The_MISRA_C_Dev.Strong_Typing.Implicit_Conversion_C
MD5: a157dd05c5fe8926886361b533305e14
Runtime output
Here, we're implicitly converting the enumerated value Bee to an int, and then implicitly
converting the integer value 2 * b to a Boolean. This does not violate 10.1 or 10.5, but it
is prohibited by MISRA C Rule 10.3: "The value of an expression shall not be assigned to an
object with a narrower essential type or of a different essential type category".
Rule 10.1 also does not prevent arguments of an operation from being inconsistent, for
example comparing a floating-point value and an enumerated value. But MISRA C Rule
10.4 handles this situation: "Both operands of an operator in which the usual arithmetic
conversions are performed shall have the same essential type category".
In addition, three rules in the "Composite operators and expressions" section avoid common
mistakes related to the combination of explicit/implicit conversions and operations.
The rules in SPARK (and Ada) are far simpler: there are no implicit conversions! This applies
both between types of a different essential type category as MISRA C puts it, as well as
between types that are structurally the same but declared as different types.
MD5: f10b50048595df0b4ed77c06a7508412
Build output
bad_conversions.adb:12:09: error: expected type "Standard.Float"
bad_conversions.adb:12:09: error: found type "Standard.Integer"
bad_conversions.adb:13:09: error: expected type "Standard.Integer"
bad_conversions.adb:13:09: error: found type "Animal" defined at line 5
bad_conversions.adb:14:09: error: expected type "Animal" defined at line 5
bad_conversions.adb:14:09: error: found type "Standard.Boolean"
bad_conversions.adb:15:09: error: expected type "My_Animal" defined at line 6
bad_conversions.adb:15:09: error: found type "Animal" defined at line 5
bad_conversions.adb:16:09: error: expected type "Standard.Boolean"
bad_conversions.adb:16:09: error: found type "Standard.Character"
bad_conversions.adb:17:09: error: expected type "Standard.Character"
bad_conversions.adb:17:09: error: found type "Standard.Float"
gprbuild: *** compilation phase failed
The compiler reports a mismatch on every statement in the above procedure (the declara-
tions are all legal).
Adding explicit conversions makes the assignments to F and M valid, since SPARK (and Ada)
allow conversions between numeric types and between a derived type and its parent type,
but all other conversions are illegal:
MD5: 4d3f6a8629d51f27b6628dae5fc7b680
Build output
Although an enumeration value cannot be converted to an integer (or vice versa) either
implicitly or explicitly, SPARK (and Ada) provide functions to obtain the effect of a type
conversion. For any enumeration type T, the function T'Pos(e) takes an enumeration
value from type T and returns its relative position as an integer, starting at 0. For example,
Animal'Pos(Bee) is 1, and Boolean'Pos(False) is 0. In the other direction, T'Val(n),
where n is an integer, returns the enumeration value in type T at relative position n. If n is
negative or greater then T'Pos(T'Last) then a run-time exception is raised.
Hence, the following is valid SPARK (and Ada) code; Character is defined as an enumeration
type:
FIVE
As with most programming languages, C does not require that variables be initialized at
their declaration, which makes it possible to unintentionally read uninitialized data. This is
a case of undefined behavior, which can sometimes be used to attack the program.
Listing 1: f.h
1 #include <stdint.h>
2
Listing 2: f.c
1 #include "f.h"
2
Listing 3: g.c
1 #include <stdint.h>
2 #include "f.h"
3
8 f ( 0, &u );
9
10 if ( u == 3U )
(continues on next page)
43
SPARK for the MISRA-C Developer
Project: Courses.SPARK_For_The_MISRA_C_Dev.Initialization.Read_Uninitialized_Data_C
MD5: f36430141f48b34810d53a43294c7d74
Detecting the violation of Rule 9.1 can be arbitrarily complex, as the program points corre-
sponding to a variable's initialization and read can be separated by many calls and condi-
tions. This is one of the undecidable rules, for which most MISRA C checkers won't detect
all violations.
In SPARK, the guarantee that all reads are to initialized data is enforced by the SPARK
analysis tool, GNATprove, through what is referred to as flow analysis. Every subprogram
is analyzed separately to check that it cannot read uninitialized data. To make this modular
analysis possible, SPARK programs need to respect the following constraints:
• all inputs of a subprogram should be initialized on subprogram entry
• all outputs of a subprogram should be initialized on subprogram return
Hence, given the following code translated from C, GNATprove reports that function F might
not always initialize output parameter P:
Listing 4: init.ads
1 with Interfaces; use Interfaces;
2
3 package Init is
4 procedure F (B : Boolean; P : out Unsigned_16);
5 procedure G;
6 end Init;
Listing 5: init.adb
1 package body Init is
2
10 procedure G is
11 U : Unsigned_16;
12 begin
13 F (False, U);
14
15 if U = 3 then
16 null;
17 end if;
18 end G;
19
20 end Init;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Initialization.Read_Uninitialized_Data_
↪Ada
MD5: d54bc9901b3bff4f0cfea9942a795156
Prover output
We can correct the program by initializing P to value 0 when condition B is not satisfied:
Listing 6: init.ads
1 with Interfaces; use Interfaces;
2
3 package Init is
4 procedure F (B : Boolean; P : out Unsigned_16);
5 procedure G;
6 end Init;
Listing 7: init.adb
1 package body Init is
2
12 procedure G is
13 U : Unsigned_16;
14 begin
15 F (False, U);
16
17 if U = 3 then
18 null;
19 end if;
20 end G;
21
22 end Init;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Initialization.Read_Uninitialized_Data_
↪Ada
MD5: 481787c333014d56814a7205720f72bc
Prover output
GNATprove now does not report any possible reads of uninitialized data. On the contrary,
it confirms that all reads are made from initialized data.
In contrast with C, SPARK does not guarantee that global data (called library-level data in
SPARK and Ada) is zero-initialized at program startup. Instead, GNATprove checks that all
global data is explicitly initialized (at declaration or elsewhere) before it is read. Hence it
goes beyond the MISRA C Rule 9.1, which considers global data as always initialized even if
the default value of all-zeros might not be valid data for the application. Here's a variation
of the above code where variable U is now global:
Listing 8: init.ads
1 with Interfaces; use Interfaces;
2
3 package Init is
4 U : Unsigned_16;
5 procedure F (B : Boolean);
6 procedure G;
7 end Init;
Listing 9: init.adb
1 package body Init is
2
3 procedure F (B : Boolean) is
4 begin
5 if B then
6 U := 3;
7 end if;
8 end F;
9
10 procedure G is
11 begin
12 F (False);
13
14 if U = 3 then
15 null;
16 end if;
17 end G;
18
19 end Init;
3 procedure Call_Init is
4 begin
5 Init.G;
6 end Call_Init;
MD5: a85cde45a658727975367b041a1a5dc3
Prover output
Phase 1 of 2: generation of Global contracts ...
Phase 2 of 2: analysis of data and information flow ...
call_init.adb:5:08: medium: "U" might not be initialized after elaboration of main␣
(continues on next page)
GNATprove reports here that variable U might not be initialized at program startup, which
is indeed the case here. It reports this issue on the main program Call_Init because its
analysis showed that F needs to take U as an initialized input (since F is not initializing U on
all paths, U keeps its value on the other path, which needs to be an initialized value), which
means that G which calls F also needs to take U as an initialized input, which in turn means
that Call_Init which calls G also needs to take U as an initialized input. At this point,
we've reached the main program, so the initialization phase (referred to as elaboration in
SPARK and Ada) should have taken care of initializing U. This is not the case here, hence
the message from GNATprove.
It is possible in SPARK to specify that G should initialize variable U; this is done with a data
dependency contract introduced with aspect Global following the declaration of procedure
G:
3 package Init is
4 U : Unsigned_16;
5 procedure F (B : Boolean);
6 procedure G with Global => (Output => U);
7 end Init;
3 procedure F (B : Boolean) is
4 begin
5 if B then
6 U := 3;
7 end if;
8 end F;
9
10 procedure G is
11 begin
12 F (False);
13
14 if U = 3 then
15 null;
16 end if;
17 end G;
18
19 end Init;
3 procedure Call_Init is
4 begin
5 Init.G;
6 end Call_Init;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Initialization.Read_Uninitialized_Data_
↪Ada
MD5: 100122ca3c8c60c134822a85d564a60a
Prover output
Phase 1 of 2: generation of Global contracts ...
Phase 2 of 2: analysis of data and information flow ...
init.adb:12:07: high: "U" is not initialized
init.adb:12:07: high: "U" is not an input in the Global contract of subprogram "G"␣
↪at init.ads:6
init.adb:12:07: high: either make "U" an input in the Global contract or␣
↪initialize it before use
GNATprove reports the error on the call to F in G, as it knows at this point that F needs U
to be initialized but the calling context in G cannot provide that guarantee. If we provide
the same data dependency contract for F, then GNATprove reports the error on F itself,
similarly to what we saw for an output parameter U.
The other rules in the section on "Initialization" deal with common errors in initializing ag-
gregates and designated initializers in C99 to initialize a structure or array at declaration.
These rules attempt to patch holes created by the lax syntax and rules in C standard. For
example, here are five valid initializations of an array of 10 elements in C:
Only a is fully initialized to all-zeros in the above code snippet. MISRA C Rule 9.3 thus forbids
all other declarations by stating that "Arrays shall not be partially initialized". In addition,
MISRA C Rule 9.4 forbids the declaration of e by stating that "An element of an object shall
not be initialised more than once" (in e's declaration, the element at index 8 is initialized
twice).
The same holds for initialization of structures. Here is an equivalent set of declarations with
the same potential issues:
Project: Courses.SPARK_For_The_MISRA_C_Dev.Initialization.Redundant_Init
MD5: e562ef70b8c8a170d2bd09281cf2a075
Here only a, d and e are fully initialized. MISRA C Rule 9.3 thus forbids the declarations of
b and c. In addition, MISRA C Rule 9.4 forbids the declaration of e.
In SPARK and Ada, the aggregate used to initialize an array or a record must fully cover the
components of the array or record. Violations lead to compilation errors, both for records:
Project: Courses.SPARK_For_The_MISRA_C_Dev.Initialization.Init_Record_1
MD5: 6b28bffe6270c5ea5055123c5b89c508
Build output
Project: Courses.SPARK_For_The_MISRA_C_Dev.Initialization.Init_Array_1
MD5: 81aa6363ba770ded10bef8d3d8776914
Build output
init_array.ads:3:15: warning: too few elements for type "Arr" defined at line 2␣
↪[enabled by default]
Project: Courses.SPARK_For_The_MISRA_C_Dev.Initialization.Init_Record_2
MD5: 07d3f790009be97cef2daaf08b2f7afd
Build output
Project: Courses.SPARK_For_The_MISRA_C_Dev.Initialization.Init_Array_2
MD5: 12f5fa4615abccde43f63f72340fd4a0
Build output
init_array.ads:3:43: error: 7
gprbuild: *** compilation phase failed
Finally, while it is legal in Ada to leave uninitialized parts in a record or array aggregate
by using the box notation (meaning that the default initialization of the type is used, which
may be no initialization at all), SPARK analysis rejects such use when it leads to components
not being initialized, both for records:
Project: Courses.SPARK_For_The_MISRA_C_Dev.Initialization.Init_Record_3
MD5: a7736f2b563c39fb4ab10007e927ad97
Prover output
↪Record_3/a7736f2b563c39fb4ab10007e927ad97/main_spark.adc:12
↪Record_3/a7736f2b563c39fb4ab10007e927ad97/main_spark.adc:12
SIX
As with most programming languages, C allows side effects in expressions. This leads to
subtle issues about conflicting side effects, when subexpressions of the same expression
read/write the same variable.
Conflicting side effects are a kind of undefined behavior; the C Standard (section 6.5) de-
fines the concept as follows:
"Between two sequence points, an object is modified more than once, or is mod-
ified and the prior value is read other than to determine the value to be stored"
This legalistic wording is somewhat opaque, but the notion of sequence points is summa-
rized in Annex C of the C90 and C99 standards. MISRA C repeats these conditions in the
Amplification of Rule 13.2, including the read of a volatile variable as a side effect similar
to writing a variable.
This rule is undecidable, so MISRA C completes it with two rules that provide simpler restric-
tions preventing some side effects in expressions, thus reducing the potential for undefined
behavior:
• Rule 13.3: "A full expression containing an increment (++) or decrement (--) operator
should have no other potential side effects other than that caused by the increment
or decrement operator".
• Rule 13.4: "The result of an assignment operator should not be used".
In practice, conflicting side effects usually manifest themselves as portability issues, since
the result of the evaluation of an expression depends on the order in which a compiler
decides to evaluate its subexpressions. So changing the compiler version or the target
platform might lead to a different behavior of the application.
To reduce the dependency on evaluation order, MISRA C Rule 13.1 states: "Initializer lists
shall not contain persistent side effects". This case is theoretically different from the previ-
ously mentioned conflicting side effects, because initializers that comprise an initializer list
are separated by sequence points, so there is no risk of undefined behavior if two initializ-
ers have conflicting side effects. But given that initializers are executed in an unspecified
order, the result of a conflict is potentially as damaging for the application.
53
SPARK for the MISRA-C Developer
Even in cases with no undefined or unspecified behavior, expressions with multiple side
effects can be confusing to programmers reading or maintaining the code. This problem
arises in particular with C's increment and decrement operators that can be applied prior
to or after the expression evaluation, and with the assignment operator = in C since it can
easily be mistaken for equality. Thus MISRA C forbids the use of the increment / decrement
(Rule 13.3) and assignment (Rule 13.4) operators in expressions that have other potential
side effects.
In other cases, the presence of expressions with side effects might be confusing, if the
programmer wrongly thinks that the side effects are guaranteed to occur. Consider the
function decrease_until_one_is_null below, which decreases both arguments until one
is null:
Listing 1: main.c
1 #include <stdio.h>
2
12 int main() {
13 int x = 42, y = 42;
14 decrease_until_one_is_null (&x, &y);
15 printf("x = %d, y = %d\n", x, y);
16 return 0;
17 }
Project: Courses.SPARK_For_The_MISRA_C_Dev.Side_Effect.Side_Effect_C
MD5: a3e991881894bc3fb25a5f49a083fd2e
Runtime output
x = 0, y = 1
x = 0, y = 1
I.e., starting from the same value 42 for both x and y, only x has reached the value zero
after decrease_until_one_is_null returns. The reason is that the side effect on y is
performed only conditionally. To avoid such surprises, MISRA C Rule 13.5 states: "The right
hand operand of a logical && or || operator shall not contain persistent side effects"; this
rule forbids the code above.
MISRA C Rule 13.6 similarly states: "The operand of the sizeof operator shall not contain any
expression which has potential side effects". Indeed, the operand of sizeof is evaluated
only in rare situations, and only according to C99 rules, which makes any side effect in such
an operand a likely mistake.
In SPARK, expressions cannot have side effects; only statements can. In particular, there
are no increment/decrement operators, and no assignment operator. There is instead an
assignment statement, whose syntax using := clearly distinguishes it from equality (using
=). And in any event an expression is not allowed as a statement and this a construct such
as X = Y; would be illegal. Here is how a variable X can be assigned, incremented and
decremented:
X := 1;
X := X + 1;
X := X - 1;
Listing 2: volatile_read.ads
1 package Volatile_Read is
2 X : Integer with Volatile;
3 procedure P (Y : out Integer);
4 end Volatile_Read;
Listing 3: volatile_read.adb
1 package body Volatile_Read is
2 procedure P (Y : out Integer) is
3 begin
4 Y := X - X; -- ERROR
5 end P;
6 end Volatile_Read;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Side_Effect.Volatile_Read_1
MD5: 7ec58b4d1432d03d60b5ea6019cc031e
Prover output
Instead, every read of a volatile variable must occur immediately before being assigned to
another variable, as follows:
Listing 4: volatile_read.ads
1 package Volatile_Read is
2 X : Integer with Volatile;
3 procedure P (Y : out Integer);
4 end Volatile_Read;
Listing 5: volatile_read.adb
1 package body Volatile_Read is
2 procedure P (Y : out Integer) is
3 X1 : constant Integer := X;
4 X2 : constant Integer := X;
5 begin
6 Y := X1 - X2;
7 end P;
8 end Volatile_Read;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Side_Effect.Volatile_Read_2
MD5: 1224af597a12a8ca77b96976c76b422f
Prover output
Note here that the order of capture of the volatile value of X might be significant. For
example, X might denote a quantity which only increases, like clock time, so that the above
expression X1 - X2 would always be negative or zero.
Even more significantly, functions in SPARK cannot have side effects; only procedures can.
The only effect of a SPARK function is the computation of a result from its inputs, which
may be passed as parameters or as global variables. In particular, SPARK functions cannot
have out or in out parameters:
Listing 6: bad_function.ads
1 function Bad_Function (X, Y : Integer; Sum, Max : out Integer) return Boolean;
2 -- ERROR, since "out" parameters are not allowed
Project: Courses.SPARK_For_The_MISRA_C_Dev.Side_Effect.Function_With_Out_Param
MD5: 204dd22df61fe15208ae34ebc3828974
Prover output
↪With_Out_Param/204dd22df61fe15208ae34ebc3828974/main_spark.adc:12
More generally, SPARK does not allow functions that have a side effect in addition to return-
ing their result, as is typical of many idioms in other languages, for example when setting
a new value and returning the previous one:
Listing 7: bad_functions.ads
1 package Bad_Functions is
2 function Set (V : Integer) return Integer;
3 function Get return Integer;
4 end Bad_Functions;
Listing 8: bad_functions.adb
1 package body Bad_Functions is
2
3 Value : Integer := 0;
4
14 end Bad_Functions;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Side_Effect.Side_Effect_Ada
MD5: 3337b6025c4996e7fa8c7e27b4df42c1
Prover output
GNATprove detects that function Set has a side effect on global variable Value and issues
an error. The correct idiom in SPARK for such a case is to use a procedure with an out
parameter to return the desired result:
Listing 9: ok_subprograms.ads
1 package Ok_Subprograms is
2 procedure Set (V : Integer; Prev : out Integer);
3 function Get return Integer;
4 end Ok_Subprograms;
3 Value : Integer := 0;
4
13 end Ok_Subprograms;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Side_Effect.No_Side_Effect_Ada
MD5: 04e2235b8b6a01706434d35f6636674c
Prover output
With the above restrictions in SPARK, none of the conflicts of side effects that can occur in
C can occur in SPARK, and this is guaranteed by flow analysis.
SEVEN
Undefined behavior (and critical unspecified behavior, which we'll treat as undefined behav-
ior) are the plague of C programs. Many rules in MISRA C are designed to avoid undefined
behavior, as evidenced by the twenty occurrences of "undefined" in the MISRA C:2012 doc-
ument.
MISRA C Rule 1.3 is the overarching rule, stating very simply:
"There shall be no occurrence of undefined or critical unspecified behaviour."
The deceptive simplicity of this rule rests on the definition of undefined or critical unspec-
ified behaviour. Appendix H of MISRA:C 2012 lists hundreds of cases of undefined and
critical unspecified behavior in the C programming language standard, a majority of which
are not individually decidable.
It is therefore not surprising that a majority of MISRA C checkers do not make a serious
attempt to verify compliance with MISRA C Rule 1.3.
Since SPARK is a subset of the Ada programming language, SPARK programs may exhibit
two types of undefined behaviors that can occur in Ada:
• bounded error: when the program enters a state not defined by the language seman-
tics, but the consequences are bounded in various ways. For example, reading unini-
tialized data can lead to a bounded error, when the value read does not correspond to
a valid value for the type of the object. In this specific case, the Ada Reference Manual
states that either a predefined exception is raised or execution continues using the
invalid representation.
• erroneous execution: when when the program enters a state not defined by the lan-
guage semantics, but the consequences are not bounded by the Ada Reference Man-
ual. This is the closest to an undefined behavior in C. For example, concurrently writing
through different tasks to the same unprotected variable is a case of erroneous exe-
cution.
Many cases of undefined behavior in C would in fact raise exceptions in SPARK. For example,
accessing an array beyond its bounds raises the exception Constraint_Error while reach-
ing the end of a function without returning a value raises the exception Program_Error.
The SPARK Reference Manual defines the SPARK subset through a combination of legality
rules (checked by the compiler, or the compiler-like phase preceding analysis) and verifica-
tion rules (checked by the formal analysis tool GNATprove). Bounded errors and erroneous
execution are prevented by a combination of legality rules and the flow analysis part of
GNATprove, which in particular detects potential reads of uninitialized data, as described in
Detecting Reads of Uninitialized Data (page 43). The following discussion focuses on how
SPARK can verify that no exceptions can be raised.
59
SPARK for the MISRA-C Developer
The most common run-time errors are related to misuse of arithmetic (division by zero, over-
flows, exceeding the range of allowed values), arrays (accessing beyond an array bounds,
assigning between arrays of different lengths), and structures (accessing components that
are not defined for a given variant).
Arithmetic run-time errors can occur with signed integers, unsigned integers, fixed-point
and floating-point (although with IEEE 754 floating-point arithmetic, errors are manifest as
special run-time values such as NaN and infinities rather than as exceptions that are raised).
These errors can occur when applying arithmetic operations or when converting between
numeric types (if the value of the expression being converted is outside the range of the
type to which it is being converted).
Operations on enumeration values can also lead to run-time errors; e.g., T'Pred(T'First)
or T'Succ(T'Last) for an enumeration type T, or T'Val(N) where N is an integer value
that is outside the range 0 .. T'Pos(T'Last).
The Update procedure below contains what appears to be a simple assignment statement,
which sets the value of array element A(I+J) to P/Q.
Listing 1: show_runtime_errors.ads
1 package Show_Runtime_Errors is
2
8 end Show_Runtime_Errors;
Listing 2: show_runtime_errors.adb
1 package body Show_Runtime_Errors is
2
8 end Show_Runtime_Errors;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Undefined_Behavior.Runtime_Errors
MD5: 8ad4488974ab9e49ac17bf094ae33eac
Prover output
↪bits machine integer] [possible fix: add precondition (if J >= 0 then I <=␣
↪errors.ads:6]
↪add precondition (if J >= 0 then I <= A'Last - J else I >= A'First - J) to␣
↪subprogram at show_runtime_errors.ads:6]
↪subprogram at show_runtime_errors.ads:6]
↪subprogram at show_runtime_errors.ads:6]
• The division P / Q may overflow in the special case where P is Integer'First and Q
is -1, because of the asymmetric range of signed integer types.
A (I + J) := Integer'First / -1;
• Since the array can only contain non-negative numbers (the element subtype is Nat-
ural), it is also an error to store a negative value in it.
A (I + J) := 1 / -1;
For each of these potential run-time errors, the compiler will generate checks in the exe-
cutable code, raising an exception if any of the checks fail:
A (Integer'Last + 1) := P / Q;
-- raised CONSTRAINT_ERROR : overflow check failed
A (A'Last + 1) := P / Q;
-- raised CONSTRAINT_ERROR : index check failed
A (I + J) := Integer'First / (-1);
-- raised CONSTRAINT_ERROR : overflow check failed
A (I + J) := 1 / (-1);
-- raised CONSTRAINT_ERROR : range check failed
A (I + J) := P / 0;
-- raised CONSTRAINT_ERROR : divide by zero
These run-time checks incur an overhead in program size and execution time. Therefore it
may be appropriate to remove them if we are confident that they are not needed.
The traditional way to obtain the needed confidence is through testing, but it is well known
that this can never be complete, at least for non-trivial programs. Much better is to guaran-
tee the absence of run-time errors through sound static analysis, and that's where SPARK
and GNATprove can help.
More precisely, GNATprove logically interprets the meaning of every instruction in the pro-
gram, taking into account both control flow and data/information dependencies. It uses
this analysis to generate a logical formula called a verification condition for each possible
check.
A (Integer'Last + 1) := P / Q;
-- medium: overflow check might fail
A (A'Last + 1) := P / Q;
-- medium: array index check might fail
A (I + J) := Integer'First / (-1);
-- medium: overflow check might fail
A (I + J) := 1 / (-1);
-- medium: range check might fail
A (I + J) := P / 0;
-- medium: divide by zero might fail
The verification conditions are then given to an automatic prover. If every verification con-
dition can be proved, then no run-time errors will occur.
GNATprove's analysis is sound — it will detect all possible instances of run-time exceptions
being raised — while also having high precision (i.e., not producing a cascade of "false
alarms").
The way to program in SPARK so that GNATprove can guarantee the absence of run-time
errors entails:
• declaring variables with precise constraints, and in particular to specify precise ranges
for scalars; and
• defining preconditions and postconditions on subprograms, to specify respectively the
constraints that callers should respect and the guarantees that the subprogram should
provide on exit.
For example, here is a revised version of the previous example, which can guarantee
through proof that no possible run-time error can be raised:
Listing 3: no_runtime_errors.ads
1 package No_Runtime_Errors is
2
13 end No_Runtime_Errors;
Listing 4: no_runtime_errors.adb
1 package body No_Runtime_Errors is
2
10 end No_Runtime_Errors;
EIGHT
MISRA C defines unreachable code as code that cannot be executed, and it defines dead
code as code that can be executed but has no effect on the functional behavior of the
program. (These definitions differ from traditional terminology, which refers to the first
category as "dead code" and the second category as "useless code".) Regardless of the
terminology, however, both types are actively harmful, as they might confuse programmers
and lead to errors during maintenance.
The "Unused code" section of MISRA C contains seven rules that deal with detecting both
unreachable code and dead code. The two most important rules are:
• Rule 2.1: "A project shall not contain unreachable code", and
• Rule 2.2: "There shall not be dead code".
Other rules in the same section prohibit unused entities of various kinds (type declarations,
tag declarations, macro declarations, label declarations, function parameters).
While some simple cases of unreachable code can be detected by static analysis (typically
if a condition in an if statement can be determined to be always true or false), most cases
of unreachable code can only be detected by performing coverage analysis in testing, with
the caveat that code reported as not being executed is not necessarily unreachable (it
could simply reflect gaps in the test suite). Note that statement coverage, rather than the
more comprehensive decision coverage or modified condition / decision coverage (MC/DC)
as defined in the DO-178C standard for airborne software, is sufficient to detect poten-
tial unreachable statements, corresponding to code that is not covered during the testing
campaign.
The presence of dead code is much harder to detect, both statically and dynamically, as it
requires creating a complete dependency graph linking statements in the code and their
effect on visible behavior of the program.
SPARK can detect some cases of both unreachable and dead code through its precise con-
struction of a dependency graph linking a subprogram's statements to all its inputs and
outputs. This analysis might not be able to detect complex cases, but it goes well beyond
what other analyses do in general.
Listing 1: much_ado_about_little.ads
1 procedure Much_Ado_About_Little (X, Y, Z : Integer; Success : out Boolean);
Listing 2: much_ado_about_little.adb
1 procedure Much_Ado_About_Little (X, Y, Z : Integer; Success : out Boolean) is
2
3 procedure Ok is
4 begin
5 Success := True;
6 end Ok;
7
65
SPARK for the MISRA-C Developer
13 begin
14 Success := False;
15
16 for K in Y .. Z loop
17 if K < X and not Success then
18 Ok;
19 end if;
20 end loop;
21
22 if X > Y then
23 Ok;
24 else
25 NOk;
26 end if;
27
28 if Z > Y then
29 NOk;
30 return;
31 else
32 Ok;
33 return;
34 end if;
35
36 if Success then
37 Success := not Success;
38 end if;
39 end Much_Ado_About_Little;
Project: Courses.SPARK_For_The_MISRA_C_Dev.Unreachable_And_Dead_Code.Much_Ado_
↪About_Little
MD5: ccccb112fbab169ba964b3f8ef36ec2d
Build output
Prover output
The only code in the body of Much_Ado_About_Little that affects the result of the pro-
cedure's execution is the if Z > Y... statement, since this statement sets Success to
either True or False regardless of what the previous statements did. I.e., the statements
preceding this if are dead code in the MISRA C sense. Since both branches of the if Z
> Y... statement return from the procedure, the subsequent if Success... statement is
unreachable. GNATprove detects and issues warnings about both the dead code and the
unreachable code.
67
SPARK for the MISRA-C Developer
NINE
CONCLUSION
The C programming language is "close to the metal" and has emerged as a lingua franca
for the majority of embedded platforms of all sizes. However, its software engineering
deficiencies (such as the absence of data encapsulation) and its many traps and pitfalls
present major obstacles to those developing critical applications. To some extent, it is
possible to put the blame for programming errors on programmers themselves, as Linus
Torvalds admonished:
"Learn C, instead of just stringing random characters together until it compiles
(with warnings)."
But programmers are human, and even the best would be hard pressed to be 100% cor-
rect about the myriad of semantic details such as those discussed in this document. Pro-
gramming language abstractions have been invented precisely to help developers focus
on the "big picture" (thinking in terms of problem-oriented concepts) rather than low-level
machine-oriented details, but C lacks these abstractions. As Kees Cook from the Kernel Self
Protection Project puts it (during the Linux Security Summit North America 2018):
"Talking about C as a language, and how it's really just a fancy assembler"
Even experts sometimes have problems with the C programming language rules, as illus-
trated by Microsoft expert David LeBlanc (see Enforcing Strong Typing for Scalars (page 33))
or the MISRA C Committee itself (see the Preface (page 3)).
The rules in MISRA C represent an impressive collective effort to improve the reliability of
C code in critical applications, with a focus on avoiding error-prone features rather than
enforcing a particular programming style. The Rationale provided with each rule is a clear
and unobjectionable justification of the rule's benefit.
At a fundamental level, however, MISRA C is still built on a base language that was not
really designed with the goal of supporting large high-assurance applications. As shown
in this document, there are limits to what static analysis can enforce with respect to the
MISRA C rules. It's hard to retrofit reliability, safety and security into a language that did
not have these as goals from the start.
The SPARK language took a different approach, starting from a base language (Ada) that
was designed from the outset to support solid software engineering, and eliminating fea-
tures that were implementation dependent or otherwise hard to formally analyze. In this
document we have shown how the SPARK programming language and its associated formal
verification tools can contribute usefully to the goal of producing error-free software, going
beyond the guarantees that can be achieved in MISRA C.
69
SPARK for the MISRA-C Developer
70 Chapter 9. Conclusion
CHAPTER
TEN
REFERENCES
The official website of the MISRA association https://www.misra.org.uk/ has many freely
available resources about MISRA C, some of which can be downloaded after registering on
the MISRA Bulletin Board at https://www.misra.org.uk/forum/ (such as the examples from
the MISRA C:2012 standard, which includes a one-line description of each guideline).
The following documents are freely available:
• MISRA Compliance 2016: Achieving compliance with MISRA coding guidelines, 2016,
which explains the rationale and process for compliance, including a thorough discus-
sions of acceptable deviations
• MISRA C:2012 - Amendment 1: Additional security guidelines for MISRA C:2012, 2016,
which contains 14 additional guidelines focusing on security. This is a minor addition
to MISRA C.
The main MISRA C:2012 document can be purchased from the MISRA webstore.
PRQA is the company that first developed MISRA C, and they have been heavily involved
in every version since then. Their webpage http://www.prqa.com/coding-standards/misra/
contains many resources about MISRA C: product datasheets, white papers, webinars, pro-
fessional courses.
The PRQA Resources Library at http://info.prqa.com/resources-library?filter=white_paper
has some freely available white papers on MISRA C and the use of static analyzers:
• An introduction to MISRA C:2012 at http://info.prqa.com/MISRA C-2012-whitepaper-
evaluation-lp
• The Myth of Perfect MISRA Compliance at http://info.prqa.com/myth-of-perfect-MISRA
Compliance-evaluation-lp, providing background information on the use and limita-
tions of static analyzers for checking MISRA C compliance
In 2013 ISO standardized a set of 45 rules focused on security, available in the C Secure
Coding Rules. A draft is freely available at http://www.open-std.org/jtc1/sc22/wg14/www/
docs/n1624.pdf
In 2018 MISRA published MISRA C:2012 - Addendum 2: Coverage of MISRA C:2012 against
ISO/IEC TS 17961:2013 "C Secure", mapping ISO rules to MISRA C:2012 guidelines. This
document is freely available from https://www.misra.org.uk/.
71
SPARK for the MISRA-C Developer