Undefined behavior (and critical unspecified behavior, which we'll treat as
undefined behavior) 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 document.

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 unspecified 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 semantics, but the consequences are bounded in various
ways. For example, reading uninitialized 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 language semantics, but the consequences are not bounded
by the Ada Reference Manual. 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 execution.

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 reaching 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 verification 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. The following discussion focuses
on how SPARK can verify that no exceptions can be raised.

The most common run-time errors are related to misuse of arithmetic (division by
zero, overflows, 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.

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 guarantee 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 program, 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.