4 KiB
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. |
Recommended assertion tiers
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=1whenever Meson is configured withbuild-tests=true, so Fold's test build path exercises debug checks in core code and test binaries. - Added explicit
-Denable-dcheck=trueMeson option for non-test canary/benchmark builds that should exerciseFOLD_DCHECK. - Added signal-isolation death tests for
FOLD_ASSERT,FOLD_PRECONDITION, andFOLD_DCHECKintests/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
runWithSignalIsolationfor 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.