Skip to content

02 · Safety-Critical Testing Standards

MISRA C/C++, DO-178C, and ISO 26262 are not testing frameworks — they're process and coding standards that dictate what evidence a testing effort must produce before software is allowed into an airplane, a car's braking system, or medical equipment. This module is a working orientation: what each standard actually asks for, how it changes day-to-day testing practice, and where the tools from earlier in this path fit into that evidence.

Environment note

Purely a standards/process module — no code was expected to run, and none did. The one illustrative MISRA-violation snippet was checked by manual reading against the rule text, not by running a MISRA checker (none installed on this host).

1. What these standards actually are

Standard Domain What it governs
MISRA C / MISRA C++ Any safety-relevant embedded C/C++ A restricted subset of the language — which constructs are banned or restricted, because they're common sources of undefined/implementation-defined behavior
DO-178C Airborne software A process standard: for a given criticality level, what verification evidence (requirements traceability, structural coverage, tool qualification) must exist
ISO 26262 Automotive electrical/electronic systems A process and risk-classification standard (ASIL A-D) analogous to DO-178C, for road vehicles

None of them replace GoogleTest, sanitizers, or fuzzing — they specify a minimum bar of evidence and process rigor around testing, at a criticality level proportional to the consequence of failure.

2. MISRA: a coding subset, not a test technique

MISRA rules ban or restrict specific language constructs because they're disproportionately associated with real bugs. A short, concrete example:

/* MISRA C:2012 Rule 14.4 (required): the controlling expression of an
   if/while statement shall have essentially Boolean type. */

int x = get_value();
if (x) {           /* VIOLATION: int used where the rule requires bool */
    do_something();
}

if (x != 0) {       /* compliant: explicit, unambiguous comparison */
    do_something();
}

The rule isn't pedantry: if (x) reads identically whether x is meant as a boolean flag or a count, and a reviewer (or the next maintainer) cannot tell from the code alone which was intended — exactly the kind of ambiguity that produces a real defect when the variable's meaning later changes.

/* Rule 10.1/10.3 family: no implicit conversions between essentially
   different types in an expression. */
uint16_t len = 300;
uint8_t truncated = len;    /* VIOLATION: silent narrowing, MISRA flags it
                                even though C allows it -- this is exactly
                                the class of bug Level 3's fuzz testing
                                (module 04) and UBSan (Level 2, module 05)
                                also independently catch at runtime */

MISRA compliance is checked statically (a MISRA-aware static analyzer — commercial tools like PC-lint, Polyspace, or Coverity; not the open-source cppcheck/clang-tidy from Level 2 module 09, which don't implement full MISRA rule sets), and is a gate, not a suggestion, in a MISRA-mandated project: code that violates a "required" rule does not ship without a documented, reviewed deviation.

3. Structural coverage requirements (DO-178C)

DO-178C assigns a Design Assurance Level (DAL A through E, A being catastrophic-failure-consequence) to software, and each level mandates a specific structural coverage criterion on top of functional testing:

DAL Consequence of failure Required structural coverage
A Catastrophic (loss of aircraft) Modified Condition/Decision Coverage (MC/DC)
B Hazardous Decision coverage
C Major Statement coverage
D Minor None mandated beyond functional testing
E No safety effect None

MC/DC is the standard's signature requirement and worth understanding concretely: for a decision with multiple conditions, every condition must be shown to independently affect the decision's outcome — not merely that every condition has been both true and false somewhere in the suite (that's condition coverage, a weaker bar).

/* if (a && b) -- MC/DC requires test cases proving EACH of a, b
   independently flips the outcome, holding the other fixed: */
/* a=T, b=T -> true   (baseline)                                   */
/* a=F, b=T -> false  (shows 'a' independently controls the result) */
/* a=T, b=F -> false  (shows 'b' independently controls the result) */
/* Three cases, not four (a=F,b=F is redundant for MC/DC purposes,
   though a decent test suite would likely include it anyway). */

This is gcov/lcov line-and-branch coverage (Level 2, module 06) taken one step further — decision coverage tells you every branch was taken both ways, MC/DC tells you every individual condition was shown to matter, and matters because a && b being reached both true and false says nothing about whether b's value alone was ever actually consequential.

# Ordinary lcov gets you line/branch coverage, not MC/DC directly.
# Specialized DO-178C coverage tools (e.g. LDRA, VectorCAST) compute and
# report MC/DC against a qualified toolchain -- part of what "tool
# qualification" (section 5) exists to certify.
lcov --capture --directory build --output-file coverage.info --rc branch_coverage=1

4. Requirements traceability, briefly (full depth in module 03)

Every requirement must trace to at least one test, and every test must trace back to the requirement(s) it verifies — a bidirectional map that DO-178C and ISO 26262 both require as certification evidence, and which module 03 covers in depth, including how to build and maintain the matrix.

REQ-BRAKE-014: "The system shall disengage cruise control within 200ms
                of brake pedal input exceeding 5% travel."
    -> TEST-BRAKE-014-01: unit test, mocked pedal sensor, asserts
       disengage() called within one control-loop tick after a 6%
       input value.
    -> TEST-BRAKE-014-02: HIL test (Level 3, module 03), real pedal
       hardware, measures actual disengagement latency.

An orphan requirement (no test) or an orphan test (traces to nothing) is itself a finding in a certification audit — the traceability matrix's completeness is evidence, independent of whether the underlying tests pass.

5. Tool qualification

If a tool's output is trusted as certification evidence (a compiler, a coverage tool, a static analyzer) without a human independently verifying every result, the tool itself typically needs qualification — documented evidence that the tool reliably does what it claims, for the specific way it's being used. This is why safety-critical projects often use qualified commercial toolchains rather than plain gcc/lcov: not because the open tools are worse, but because qualification evidence for them doesn't already exist in a form auditors accept.

6. ISO 26262's ASIL and the same shape of argument

ISO 26262 mirrors DO-178C's structure for automotive: ASIL A (lowest) to ASIL D (highest), each specifying a growing rigor bar across the full lifecycle — hazard analysis, requirements traceability, coverage targets, and process documentation — not just "write more tests." The core lesson transfers directly: higher assurance level does not mean "test harder" in some vague sense, it means a specific, enumerable, auditable set of additional evidence.

Cheat sheet

Question Where the answer lives
Is this C construct even allowed? MISRA C/C++ rule set, checked by a MISRA-aware static analyzer
How much structural coverage is required? DO-178C DAL / ISO 26262 ASIL table (section 3)
Does every requirement have a test? The requirements traceability matrix (module 03)
Can I trust this analysis tool's output as evidence? Only if it (or its use) is qualified (section 5)
What's actually different at a higher assurance level? A specific, enumerated evidence bar — not "more effort" in the abstract

How It Actually Works: how a MISRA checker proves a rule, mechanically

  • A MISRA rule is checked the same way clang-tidy checks any rule — by pattern-matching over the AST/CFG (Level 2 Module 9) — but MISRA rules are chosen specifically because they eliminate constructs whose meaning static analysis (or a human) cannot fully pin down. Rule 10.1 (no implicit conversions between "essentially different" types) exists because C's implicit conversion rules are numerous and easy to misjudge by eye; a checker can enumerate every implicit conversion in the AST and flag ones outside the permitted matrix, which is a purely syntactic check requiring no execution.
  • "Coverage" under DO-178C/ISO 26262 is measured with the same .gcno/ .gcda control-flow-edge instrumentation from Level 2 Module 6, extended to distinguish individual boolean sub-conditions rather than just branches. An MC/DC-capable coverage tool instruments each atomic condition inside a compound decision separately (not just the overall true/false outcome of the if), which is why ordinary lcov — built for line/branch coverage — cannot report MC/DC directly: its instrumentation granularity stops at the branch, one level short of what MC/DC needs.
  • Tool qualification is really a claim about the tool's own test suite, not about your code. A "qualified" static analyzer or compiler has itself been through documented verification proving it doesn't silently mis-report a violation as clean, or (for a compiler) doesn't generate incorrect object code from correct source — because if the tool used to produce your evidence is unreliable, every artifact it generated is suspect regardless of how rigorous your own process was. This is why standards distinguish a "verification tool" (whose output is cross-checked by another means, so it needs less qualification) from a "development tool" whose output ships directly (needing full qualification) — the qualification burden tracks how directly the tool's own correctness becomes your code's correctness.

Exercise

  1. Take one function from an earlier module in this path (e.g. frame_parse from Level 3 module 02) and identify one place its code, as written, would likely draw a MISRA finding (implicit conversion, non-boolean condition, or similar) — rewrite that line to be MISRA-compliant.
  2. For a decision with three conditions (if (a && (b || c))), work out the minimal set of test cases that would satisfy MC/DC, and compare the count against what would satisfy only condition coverage.
  3. Write one requirement (in the REQ-... style of section 4) for a feature in your own project, and list every test that currently traces to it — if none do, that's the finding a real audit would raise.
  4. Explain in three sentences why a project targeting ASIL D would need qualification evidence for its coverage tool even if the tool is technically correct — what's the actual argument for qualification beyond correctness?
  5. Write two sentences comparing DO-178C's DAL table to ISO 26262's ASIL scale: what's structurally the same about how they scale rigor with consequence, and what's different about the domains they apply to?