ARM NEON reductions (vaddvq_*/vminvq_*/vmaxvq_*) expand to GNU statement
expressions `({ ...; __ret = builtin(x); __ret; })`. The uninit-read walker
flat-traversed the statement expression, flagging the result read `__ret`
before noticing the assignment that writes it inside the same expression.
Track variables assigned within a StmtExpr (TraverseStmtExpr) so the result
read is not a false uninit-read.
Verified: simeon neon.cpp range_neon 2->0, statement-expression repro 1->0,
genuine uninit local still fires; full suite 137/137.
`(d & (d-1)) == 0 ? low & (d-1) : low % d` (simeon sketch_bucket) flagged
`low % d` as division-by-zero, but the guard `(0 & -1) == 0` is TRUE when
d==0, so the `%` arm is unreachable for a zero divisor. Teach
conditionProvesNonZero that `x & (x-1)` clears the lowest set bit (zero iff x
is 0 or a power of two):
* `(x & (x-1)) == 0` false arm -> x != 0
* `(x & (x-1)) != 0` true arm -> x != 0
Also route the same check through analyzeDivCallee so the callsite
precondition for the guarded divisor param is suppressed in the same TU.
Verified: simeon.cpp:48 `proved` finding gone; positive repro 1->0, bare
`a % d` still fires; 59 div-by-zero tests + full suite 137/137.
Two return-escape FPs:
1. `if (!p) return nullptr;` encodes the null check imprecisely, so walkIf
took the phi-merge path and replaced `p` with a fresh unknown, losing its
heap-region binding (yams makeTable/dup_cstr/plugin_host_services_shim).
Honor then_exits/else_exits even when the condition is imprecise: if one
arm always exits, the other arm is the only continuation, so skip the merge.
2. `return {v, static_cast<void*>(handle)}` lowers to a CXXConstructExpr (not
an InitListExpr), and IgnoreParenImpCasts stopped at the explicit cast, so
`handle` was never marked retained (yams expose_as_c_abi_with_state). Use
IgnoreCasts and handle the CXXConstructExpr (pair) shape.
Verified: object_storage_adapter.cpp 3->0, plugin_host_services_shim 1->0,
repro 3->0, raw new still fires. Full suite 137/137.
std::unique_ptr<T>(new T(...)) / std::shared_ptr<T>(new T(...)) hands the
allocation to the smart pointer, whose destructor frees it — not a leak. The
detector flagged these (yams peer_registry.cpp:99, embedded_service_host.cpp:384).
The init is wrapped as ExprWithCleanups -> CXXFunctionalCastExpr ->
CXXBindTemporaryExpr -> CXXConstructExpr -> CXXNewExpr; unwrap those wrappers,
detect the unique_ptr/shared_ptr type, and mark the new-expression's site so
checkLeaks skips it. A raw `new T` with no owner still reports.
Verified: repro 1->0, yams peer_registry + embedded_service_host 1->0 each,
raw new still fires. Full suite 137/137.
std::string/std::string_view size()/length() was cached only in the member
cache, which assign() clears. So `char* p = malloc(s.size()+1); ...; p[s.size()]`
encoded the two size() calls as DIFFERENT symbols, letting the solver find a
spurious out-of-bounds (yams object_storage_adapter.cpp dup_cstr, plugin_host_services_shim).
Route zero-arg const size()/length() through the assignment-surviving
string-length cache (string_lengths_), so both calls resolve to one length
symbol: the final write lands exactly at the end (in-bounds), while a genuine
size()+1 write still fires.
Adds positive/negative oob tests; full suite 137/137.
The 4-minute (8x) escalation ceiling let a single TU with ~27 hard queries
pin a worker for 109 minutes (observed: 1 worker stuck at 99% CPU, scan
stalled 2h). Escalate once (2x base, capped 60s) so "just barely over
budget" queries still resolve, but a TU dense with hard queries can't stall
a worker for hours.
- checkAdaptive: cap escalation at 4 min (8x base) instead of 1 hour. The
1-hour ceiling let a genuinely-hard query pin a worker for an hour with
zero file completion, stalling the whole scan (observed: 5 workers at 100%
CPU, log stopped advancing). 4 min still resolves "just barely over budget"
queries while bounding pathological ones.
- score_fp.py: guard the last bare .read_text(), drop an unused `elapsed`,
log the fold --version failure instead of bare pass, and suppress the
runtime sys.path import (reportMissingImports/E402).
Given the engine can afford days of compute (fuzzing-grade), widen the
solver budgets so hard queries resolve instead of timing out:
- checkAdaptive escalation ceiling raised to 1 hour per query (was 8x base)
- budget_seconds 7200 -> 604800 (7 days)
- per_tu_timeout 900 -> 86400 (24h)
A single hard query getting up to an hour of escalating compute is cheap
relative to a lost vulnerability.
Replace bare json.loads/int/float calls with small guarded helpers so a
missing or malformed gold/SARIF/compile-DB file exits with a clear message
instead of a raw traceback, and numeric fields degrade to defaults.
Instead of a fixed per-query timeout that loses findings, the security
detectors now escalate: a query returning UNKNOWN at the base budget is
re-checked at 2x/4x/8x the base timeout (Z3's incremental solver preserves
search state, so escalation continues rather than restarts). The engine
self-tunes its per-query effort to gather findings, with the cap derived
(8x base) rather than exposed as another knob.
Wired into null-deref, int-overflow, oob, and div-by-zero non-portfolio
check paths. Full suite 137/137.
The 1800s solver budget, 6s per-query timeout, and 300s per-TU watchdog were
too tight for large security scans (e.g. 780-file yams), causing Z3 "unknown"
results and preempted TUs. Raise to 7200s budget, 30s per-query, and 900s
per-TU so slow-but-productive TUs finish and complex queries resolve.
UBSan pinned the chromium file_enumerator_posix.cc SIGSEGV: shouldQuery did
`dyn_cast<DeclRefExpr>(value)` on a null `value` (a no-initializer def like
`int *p;`), binding a null reference (Casting.h:110) and dereferencing it in
the optimized build. Use dyn_cast_or_null; the existing `!dre` guard treats a
valueless def as non-aliasing.
The null-deref hardening and container-bounded callee guards pushed these two
helper files past the 1000-line ratchet; register them with the existing
tracked-monolith mechanism (matching int_overflow.cpp / oob_access.cpp) and
record the next extraction target.
std::basic_string / std::basic_string_view methods (substr/append/insert/...)
do size()-pos and pos+count arithmetic that is bounded by the container size
invariant, so an unbounded pos/length at a call site is a logic contract, not
signed-overflow UB. analyzeOverflowCallee now skips these, removing the
chromium file_path.cc substr/append FPs (6 signed findings -> 0) while
preserving the 10 genuine unsigned-wrap findings.
Adds positive tests (substr/append call sites are not flagged) and a clean
negative (free-function arithmetic is still flagged).
stripPointerCasts could call IgnoreParenImpCasts() on a null getSubExpr()
(malformed/dependent cast), and defIsNullable used non-null-checked isa/dyn_cast
on its result — both deref null (getStmtClass(this=0x0)) in the optimized
build. Return nullptr from stripPointerCasts and use isa_and_nonnull /
dyn_cast_or_null at the call sites.
- getName() on an operator/ctor/dtor decl asserts (Decl.h:302) and is
unreliable; use getIdentifier() (null for special names) in curlStrLikeBase
and isStrlenOfArg.
- shouldQuery: return early when the def-use chains are absent (restores the
documented "short-circuits on !chains" intent), and null-check the reference
pointee before chaining ->isPointerType().
- defIsNullable: restrict isNullPointerConstant to literal probes (0/nullptr)
to avoid EvaluateAsRValue-driven record layout on value-dependent defs.
The debug build (assertions on) now completes on chromium file_enumerator_posix.cc.
The chromium 8-TU canary crashed with SIGSEGV on every TU: the security
walkers called clang Expr evaluation (EvaluateAsRValue /
isNullPointerConstant) on arbitrary expressions, forcing ASTRecordLayout,
which recurses/blows the stack or derefs null on complex production types.
1. int-overflow seedMemberConstants: only evaluate plain integer literals
(`this->data = 42`) instead of any `this->member = <expr>` RHS. This was
the dominant crash (7/8 chromium TUs unblocked; 17 findings now emit).
2. null-deref shouldQuery: guard `chains == nullptr` (restores the
documented "short-circuits on !chains" intent) and null-check the
reference pointee before chaining `->isPointerType()`.
3. null-deref defIsNullable: restrict isNullPointerConstant to literal
probes (0/nullptr/(void*)0) — lossless for the predicate, and avoids
EvaluateAsRValue-driven layout on value-dependent def values.
null_deref + int_overflow unit tests pass.
Three changes restore genuine-leak detection while keeping the
process-lifetime / singleton idiom suppressed (SV-COMP memsafety 0->3 TP,
0 FP; yams singletons stay clean):
1. Return escape: only a pointer-typed return value can hand a heap region
to the caller. The old fallback called markAllRetained() for ANY
unresolvable return (including `return 0;`, which ends every main),
suppressing every allocation in the function. Now non-pointer returns
leave regions unfreed so they report.
2. Placement new: `new(buffer) T` constructs in the caller's storage and
allocates no heap memory, but the encoder still creates a region for
OOB-bounds tracking. Mark that region `placement` so the memory-leak
detector skips it (yams `g_global_io_context_ptr = new(storage) T()`).
3. Atexit gate: a file-scope global/static store is process-lifetime
retained only when the TU registers an atexit/__cxa_atexit handler.
Without one, a global store can still leak (SV-COMP lockfree-*).
Validated on SV-COMP memsafety (3 TP / 0 real FP, 100% precision) and yams
(global_io_context placement-new + config_helpers Meyers singletons: 0 FP).
The subprocess fan-out spells a file's path with a non-canonical form (e.g.
tests/integration/../fixtures/...) while the in-process serial path spells it
canonically. stableFunctionId embedded the raw presumed filename, so the same
function produced different clone finding IDs in parallel vs serial runs and
fold:subprocess_fanout flaked/failed. Apply lexically_normal to the path so the
id is identical regardless of how the compile-DB entry spelled it.
Add the structural reachability closure over the project call graph with
explicit production roots, plus the supporting ast_visitor, def-use, and
finding-query/sarif plumbing.
Grow the persistent project index and TU store to support the incremental
project-graph and production-reachability queries used by whole-project scans.
Add discoverGadgetEffects and address-keyed space-id offset mapping to the
binary P-Code scan, and recognize PtrAdd/PtrSub/IntAdd/IntSub for binary
pointer-arithmetic analysis.
Demote parameter-origin detector findings from proved/heuristic to latent
across the source detectors (the callee alone can't prove an extreme value is
reachable), and refine dead-branch prefiltering, uninit-read, sign-conversion,
int-truncation, unused-function, and clone/iterator analysis.
Extend the heap model with array-region tracking (allocate/setSymbolicSize/
createHeapArray + zero-offset init) and thread heap-access info through the
expr-arms encoder so member writes past a buffer reach the bounds check;
P-Code encoder improvements for the binary lane.
Unit + integration coverage for the session's work: incremental project
graph/index, pcode encoder, unreachable/dead-branch, equiv/cross-TU,
div-by-zero/sign-conversion/int-truncation, use-after-free, quality-precision,
scan-router profile, source TU runner, detector plan, and the eval fixtures.
Add negative_cases_from_universe to expand an explicit file-by-rule universe
into labeled negatives (enabling TP/FP/TN/FN, not just TP/FP), plus project-eval
harness updates in run_project_eval and the score/score_fp/quality-gate helpers.
Pin actions/checkout, setup-python, cache, and upload-artifact to full-length
commit SHAs across the bench, lint, precision-gate, and tsan workflows
(supply-chain hardening; mutable tags can be re-pointed).
- skills/fold-sast: rename to skill-fold-sast and expand the scan/precision
guidance for C++/Rust/Swift lanes
- docs/architecture: record call-graph phase decomposition and the
optimization-loop TP/FP/TN/FN evidence policy
- docs/mcp-tools + README: update budget/per-query defaults (1800s/6000ms)
and note the OpenGrep pre-push hook
Use a bash array for MESON_NATIVE_FILES instead of a space-joined string
(safe word-splitting/quoting), and set core.hooksPath to .githooks so the
tracked lint/pre-push hooks are active after setup.
- int-overflow: unsigned wrap is defined, not UB — split to
fold.unsigned-wrap so signed-UB findings aren't drowned; on by
default (--report-unsigned-wrap=false opts out)
- use-after-free: model return/throw as path-terminating so
`delete p; return;` in an error branch can't poison the fall-through
- oob-access: real sizeof for complete structs (was 1); recognize
std::min/clamp/modulo index bounds
- memory-leak: static-storage singletons + return-to-caller are
caller-owned, not leaks
YAMS scan: UAF 4→0, struct-OOB ~10→0, singleton-leak ~15→0,
min/modulo-bound OOB →0. 137/137 tests pass.