Detecting Unreachable Code and Dead Code
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
potential 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 construction 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.
procedure Much_Ado_About_Little (X, Y, Z : Integer; Success : out Boolean);procedure Much_Ado_About_Little (X, Y, Z : Integer; Success : out Boolean) is procedure Ok is begin Success := True; end Ok; procedure NOk is begin Success := False; end NOk; begin Success := False; for K in Y .. Z loop if K < X and not Success then Ok; end if; end loop; if X > Y then Ok; else NOk; end if; if Z > Y then NOk; return; else Ok; return; end if; if Success then Success := not Success; end if; end Much_Ado_About_Little;
The only code in the body of Much_Ado_About_Little
that affects the result
of the procedure'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.