Merge: structural taint engine foundation (java bare-expr source scaffolding)

ober

5e82247df7c54c5d2b8fc3a7a401ba2a985f82bf

diff --git a/lib/semgrep/match/structural.sls b/lib/semgrep/match/structural.sls
index a56bd72..6157864 100644
--- 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
diff --git a/lib/semgrep/scan.sls b/lib/semgrep/scan.sls
index a814ece..49e259f 100644
--- 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)
diff --git a/src/semgrep/match/structural.ss b/src/semgrep/match/structural.ss
index f400df5..9ff0c21 100644
--- 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
diff --git a/src/semgrep/scan.ss b/src/semgrep/scan.ss
index f390b10..9edb595 100644
--- 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")]))