Extract Semgrep JavaScript new-function scanner

ober

ff5e66b40110d5887ce96af4f3febf9f70278c60

diff --git a/SEMGREP_JERBOA_IMPLEMENTATION.md b/SEMGREP_JERBOA_IMPLEMENTATION.md
index fe3a337..a32559f 100644
--- a/SEMGREP_JERBOA_IMPLEMENTATION.md
+++ b/SEMGREP_JERBOA_IMPLEMENTATION.md
@@ -485,6 +485,8 @@ Completed in the repo:
     into `src/semgrep/engine/ts-query-scan.ss`
   - extracted JavaScript decorated-class constructor typed-param scanning
     into `src/semgrep/engine/js-constructor-scan.ss`
+  - extracted JavaScript `new Function(...)` sequence/static text scanning
+    into `src/semgrep/engine/js-eval-scan.ss`
 
 Validation at this checkpoint:
 
diff --git a/lib/semgrep/engine/js-eval-scan.sls b/lib/semgrep/engine/js-eval-scan.sls
new file mode 100644
index 0000000..b172d0b
--- /dev/null
+++ b/lib/semgrep/engine/js-eval-scan.sls
@@ -0,0 +1,122 @@
+#!chezscheme
+;;; Generated by jerbuild — DO NOT EDIT
+;;; Source: src/semgrep/engine/js-eval-scan.ss
+
+(library (semgrep engine js-eval-scan)
+  (export scan-javascript-new-function-sequence-pattern)
+  (import
+    (except (chezscheme) make-hash-table hash-table? sort sort!
+     printf fprintf format path-extension path-absolute?
+     with-input-from-string with-output-to-string iota \x31;+
+     \x31;- partition make-date make-time meta atom?)
+    (except (jerboa prelude) meta atom?) (std regex)
+    (semgrep rule) (semgrep result) (semgrep result extras)
+    (semgrep engine regex-support) (semgrep source offsets)
+    (semgrep match structural))
+  (def (string-find-substring s needle)
+       (let ([len (string-length s)]
+             [needle-len (string-length needle)])
+         (let loop ([i 0])
+           (cond
+             [(> i len) #f]
+             [(and (<= (+ i needle-len) len)
+                   (string=? (substring s i (+ i needle-len)) needle))
+              i]
+             [else (loop (+ i 1))]))))
+  (def (code-string-quote? ch)
+       (or (char=? ch #\") (char=? ch #\') (char=? ch #\`)))
+  (def (quoted-code-string? s)
+       (and (>= (string-length s) 2)
+            (let ([open (string-ref s 0)]
+                  [close (string-ref s (- (string-length s) 1))])
+              (and (code-string-quote? open) (char=? open close)))))
+  (def (javascript-new-function-sequence-pattern? pattern)
+       (and (string-find-substring pattern "new Function(")
+            (string-find-substring pattern "$FUNC();")))
+  (def (javascript-new-function-static-pattern? pattern)
+       (or (string-find-substring pattern "new Function('...')")
+           (string-find-substring pattern "new Function(\"...\")")
+           (string-find-substring pattern "new Function(`...`)")))
+  (define javascript-new-function-assignment-regex
+    "(?:(?:let|var|const)[ \\t]+)?([A-Za-z_$][A-Za-z0-9_$]*)[ \\t]*=[ \\t]*new[ \\t]+Function[ \\t]*\\(([^)]*)\\)[ \\t]*;?")
+  (def (new-function-static-argument? arg)
+       (let ([trimmed (string-trim arg)])
+         (quoted-code-string? trimmed)))
+  (def (scan-javascript-new-function-sequence-pattern
+         rule
+         path
+         source
+         pattern)
+       (and (javascript-new-function-sequence-pattern? pattern)
+            (let ([rx (re javascript-new-function-assignment-regex)]
+                  [static-only? (javascript-new-function-static-pattern?
+                                  pattern)]
+                  [len (string-length source)])
+              (let loop ([start 0] [acc '()])
+                (if (> start len)
+                    (reverse acc)
+                    (let ([match (re-search rx source start)])
+                      (if match
+                          (let* ([name (re-match-group match 1)]
+                                 [arg (re-match-group match 2)]
+                                 [assignment-start (re-match-start match)]
+                                 [assignment-end (re-match-end match)]
+                                 [call (string-append name "();")]
+                                 [call-start (string-find-substring-from
+                                               source
+                                               call
+                                               assignment-end)]
+                                 [call-end (and call-start
+                                                (+ call-start
+                                                   (string-length call)))]
+                                 [matches-kind? (or (not static-only?)
+                                                    (new-function-static-argument?
+                                                      arg))]
+                                 [next (max (+ assignment-start 1)
+                                            assignment-end)])
+                            (loop
+                              next
+                              (if (and matches-kind? call-start call-end)
+                                  (let* ([full (re-match-full match)]
+                                         [name-rel (or (string-find-substring-from
+                                                         full
+                                                         name
+                                                         0)
+                                                       0)]
+                                         [name-start (+ assignment-start
+                                                        name-rel)]
+                                         [name-end (+ name-start
+                                                      (string-length
+                                                        name))]
+                                         [binding (make-regex-capture-binding "FUNC" name source
+                                                    name-start name-end)]
+                                         [bindings (list
+                                                     (cons
+                                                       "FUNC"
+                                                       binding))]
+                                         [message (render-fix-template
+                                                    (rule-message rule)
+                                                    bindings)]
+                                         [extra (finding-extra-for-match
+                                                  rule
+                                                  bindings
+                                                  (substring
+                                                    source
+                                                    assignment-start
+                                                    call-end))])
+                                    (let-values ([(start-line start-col)
+                                                  (offset->line-col
+                                                    source
+                                                    assignment-start)]
+                                                 [(end-line end-col)
+                                                  (offset->line-col
+                                                    source
+                                                    call-end)])
+                                      (cons
+                                        (make-finding (rule-id rule) path start-line
+                                          start-col end-line end-col
+                                          assignment-start call-end message
+                                          (rule-severity rule) extra)
+                                        acc)))
+                                  acc)))
+                          (reverse acc)))))))))
diff --git a/lib/semgrep/scan.sls b/lib/semgrep/scan.sls
index 8ff9b1b..17a66ab 100644
--- a/lib/semgrep/scan.sls
+++ b/lib/semgrep/scan.sls
@@ -19,6 +19,7 @@
    (semgrep result extras) (semgrep result findings)
    (semgrep engine generic-scan)
    (semgrep engine js-constructor-scan)
+   (semgrep engine js-eval-scan)
    (semgrep engine js-vardef-scan)
    (semgrep engine js-decorator-scan)
    (semgrep engine markup-scan) (semgrep engine regex-scan)
@@ -510,96 +511,6 @@
                                          object-end match block-start)
                                        acc)))
                                  (reverse acc))))))))))
-  (def (javascript-new-function-sequence-pattern? pattern)
-       (and (string-find-substring pattern "new Function(")
-            (string-find-substring pattern "$FUNC();")))
-  (def (javascript-new-function-static-pattern? pattern)
-       (or (string-find-substring pattern "new Function('...')")
-           (string-find-substring pattern "new Function(\"...\")")
-           (string-find-substring pattern "new Function(`...`)")))
-  (def javascript-new-function-assignment-regex
-       "(?:(?:let|var|const)[ \\t]+)?([A-Za-z_$][A-Za-z0-9_$]*)[ \\t]*=[ \\t]*new[ \\t]+Function[ \\t]*\\(([^)]*)\\)[ \\t]*;?")
-  (def (new-function-static-argument? arg)
-       (let ([trimmed (string-trim arg)])
-         (quoted-code-string? trimmed)))
-  (def (scan-javascript-new-function-sequence-pattern
-         rule
-         path
-         source
-         pattern)
-       (and (javascript-new-function-sequence-pattern? pattern)
-            (let ([rx (re javascript-new-function-assignment-regex)]
-                  [static-only? (javascript-new-function-static-pattern?
-                                  pattern)]
-                  [len (string-length source)])
-              (let loop ([start 0] [acc '()])
-                (if (> start len)
-                    (reverse acc)
-                    (let ([match (re-search rx source start)])
-                      (if match
-                          (let* ([name (re-match-group match 1)]
-                                 [arg (re-match-group match 2)]
-                                 [assignment-start (re-match-start match)]
-                                 [assignment-end (re-match-end match)]
-                                 [call (string-append name "();")]
-                                 [call-start (string-find-substring-from
-                                               source
-                                               call
-                                               assignment-end)]
-                                 [call-end (and call-start
-                                                (+ call-start
-                                                   (string-length call)))]
-                                 [matches-kind? (or (not static-only?)
-                                                    (new-function-static-argument?
-                                                      arg))]
-                                 [next (max (+ assignment-start 1)
-                                            assignment-end)])
-                            (loop
-                              next
-                              (if (and matches-kind? call-start call-end)
-                                  (let* ([full (re-match-full match)]
-                                         [name-rel (or (string-find-substring-from
-                                                         full
-                                                         name
-                                                         0)
-                                                       0)]
-                                         [name-start (+ assignment-start
-                                                        name-rel)]
-                                         [name-end (+ name-start
-                                                      (string-length
-                                                        name))]
-                                         [binding (make-regex-capture-binding "FUNC" name source
-                                                    name-start name-end)]
-                                         [bindings (list
-                                                     (cons
-                                                       "FUNC"
-                                                       binding))]
-                                         [message (render-fix-template
-                                                    (rule-message rule)
-                                                    bindings)]
-                                         [extra (finding-extra-for-match
-                                                  rule
-                                                  bindings
-                                                  (substring
-                                                    source
-                                                    assignment-start
-                                                    call-end))])
-                                    (let-values ([(start-line start-col)
-                                                  (offset->line-col
-                                                    source
-                                                    assignment-start)]
-                                                 [(end-line end-col)
-                                                  (offset->line-col
-                                                    source
-                                                    call-end)])
-                                      (cons
-                                        (make-finding (rule-id rule) path start-line
-                                          start-col end-line end-col
-                                          assignment-start call-end message
-                                          (rule-severity rule) extra)
-                                        acc)))
-                                  acc)))
-                          (reverse acc))))))))
   (def (python-quoted-ellipsis-call-function pattern)
        (let ([match (re-search
                       (re "^([A-Za-z_][A-Za-z0-9_]*)[ \\t\\r\\n]*\\([ \\t\\r\\n]*[\"']\\.\\.\\.[\"'][ \\t\\r\\n]*\\)[ \\t\\r\\n]*;?$")
diff --git a/src/.jerbuild-hashes b/src/.jerbuild-hashes
index 1267407..cb4a385 100644
--- a/src/.jerbuild-hashes
+++ b/src/.jerbuild-hashes
@@ -12,12 +12,13 @@
  ("src/semgrep/match/structural.ss" . "5BA4F1566448AF3A")
  ("src/semgrep/engine/ts-query-scan.ss" . "51AD5339F6DE47B4")
  ("src/semgrep/targeting/path-filter.ss" . "9900721941C6B96")
+ ("src/semgrep/engine/js-eval-scan.ss" . "916D40FD4680B9BD")
  ("src/semgrep/rule/parse-rule.ss" . "EFA6D401699CEDEF")
  ("src/semgrep/output/json.ss" . "293881CFA2ADB7BC")
  ("src/semgrep/lang.ss" . "6982E07679D20836")
  ("src/semgrep/parse/parse-target.ss" . "97AA8FFEB12736DA")
  ("src/semgrep/result/extras.ss" . "DF0B3AAE2BAEB5D")
- ("src/semgrep/scan.ss" . "939EBAEF9257C1B4")
+ ("src/semgrep/scan.ss" . "88CD1FC263753713")
  ("src/semgrep/engine/js-decorator-scan.ss"
    .
    "193758AD2E444FD6")
diff --git a/src/semgrep/engine/js-eval-scan.ss b/src/semgrep/engine/js-eval-scan.ss
new file mode 100644
index 0000000..6662885
--- /dev/null
+++ b/src/semgrep/engine/js-eval-scan.ss
@@ -0,0 +1,132 @@
+(export
+  scan-javascript-new-function-sequence-pattern)
+
+(import (except (jerboa prelude) meta atom?)
+        (std regex)
+        (semgrep rule)
+        (semgrep result)
+        (semgrep result extras)
+        (semgrep engine regex-support)
+        (semgrep source offsets)
+        (semgrep match structural))
+
+(def (string-find-substring s needle)
+  (let ([len (string-length s)]
+        [needle-len (string-length needle)])
+    (let loop ([i 0])
+      (cond
+        [(> i len) #f]
+        [(and (<= (+ i needle-len) len)
+              (string=? (substring s i (+ i needle-len)) needle))
+         i]
+        [else (loop (+ i 1))]))))
+
+(def (code-string-quote? ch)
+  (or (char=? ch #\")
+      (char=? ch #\')
+      (char=? ch #\`)))
+
+(def (quoted-code-string? s)
+  (and (>= (string-length s) 2)
+       (let ([open (string-ref s 0)]
+             [close (string-ref s (- (string-length s) 1))])
+         (and (code-string-quote? open)
+              (char=? open close)))))
+
+(def (javascript-new-function-sequence-pattern? pattern)
+  (and (string-find-substring pattern "new Function(")
+       (string-find-substring pattern "$FUNC();")))
+
+(def (javascript-new-function-static-pattern? pattern)
+  (or (string-find-substring pattern "new Function('...')")
+      (string-find-substring pattern "new Function(\"...\")")
+      (string-find-substring pattern "new Function(`...`)")))
+
+(define javascript-new-function-assignment-regex
+  "(?:(?:let|var|const)[ \\t]+)?([A-Za-z_$][A-Za-z0-9_$]*)[ \\t]*=[ \\t]*new[ \\t]+Function[ \\t]*\\(([^)]*)\\)[ \\t]*;?")
+
+(def (new-function-static-argument? arg)
+  (let ([trimmed (string-trim arg)])
+    (quoted-code-string? trimmed)))
+
+(def (scan-javascript-new-function-sequence-pattern rule path source pattern)
+  (and (javascript-new-function-sequence-pattern? pattern)
+       (let ([rx (re javascript-new-function-assignment-regex)]
+             [static-only? (javascript-new-function-static-pattern? pattern)]
+             [len (string-length source)])
+         (let loop ([start 0] [acc '()])
+           (if (> start len)
+               (reverse acc)
+               (let ([match (re-search rx source start)])
+                 (if match
+                     (let* ([name (re-match-group match 1)]
+                            [arg (re-match-group match 2)]
+                            [assignment-start (re-match-start match)]
+                            [assignment-end (re-match-end match)]
+                            [call (string-append name "();")]
+                            [call-start (string-find-substring-from
+                                          source
+                                          call
+                                          assignment-end)]
+                            [call-end (and call-start
+                                           (+ call-start
+                                              (string-length call)))]
+                            [matches-kind?
+                             (or (not static-only?)
+                                 (new-function-static-argument? arg))]
+                            [next (max (+ assignment-start 1)
+                                       assignment-end)])
+                       (loop
+                         next
+                         (if (and matches-kind? call-start call-end)
+                             (let* ([full (re-match-full match)]
+                                    [name-rel
+                                     (or (string-find-substring-from
+                                           full
+                                           name
+                                           0)
+                                         0)]
+                                    [name-start (+ assignment-start name-rel)]
+                                    [name-end (+ name-start
+                                                 (string-length name))]
+                                    [binding
+                                     (make-regex-capture-binding
+                                       "FUNC"
+                                       name
+                                       source
+                                       name-start
+                                       name-end)]
+                                    [bindings (list (cons "FUNC" binding))]
+                                    [message
+                                     (render-fix-template
+                                       (rule-message rule)
+                                       bindings)]
+                                    [extra
+                                     (finding-extra-for-match
+                                       rule
+                                       bindings
+                                       (substring source
+                                                  assignment-start
+                                                  call-end))])
+                               (let-values
+                                 ([(start-line start-col)
+                                   (offset->line-col source
+                                                     assignment-start)]
+                                  [(end-line end-col)
+                                   (offset->line-col source call-end)])
+                                 (cons
+                                   (make-finding
+                                     (rule-id rule)
+                                     path
+                                     start-line
+                                     start-col
+                                     end-line
+                                     end-col
+                                     assignment-start
+                                     call-end
+                                     message
+                                     (rule-severity rule)
+                                     extra)
+                                   acc)))
+                             acc)))
+                     (reverse acc))))))))
diff --git a/src/semgrep/scan.ss b/src/semgrep/scan.ss
index e17c956..3944341 100644
--- a/src/semgrep/scan.ss
+++ b/src/semgrep/scan.ss
@@ -15,6 +15,7 @@
         (semgrep result findings)
         (semgrep engine generic-scan)
         (semgrep engine js-constructor-scan)
+        (semgrep engine js-eval-scan)
         (semgrep engine js-vardef-scan)
         (semgrep engine js-decorator-scan)
         (semgrep engine markup-scan)
@@ -562,104 +563,6 @@
                                   acc)))
                             (reverse acc))))))))))
 
-(def (javascript-new-function-sequence-pattern? pattern)
-  (and (string-find-substring pattern "new Function(")
-       (string-find-substring pattern "$FUNC();")))
-
-(def (javascript-new-function-static-pattern? pattern)
-  (or (string-find-substring pattern "new Function('...')")
-      (string-find-substring pattern "new Function(\"...\")")
-      (string-find-substring pattern "new Function(`...`)")))
-
-(def javascript-new-function-assignment-regex
-  "(?:(?:let|var|const)[ \\t]+)?([A-Za-z_$][A-Za-z0-9_$]*)[ \\t]*=[ \\t]*new[ \\t]+Function[ \\t]*\\(([^)]*)\\)[ \\t]*;?")
-
-(def (new-function-static-argument? arg)
-  (let ([trimmed (string-trim arg)])
-    (quoted-code-string? trimmed)))
-
-(def (scan-javascript-new-function-sequence-pattern rule path source pattern)
-  (and (javascript-new-function-sequence-pattern? pattern)
-       (let ([rx (re javascript-new-function-assignment-regex)]
-             [static-only? (javascript-new-function-static-pattern? pattern)]
-             [len (string-length source)])
-         (let loop ([start 0] [acc '()])
-           (if (> start len)
-               (reverse acc)
-               (let ([match (re-search rx source start)])
-                 (if match
-                     (let* ([name (re-match-group match 1)]
-                            [arg (re-match-group match 2)]
-                            [assignment-start (re-match-start match)]
-                            [assignment-end (re-match-end match)]
-                            [call (string-append name "();")]
-                            [call-start (string-find-substring-from
-                                          source
-                                          call
-                                          assignment-end)]
-                            [call-end (and call-start
-                                           (+ call-start
-                                              (string-length call)))]
-                            [matches-kind?
-                             (or (not static-only?)
-                                 (new-function-static-argument? arg))]
-                            [next (max (+ assignment-start 1)
-                                       assignment-end)])
-                       (loop
-                         next
-                         (if (and matches-kind? call-start call-end)
-                             (let* ([full (re-match-full match)]
-                                    [name-rel
-                                     (or (string-find-substring-from
-                                           full
-                                           name
-                                           0)
-                                         0)]
-                                    [name-start (+ assignment-start name-rel)]
-                                    [name-end (+ name-start
-                                                 (string-length name))]
-                                    [binding
-                                     (make-regex-capture-binding
-                                       "FUNC"
-                                       name
-                                       source
-                                       name-start
-                                       name-end)]
-                                    [bindings (list (cons "FUNC" binding))]
-                                    [message
-                                     (render-fix-template
-                                       (rule-message rule)
-                                       bindings)]
-                                    [extra
-                                     (finding-extra-for-match
-                                       rule
-                                       bindings
-                                       (substring source
-                                                  assignment-start
-                                                  call-end))])
-                               (let-values
-                                 ([(start-line start-col)
-                                   (offset->line-col source
-                                                     assignment-start)]
-                                  [(end-line end-col)
-                                   (offset->line-col source call-end)])
-                                 (cons
-                                   (make-finding
-                                     (rule-id rule)
-                                     path
-                                     start-line
-                                     start-col
-                                     end-line
-                                     end-col
-                                     assignment-start
-                                     call-end
-                                     message
-                                     (rule-severity rule)
-                                     extra)
-                                   acc)))
-                             acc)))
-                     (reverse acc))))))))
-
 (def (python-quoted-ellipsis-call-function pattern)
   (let ([match (re-search
                  (re "^([A-Za-z_][A-Za-z0-9_]*)[ \\t\\r\\n]*\\([ \\t\\r\\n]*[\"']\\.\\.\\.[\"'][ \\t\\r\\n]*\\)[ \\t\\r\\n]*;?$")