fold/docs/research/fold-assertion-policy-2026-06-04.md
2026-06-04 20:20:29 -04:00

4 KiB

Fold assertion policy checkpoint

Date: 2026-06-04 PBI: PBI-012 Task: assertion-invariant-hardening Owner: codex

Research question

How should Fold add C++ assertions to catch invariant drift in tests and benchmarks without turning recoverable scan/input failures into process aborts?

Evidence table

Source Confidence Relevance
C++ standard / cppreference assert behavior High Plain assert is disabled when NDEBUG is defined, so hard invariants should not rely on it in release-like builds.
Fold meson.build High Fold defaults to buildtype=debugoptimized and b_ndebug=if-release; assertion behavior must be explicit rather than accidental.
C++ Core Guidelines / GSL Expects and Ensures Medium-high Supports explicit precondition/postcondition vocabulary for programmer contracts.
Herb Sutter GotW contracts series Medium-high Reinforces separating assertions/preconditions/postconditions from recoverable error handling.
Repo assertion audit (rg assert/CHECK/UNREACHABLE) High Fold had no project-local assertion layer; existing assertion text was mostly SMT strings, Catch2 checks, and target-code DEBUGASSERT modeling.
  • FOLD_ASSERT(cond, msg): always-on hard invariant. Use for corruption, accounting underflow, impossible internal states, and UB-risk programmer bugs.
  • FOLD_PRECONDITION(cond, msg): internal API contract. Use when callers inside Fold must pass a valid state/value.
  • FOLD_POSTCONDITION(cond, msg): mutation/result contract. Use after state transitions and path/fact transformations.
  • FOLD_DCHECK(cond, msg): debug/canary-only consistency check. Use for extra diagnostics that should not change release behavior.
  • FOLD_UNREACHABLE(msg): impossible path only after real invariant reasoning.

Do not use these for malformed user input, missing files, toolchain absence, unsupported syntax, solver timeout, or ordinary scan/report failures.

Changes applied

  • Added header-only assertion surface: include/fold/core/assert.hpp.
  • Enabled FOLD_ENABLE_DCHECK=1 whenever Meson is configured with build-tests=true, so Fold's test build path exercises debug checks in core code and test binaries.
  • Added explicit -Denable-dcheck=true Meson option for non-test canary/benchmark builds that should exercise FOLD_DCHECK.
  • Added signal-isolation death tests for FOLD_ASSERT, FOLD_PRECONDITION, and FOLD_DCHECK in tests/unit/test_signal_isolation.cpp.
  • Added boolean-path pre/postcondition checks to src/symbolic/source_path_state.cpp.
  • Added guarded-fact invariant checks to src/symbolic/guarded_facts.cpp.
  • Added P-Code encoder invariants for initialized boolean path state, bit-vector value coercion, boolean predicate outputs, and observer path readiness in src/symbolic/pcode_encoder.cpp.
  • Migrated a first detector/router invariant batch:
    • src/cli/detector_plan.cpp: enabled detector flags must match generated profile lists; security detector count cannot exceed total profiles.
    • src/cli/scan_router.cpp: router result must run at least one backend lane, record a non-empty mode, and keep merged finding counters consistent.

Validation

Passed focused gates:

meson test -C build/release \
  fold:source_path_state fold:guarded_facts fold:pcode_encoder \
  fold:div_by_zero fold:null_deref fold:oob_access fold:scan_e2e \
  --print-errorlogs

meson test -C build/release \
  fold:signal_isolation fold:source_path_state fold:guarded_facts \
  fold:pcode_encoder fold:scan_e2e fold:subprocess_fanout \
  --print-errorlogs

git diff --check

Results: first gate 7/7 OK, second gate 6/6 OK, 0 failures.

Remaining rollout items

  • Consider a small helper wrapper around runWithSignalIsolation for future assertion death tests to reduce boilerplate.
  • Continue migrating older unreachable/default cases after a broader detector/router audit, prioritizing lifecycle/counter/state-machine invariants over recoverable input failures.