10 KiB
YAMS null/div focused findings classification
Date: 2026-06-03 PBI: PBI-012 Task: classify-yams-null-div-findings Owner: codex
Research question
How should Fold classify the 23 source-lane null-deref / div-by-zero findings from the YAMS focused shard so the symbolic-modeling implementation tasks target general model gaps rather than YAMS-specific suppressions?
Inputs and validation
- Primary classification report:
/tmp/fold-yams-reported-v7.json. - Latest rerun report:
/tmp/fold-yams-reported-v8.json. - Compile DB shard:
/tmp/yams-reported-compile_commands.json. - Re-validation command run from Fold:
build/release/src/cli/fold scan \
--mode source \
--compile-db /tmp/yams-reported-compile_commands.json \
--detectors div-by-zero,null-deref \
-f json \
-o /tmp/fold-yams-current-classify.json \
-j 4 \
--budget-seconds 120 \
--per-query-ms 2000
Initial v7 result: scan reproduced all 23 findings (13 fold.null-deref, 10 fold.div-by-zero).
V8 rerun after first-class path-state, AccessPath, guarded-fact substrate, unknown-call havoc, and P-Code pruning work emitted 20 SARIF results (13 fold.null-deref, 7 fold.div-by-zero). The removed findings are:
../src/daemon/components/post_ingest_enrichment.cpp:91../src/daemon/components/post_ingest_enrichment.cpp:92../src/vector/sqlite_vec_backend.cpp:3140
No new findings were added relative to v7. The remaining 20 findings continue to represent the same unresolved modeling classes below.
Category summary (v7 baseline)
| Category | Count | Meaning for Fold |
|---|---|---|
| True positive / likely real bug | 1 | Keep as actionable unless YAMS adds a contract/check. |
| Latent external precondition | 4 | Should not be emitted as Proved unless caller/contract discharges the precondition. |
| CFG/path propagation gap | 11 | Dominating guards, early returns, logical &&/` |
| Access-path / alias / postcondition gap | 6 | Need canonical AccessPath subjects and modeled call/cast/container postconditions. |
| Intentional unsupported / constexpr-template fact | 1 | Track separately from actionable runtime bugs; optional future constexpr/static-assert modeling. |
Finding classification table
| # | Rule | Location | Classification | Evidence | Fold modeling implication |
|---|---|---|---|---|---|
| 1 | div-by-zero | ../src/cli/progress_indicator.cpp:119 |
CFG/path propagation gap | Division is dominated by if (total_ > 0) at line 112. |
assume(total_ > 0) must constrain all statements in the then branch. |
| 2 | div-by-zero | ../src/daemon/components/GradientLimiter.cpp:99 |
CFG/path propagation gap | RHS division is guarded by short-circuit newSmoothed > 0.0 && .... |
Logical && RHS must inherit LHS truth. |
| 3 | div-by-zero | ../src/daemon/components/post_ingest_enrichment.cpp:91 |
Access-path/range gap — resolved in v8 | totalSecs = docSections.sections.size() inside branch requiring !empty() and size() >= 2. |
Stable .size() access-path identity plus range propagation. |
| 4 | div-by-zero | ../src/daemon/components/post_ingest_enrichment.cpp:92 |
Access-path/range gap — resolved in v8 | Same totalSecs >= 2 fact as line 91. |
Same as #3. |
| 5 | div-by-zero | ../src/daemon/ipc/connection_fsm.cpp:247 |
Intentional unsupported / constexpr-template fact | SnapshotRing<N> has static_assert(N > 0) and current instantiation is SnapshotRing<256>. |
Optional template constant/static-assert modeling; not a runtime bug. |
| 6 | div-by-zero | ../src/vector/sqlite_vec_backend.cpp:3019 |
CFG/path + assertion contract gap | YAMS_ASSERT(m > 0 && dim % m == 0) reports the RHS % m; RHS is short-circuit guarded by m > 0. |
Logical && RHS path plus assertion macro treatment. |
| 7 | div-by-zero | ../src/vector/sqlite_vec_backend.cpp:3140 |
CFG/path propagation gap — resolved in v8 | `if (m == 0 | |
| 8 | div-by-zero | ../src/vector/turboquant.cpp:1019 |
Latent external precondition | config_.dimension == 0 causes float division by sqrt(0); constructor has no local guard. |
Treat as precondition dimension > 0, not automatically Proved. |
| 9 | div-by-zero | ../src/vector/turboquant.cpp:1189 |
Latent external precondition / library semantics | sampleBeta divides by x1 + x2; safety depends on valid positive gamma parameters and nonzero samples. |
Need numeric/library contracts or latent classification. |
| 10 | div-by-zero | ../src/vector/turboquant.cpp:501 |
True positive / likely real bug | computeMSE only checks equal sizes; two empty vectors satisfy the precondition and divide by original.size() == 0. |
Keep actionable; regression should ensure empty-container divisor remains reported. |
| 11 | null-deref | ../src/daemon/components/ConfigResolver.cpp:103 |
Access-path/postcondition gap | std::strtod(value.c_str(), &end) with non-null endptr should write end to a non-null position in the input buffer. |
Add libc postcondition model for strtod/endptr or demote to latent when unmodeled. |
| 12 | null-deref | ../src/daemon/components/DatabaseManager.cpp:41 |
CFG/path propagation gap | `if (!raw | |
| 13 | null-deref | ../src/daemon/components/EntityGraphService.cpp:498 |
Latent external precondition / interprocedural guard | Public helper populateKnowledgeGraph checks `if (!result |
|
| 14 | null-deref | ../src/daemon/components/LifecycleComponent.cpp:98 |
CFG/path propagation gap | equalsIgnoreCase returns immediately when `a == nullptr |
|
| 15 | null-deref | ../src/daemon/components/LifecycleComponent.cpp:100 |
CFG/path propagation gap | Same early-return guard; deref of b occurs after both inputs proved non-null. |
Same as #14. |
| 16 | null-deref | ../src/daemon/components/LifecycleComponent.cpp:106 |
CFG/path propagation gap | Return derefs both pointers after early-return guard and loop increments. | Non-null fact should survive loop; pointer increment identity should preserve non-null. |
| 17 | null-deref | ../src/daemon/components/LifecycleComponent.cpp:106 |
CFG/path propagation gap | Same as #16. | Same as #16. |
| 18 | null-deref | ../src/daemon/components/LifecycleComponent.cpp:98 |
CFG/path propagation gap | Duplicate location/variable class from same function. | Dedup/class-level regression can cover all equalsIgnoreCase reports. |
| 19 | null-deref | ../src/daemon/components/RepairService.cpp:456 |
Latent external precondition | Coroutine receives shutdownState.get() from shutdownState_; function-local signature is raw pointer and lacks local guard. |
Raw pointer parameter derefs should be latent unless contract/caller summary proves non-null. |
| 20 | null-deref | ../src/downloader/http_adapter_curl.cpp:241 |
Access-path/alias gap | userdata == nullptr returns before auto* ctx = static_cast<WriteContext*>(userdata); ctx aliases non-null userdata. |
Cast alias should inherit non-null from source pointer. |
| 21 | null-deref | ../src/downloader/http_adapter_curl.cpp:243 |
Access-path/alias gap | Same ctx alias as #20; optional ctx->total guard is separate and valid. |
Same as #20 plus optional/deref access-path modeling. |
| 22 | null-deref | ../src/metadata/connection_pool.cpp:775 |
Access-path / CFG guard gap | `if (!conn | |
| 23 | null-deref | ../src/search/simeon_lexical_backend.cpp:76 |
Access-path / array-base guard gap | zoneAddrs != nullptr dominates loop and zoneAddrs[i]. |
Array base access path should inherit non-null guard. |
Minimal regression proposals by class
-
True positive
double mse(std::vector<float> a, std::vector<float> b) { if (a.size() != b.size()) return 0; return sum / a.size(); }- Empty/equal vectors should remain reported.
-
Latent external precondition
- Raw pointer parameter helper:
void f(State* s) { use(s->x); }called from one guarded caller and one unknown context. - Expected Fold behavior after Phase 4: latent/precondition evidence, not
Proved, unless a caller summary provess != nullptr.
- Raw pointer parameter helper:
-
CFG/path propagation
- Short-circuit OR:
if (m == 0 || n % m != 0) return false; - Short-circuit AND:
if (d > 0 && n / d > 1) return true; - Early return:
if (p == nullptr || q == nullptr) return false; return *p == *q;
- Short-circuit OR:
-
Access-path / alias / postcondition
- Cast alias:
if (!userdata) return; auto* ctx = static_cast<C*>(userdata); return ctx->x; - Size alias:
if (v.size() >= 2) { auto n = v.size(); return x / n; } - Endptr postcondition:
char* end=nullptr; std::strtod(s, &end); if (end && *end == '\0') ...plus modeled non-null when appropriate.
- Cast alias:
-
Intentional unsupported / constexpr-template fact
template <std::size_t N> void push() { static_assert(N > 0); i = (i + 1) % N; }instantiated withN=256.- Keep as non-blocking until Fold models template non-type parameters/static assertions.
Recommended action plan
- Implement
SourcePathState/assumebefore detector-specific fixes; most CFG reports share the same root cause. - Add
AccessPathsubjects for local/member/cast/array-base/zero-arg call identities before adding YAMS-specific contracts. - Add guarded facts and join semantics so early returns and short-circuit facts are retained naturally.
- Add latent/precondition evidence classification for raw pointer params and config-derived divisors.
- Add targeted standard/library contracts only after the generic state model is in place:
strtodendptr, assertions/preconditions, optional-like guards.
Open unknowns
- Whether YAMS intends
TurboQuantMSE::computeMSE({}, {})to be valid input; until YAMS adds a precondition or explicit empty handling, classify as likely true positive. - Whether
sampleBetashould accept non-positive alpha/beta; this needs a YAMS-side API contract to decide TP vs latent. - Whether Fold should model
YAMS_PRECONDITION/YAMS_ASSERTgenerically through macro names or through a user-configurable contract registry.