Merge: per-method scope-aware taint reachability
ober
62e1509c27cd5d2f5a804c38b088577e5c254454
--- a/HANDOFF_OPUS_4_8.md +++ b/HANDOFF_OPUS_4_8.md @@ -193,20 +193,21 @@ languages, so closing them is the tip of the larger work: `java-iterator-missed-propagation`, and `documentbuilderfactory` overfit clauses (java 42/42, smoke 321/321 each step). - **The next fundamental blocker is SCOPE-AWARE REACHABILITY (verified).** With - param-sources matching, `taint_assume_safe_numbers1` still fails because the - general engine's source→sink reachability is *positional/global*, not - per-method: every method's `$X` param reaches every `sink(...)`, so test1's - sink is (wrongly) flagged by test3-10's params. Confirmed empirically — a - numeric-typed-source filter (`source-state-numeric-typed-param?`, classifies - `int`/`long`/`Integer`… correctly) drops the right sources but findings persist - because cross-method non-numeric sources still reach. So the order is: (1) - per-method (CFG/scope) reachability, THEN (2) numeric/boolean source+value - type-classification, THEN (3) numeric-result value-analysis (`Integer.valueOf`, - `+`, `!=`). Each is real dataflow/type-inference work; together they're a - multi-day engine, and the `test` rule-id group needs ALL of them + best-fit-sink, - getter/setter, lambda, jpa, FQN before its overfit clause retires. The same - engine retires the other languages' taint handlers + unblocks dart. + **(1) Per-method scope-aware reachability — BUILT.** The general engine's + reachability was positional/global (every method's `$X` param reached every + `sink(...)`). Root cause: `finding-simple-function-scope` scanned upward for a + lower-indent function line, so a finding ON a signature line (a parameter) + returned no scope and `same-simple-function-scope?` bypassed the constraint. + Fix: a finding whose own line is a function-scope line takes that line as its + scope, so a parameter is scoped to its own method. Non-regressing (all + structural langs unchanged, smoke 321/321). This unblocks the param-source chain. + STILL TODO for assume-safe: (2) numeric/boolean source+value type-classification + (helpers were prototyped: `source-state-numeric-typed-param?` classifies + `int`/`long`/`Integer`… correctly via `param-type-before-name`), THEN (3) + numeric-result value-analysis (`Integer.valueOf`, `+`, `!=`). The `test` rule-id + group needs (2)+(3) + best-fit-sink, getter/setter, lambda, jpa, FQN before its + overfit clause retires. The same engine retires the other languages' taint + handlers + unblocks dart. - a few per-language pattern hard-cases (go constant-folding/struct_tags, php metavar-call/non-prim-type, rust macro-call, kotlin named-ellipsis). --- a/lib/semgrep/scan.sls +++ b/lib/semgrep/scan.sls @@ -31850,18 +31850,20 @@ (let* ([line (finding-start-line finding)] [target-line (source-line source line)] [target-indent (line-indent target-line)]) - (if (= target-indent 0) - (and (simple-function-scope-line? target-line) line) - (let loop ([current-line (- line 1)]) - (cond - [(< current-line 1) #f] - [else - (let* ([text (source-line source current-line)] - [indent (line-indent text)]) - (if (and (< indent target-indent) - (simple-function-scope-line? text)) - current-line - (loop (- current-line 1))))]))))) + (cond + [(simple-function-scope-line? target-line) line] + [(= target-indent 0) #f] + [else + (let loop ([current-line (- line 1)]) + (cond + [(< current-line 1) #f] + [else + (let* ([text (source-line source current-line)] + [indent (line-indent text)]) + (if (and (< indent target-indent) + (simple-function-scope-line? text)) + current-line + (loop (- current-line 1))))]))]))) (def (same-simple-function-scope? source a b) (let ([a-scope (finding-simple-function-scope source a)] [b-scope (finding-simple-function-scope source b)]) --- a/src/semgrep/scan.ss +++ b/src/semgrep/scan.ss @@ -31800,9 +31800,14 @@ (let* ([line (finding-start-line finding)] [target-line (source-line source line)] [target-indent (line-indent target-line)]) - (if (= target-indent 0) - (and (simple-function-scope-line? target-line) line) - (let loop ([current-line (- line 1)]) + (cond + ;; The finding is ON a function signature line (e.g. a method parameter): + ;; that line is its scope. Without this a parameter source has no scope + ;; and reaches sinks in every method (cross-method false positives). + [(simple-function-scope-line? target-line) line] + [(= target-indent 0) #f] + [else + (let loop ([current-line (- line 1)]) (cond [(< current-line 1) #f] [else @@ -31811,7 +31816,7 @@ (if (and (< indent target-indent) (simple-function-scope-line? text)) current-line - (loop (- current-line 1))))]))))) + (loop (- current-line 1))))]))]))) (def (same-simple-function-scope? source a b) (let ([a-scope (finding-simple-function-scope source a)]