Merge: structural taint engine foundation (java bare-expr source scaffolding)
ober
5e82247df7c54c5d2b8fc3a7a401ba2a985f82bf
--- a/lib/semgrep/match/structural.sls +++ b/lib/semgrep/match/structural.sls @@ -1680,6 +1680,40 @@ (if chain-ellipsis? (drop-contained-structural-matches raw-matches) raw-matches))) + (define java-pattern-scaffolds + (list + (cons "class _C{Object _f=" ";}") + (cons "class _C{void _m(){" ";}}") + (cons "class _C{void _m(){" "}}"))) + (def (java-parse-pattern rewritten-pattern) + (let ([trimmed (string-trim rewritten-pattern)]) + (let loop ([scaffolds java-pattern-scaffolds]) + (if (null? scaffolds) + (values #f #f) + (let* ([prefix (caar scaffolds)] + [suffix (cdar scaffolds)] + [wrapped (string-append prefix trimmed suffix)] + [result (parse-target-string "java" wrapped)] + [root (parse-result-root result)]) + (if (or (not root) (parse-result-has-errors? result)) + (begin + (when root (node-close! root)) + (tree-close! (parse-result-tree result)) + (loop (cdr scaffolds))) + (let* ([hstart (utf8-length prefix)] + [hend (+ hstart (utf8-length trimmed))] + [hole (and (<= (node-start-byte root) hstart) + (>= (node-end-byte root) hend) + (go-deepest-node-containing + root + hstart + hend))]) + (node-close! root) + (if hole + (values hole result) + (begin + (tree-close! (parse-result-tree result)) + (loop (cdr scaffolds))))))))))) (def (structural-pattern-matches-with-bindings language pattern-source @@ -1708,7 +1742,8 @@ (let* ([pattern-result (parse-target-string language rewritten-pattern)] - [pattern-root (parse-result-root pattern-result)]) + [pattern-root (parse-result-root pattern-result)] + [errored? (parse-result-has-errors? pattern-result)]) (let-values ([(normalized owned-nodes) (normalized-pattern-root language @@ -1721,7 +1756,23 @@ (for-each node-close! owned-nodes) (when pattern-root (node-close! pattern-root)) (tree-close! (parse-result-tree pattern-result)) - matches)))]))) + (if (and (null? matches) + errored? + (string=? language "java")) + (let-values ([(normalized2 result2) + (java-parse-pattern + rewritten-pattern)]) + (if (not normalized2) + '() + (let ([m2 (run-structural-matches + language + normalized2 + target-root + initial-bindings)]) + (node-close! normalized2) + (tree-close! (parse-result-tree result2)) + m2))) + matches))))]))) (def (structural-pattern-matches language pattern-source --- a/lib/semgrep/scan.sls +++ b/lib/semgrep/scan.sls @@ -31267,6 +31267,8 @@ '("$L = $R" "$L[$I] = $R" "let $L = $R" "let mut $L = $R")] [(string=? language "kotlin") '("$L = $R" "$L[$I] = $R" "val $L = $R" "var $L = $R")] + [(string=? language "java") + '("$L = $R" "$L[$I] = $R" "$T $L = $R" "this.$L = $R")] [else '("$L = $R" "$L[$I] = $R")])) (def (scan-implicit-assignment-propagators rule language path source target-root) --- a/src/semgrep/match/structural.ss +++ b/src/semgrep/match/structural.ss @@ -1674,6 +1674,42 @@ (drop-contained-structural-matches raw-matches) raw-matches))) +;; Java bare expressions/identifiers (e.g. a `tainted` source) error at the +;; top level, so wrap them in a class context and extract the spanned node, +;; like the Go/PHP scaffolds. Used only as a fallback when the bare parse errors +;; (bare calls/statements parse fine and keep the normalized-root path). +(define java-pattern-scaffolds + (list (cons "class _C{Object _f=" ";}") ; bare expressions / identifiers + (cons "class _C{void _m(){" ";}}") ; bare statements needing a `;` + (cons "class _C{void _m(){" "}}"))) ; statements already terminated + +(def (java-parse-pattern rewritten-pattern) + (let ([trimmed (string-trim rewritten-pattern)]) + (let loop ([scaffolds java-pattern-scaffolds]) + (if (null? scaffolds) + (values #f #f) + (let* ([prefix (caar scaffolds)] + [suffix (cdar scaffolds)] + [wrapped (string-append prefix trimmed suffix)] + [result (parse-target-string "java" wrapped)] + [root (parse-result-root result)]) + (if (or (not root) (parse-result-has-errors? result)) + (begin + (when root (node-close! root)) + (tree-close! (parse-result-tree result)) + (loop (cdr scaffolds))) + (let* ([hstart (utf8-length prefix)] + [hend (+ hstart (utf8-length trimmed))] + [hole (and (<= (node-start-byte root) hstart) + (>= (node-end-byte root) hend) + (go-deepest-node-containing root hstart hend))]) + (node-close! root) + (if hole + (values hole result) + (begin + (tree-close! (parse-result-tree result)) + (loop (cdr scaffolds))))))))))) + (def (structural-pattern-matches-with-bindings language pattern-source @@ -1695,7 +1731,8 @@ matches)))] [else (let* ([pattern-result (parse-target-string language rewritten-pattern)] - [pattern-root (parse-result-root pattern-result)]) + [pattern-root (parse-result-root pattern-result)] + [errored? (parse-result-has-errors? pattern-result)]) (let-values ([(normalized owned-nodes) (normalized-pattern-root language pattern-root)]) (let ([matches (run-structural-matches @@ -1703,7 +1740,22 @@ (for-each node-close! owned-nodes) (when pattern-root (node-close! pattern-root)) (tree-close! (parse-result-tree pattern-result)) - matches)))]))) + ;; Fallback: a java bare expression/identifier (e.g. a `tainted` + ;; source) errors at top level and the error-tree path finds + ;; nothing — re-try via a class-context scaffold. Only when the + ;; normal path matched nothing, so it never overrides a good match. + (if (and (null? matches) errored? (string=? language "java")) + (let-values ([(normalized2 result2) + (java-parse-pattern rewritten-pattern)]) + (if (not normalized2) + '() + (let ([m2 (run-structural-matches + language normalized2 target-root + initial-bindings)]) + (node-close! normalized2) + (tree-close! (parse-result-tree result2)) + m2))) + matches))))]))) (def (structural-pattern-matches language pattern-source target-root) (structural-pattern-matches-with-bindings --- a/src/semgrep/scan.ss +++ b/src/semgrep/scan.ss @@ -31265,6 +31265,13 @@ "$L[$I] = $R" "val $L = $R" "var $L = $R")] + [(string=? language "java") + ;; Java: plain assignment, typed local/field declaration, field via this, + ;; array element. + '("$L = $R" + "$L[$I] = $R" + "$T $L = $R" + "this.$L = $R")] [else '("$L = $R" "$L[$I] = $R")]))