Get AI summaries of any video or article — Sign up free
NASAs Coding Requirements Are Insane thumbnail

NASAs Coding Requirements Are Insane

The PrimeTime·
6 min read

Based on The PrimeTime's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

Safety-critical coding standards become more effective when the rule set is small (around 10), clear, and designed for tool-based mechanical checking rather than manual review.

Briefing

Safety-critical software standards often sprawl into hundreds of rules, but NASA/JPL-style guidance argues that reliability improves when the rule set stays small, strict, and mechanically checkable. The core idea is that long coding guidelines mostly fail to change day-to-day developer behavior—especially when they rely on personal preference, lack tool-based enforcement, or can’t be verified at scale. A tighter set of rules, the argument goes, can make critical code more analyzable for properties like bounded execution, safe memory use, and predictable control flow.

The proposal centers on “no more than 10 rules” for safety-critical coding in C, chosen partly because C has mature tooling: source analyzers, logical model extractors, metric tools, debuggers, test support, and stable compilers. The goal isn’t aesthetic cleanliness; it’s to enable stronger verification than plain compliance. Each rule is designed to be strict enough to matter when failures are unacceptable, yet clear enough that developers can remember and follow it—and static analysis tools can check it without manual review of massive codebases.

The rules themselves target the failure modes that are hardest to prove away. Control-flow restrictions come first: ban goto, setjmp/longjmp, and recursion (including indirect recursion), because simple control flow and bounded call graphs are easier for analyzers to reason about. Loop behavior must also be provably bounded: every loop needs a statically checkable upper bound, with special handling for intentionally non-terminating loops in schedulers. Memory safety is enforced by forbidding dynamic heap allocation after initialization, pushing systems toward fixed pre-allocated memory regions (so allocation/free behavior doesn’t introduce unpredictable performance or memory errors). Functions are capped at roughly what fits on a single printed page (about 60 lines), aiming to keep each unit understandable and verifiable.

Assertions are treated as a verification tool, not a debugging afterthought. Code should average at least two assertions per function; assertions must be side-effect free, and failures must trigger explicit recovery (typically returning an error). “Defensive” checks are also encouraged through scope discipline: data should be declared at the smallest possible scope to reduce the surface area for corruption and make faults easier to diagnose.

Other rules focus on correctness hygiene that tools can enforce. Non-void return values must be checked by callers, and parameter validity must be checked inside functions—so ignored errors don’t silently propagate. Preprocessor use must be limited to safe patterns (header inclusion and simple macros), with conditional compilation heavily constrained because it multiplies code variants and test effort. Pointers are restricted to at most one level of dereferencing, and function pointers are banned because they can cripple static analysis (for example, by making recursion absence impossible to prove). Finally, all code must compile with maximum warnings enabled and be analyzed daily with state-of-the-art static analyzers, requiring zero warnings; if a tool warns incorrectly, the code should be rewritten so the warning disappears.

The practical payoff claimed is reduced burden on developers and testers: fewer unknowns about termination, memory bounds, and safe execution. The guidance is framed as “seat belts” for systems where correctness can determine outcomes ranging from aircraft control to nuclear plant safety and astronaut missions—initially uncomfortable, but ultimately making unsafe shortcuts harder to justify.

Cornell Notes

The guidance argues that safety-critical coding standards work best when they’re small (around 10 rules), strict, and designed for mechanical checking. Long guidelines with hundreds of rules often don’t change behavior because they include personal-preference items and can’t be reliably enforced across large codebases. For C-based mission-critical software, the proposed rules restrict control flow (no goto, no recursion), require loops to have statically provable bounds, and eliminate heap allocation after initialization to avoid unpredictable allocator behavior and memory errors. Functions are kept short, assertions are required and must support explicit recovery, and callers must not ignore error-returning values. The payoff is better analyzability of properties like termination and memory safety, reducing the verification burden when failures are costly.

Why does the guidance push for “no more than 10 rules” instead of hundreds of coding standards?

Large standards tend to accumulate rules with weak justification and personal preference (for example, formatting rules like whitespace in Python). Even when rules exist, they often have little effect because developers can’t feasibly manually review hundreds of thousands of lines, and many guidelines lack comprehensive tool-based compliance checks. A small, clear, mechanically checkable set creates an upper bound on rule complexity and can still deliver measurable gains in software reliability and verifiability—especially when the rules target properties that analyzers can prove (like bounded execution and safe memory use).

What do the control-flow and recursion restrictions aim to make provable?

The rules ban goto, setjmp/longjmp, and both direct and indirect recursion. The rationale is that simpler control flow and the absence of recursion produce a cyclic function-call graph that analyzers can exploit to prove boundedness—i.e., that executions that should be bounded are in fact bounded. The guidance also notes that the rule doesn’t require a single return point, since early returns can sometimes be simpler while still remaining analyzable.

How does the “no dynamic memory allocation after initialization” rule reduce risk?

It targets unpredictability and common memory-management failure modes. Allocators and garbage collectors can behave unpredictably, affecting performance in ways that are hard to reason about. Memory errors also arise from forgetting to free memory, using memory after it’s freed, or writing past allocated boundaries. By forcing applications to live within a fixed pre-allocated memory area, the code’s memory behavior becomes easier to verify. With no heap allocation and no recursion, stack usage can be bounded and derived statically, enabling proofs that the program stays within its memory budget.

What is the role of assertions in these rules, and what makes them different from “debug asserts”?

Assertions must average at least two per function. They’re used to check anomaly conditions that should never occur during real executions. Assertions must be side-effect free, and when an assertion fails, the code must take explicit recovery action—typically returning an error to the caller. The guidance also warns against “fake” assertions that tools can prove never fail (like always-true checks), since those don’t add verification value.

Why are preprocessor and pointer rules treated as verification threats?

Preprocessor complexity multiplies code variants and makes behavior harder to analyze. Conditional compilation can create up to 2^N versions with N directives, inflating testing requirements; therefore conditional compilation is heavily constrained and must be justified and flagged by tools. Pointers are restricted because they can obscure data flow and limit what static analyzers can prove. Function pointers are banned because they can make it impossible to prove properties like the absence of recursion, since indirect calls break analyzers’ ability to determine call hierarchies.

What does “zero warnings” mean in practice for safety-critical development?

All code must compile from day one with all compiler warnings enabled at the most pedantic setting, and it must pass static analysis with zero warnings. If a tool produces an erroneous warning, the code should be rewritten so the warning goes away—rather than treating warnings as ignorable noise. The guidance argues that modern static analyzers are accurate enough that warnings shouldn’t be negotiable on serious projects.

Review Questions

  1. Which specific coding choices most directly enable static proof of bounded execution in this rule set (control flow, recursion, loops, or memory)?
  2. How do the rules distinguish useful assertions from unhelpful ones, and what recovery behavior is required when an assertion fails?
  3. Why does limiting preprocessor use and function pointers improve the effectiveness of static analyzers?

Key Points

  1. 1

    Safety-critical coding standards become more effective when the rule set is small (around 10), clear, and designed for tool-based mechanical checking rather than manual review.

  2. 2

    Long guidelines with hundreds of rules often fail to change developer behavior because many rules reflect personal preference or lack enforceable compliance mechanisms.

  3. 3

    The proposed C-focused rules restrict control flow (no goto, setjmp/longjmp, or recursion) to make boundedness and execution properties easier for analyzers to prove.

  4. 4

    Loops must have statically provable upper bounds, preventing runaway iteration unless non-terminating scheduler cases are handled with an explicit reverse provability requirement.

  5. 5

    Heap allocation is banned after initialization to eliminate unpredictable allocator behavior and common memory-management errors; fixed pre-allocation supports verifiable memory bounds.

  6. 6

    Assertions are mandatory and must be side-effect free; failures require explicit recovery (such as returning an error), and “always-true” assertions don’t count.

  7. 7

    Error handling hygiene is enforced by requiring callers to check non-void return values and by tightly limiting preprocessor directives and pointer usage to preserve analyzability.

Highlights

The guidance claims that hundreds of coding rules often don’t meaningfully improve safety because they’re too numerous, too preference-driven, and too hard to enforce at scale.
Recursion is treated as a major verification obstacle: banning it (including indirect recursion) helps analyzers prove bounded execution.
Eliminating heap allocation after initialization is framed as a practical way to remove whole classes of memory errors and make memory use easier to verify.
Assertions aren’t just for debugging: they must be frequent, side-effect free, and paired with explicit recovery when they fail.
“Zero warnings” is elevated from best practice to a hard requirement: code must compile with maximum warnings and pass daily static analysis without warnings.

Topics