Limit multiline ellipsis to call parentheses
ober
880a691bb7567f5c0599a04cca3510a50c7e337d
--- a/lib/semgrep/scan.sls +++ b/lib/semgrep/scan.sls @@ -900,15 +900,20 @@ (generic-plain-ellipsis-at? source (generic-skip-whitespace source (+ i 1))))) - (def (generic-ellipsis-inside-parens? source i) - (let loop ([j 0] [depth 0]) + (def (generic-ellipsis-inside-call-parens? source i) + (let loop ([j 0] [stack '()]) (cond - [(>= j i) (> depth 0)] + [(>= j i) + (and (pair? stack) + (let ([open (car stack)]) + (and (> open 0) + (identifier-token-char? + (string-ref source (- open 1))))))] [(char=? (string-ref source j) #\() - (loop (+ j 1) (+ depth 1))] + (loop (+ j 1) (cons j stack))] [(char=? (string-ref source j) #\)) - (loop (+ j 1) (max 0 (- depth 1)))] - [else (loop (+ j 1) depth)]))) + (loop (+ j 1) (if (pair? stack) (cdr stack) stack))] + [else (loop (+ j 1) stack)]))) (def (generic-word-char? ch) (or (char-alphabetic? ch) (char-numeric? ch) @@ -1026,7 +1031,7 @@ (loop (generic-skip-whitespace source (+ i 3)) (cons - (if (generic-ellipsis-inside-parens? source i) + (if (generic-ellipsis-inside-call-parens? source i) "(?:.|\\n)*?" plain-ellipsis) parts) --- a/src/.jerbuild-hashes +++ b/src/.jerbuild-hashes @@ -3,11 +3,11 @@ ("src/semgrep/output/json.ss" . "293881CFA2ADB7BC") ("src/semgrep/lang.ss" . "6982E07679D20836") ("src/semgrep/parse/parse-target.ss" . "E74854DDDACF6BA") - ("src/semgrep/scan.ss" . "71FE8A8073E6A24A") - ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0") + ("src/semgrep/scan.ss" . "E4428A1FD577875E") ("src/semgrep/rule.ss" . "E12C108153C181FA") - ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1") + ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0") ("src/semgrep/output/text.ss" . "BE476CB84B807FBA") + ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1") ("src/semgrep/match/structural.ss" . "6FE77014EE9FDCE4") ("src/semgrep/main.ss" . "A4EC9E7F2A09D25E") ("src/semgrep/cli.ss" . "EBDC4B1DAD3F13CC")) --- a/src/semgrep/scan.ss +++ b/src/semgrep/scan.ss @@ -999,16 +999,22 @@ source (generic-skip-whitespace source (+ i 1))))) -;; An ellipsis inside an open call/parenthesis should match across newlines, so -;; `sink(...)` matches a call whose arguments span multiple lines. Outside -;; parens the line-bounded form is kept to avoid over-matching across statements. -(def (generic-ellipsis-inside-parens? source i) - (let loop ([j 0] [depth 0]) +;; An ellipsis inside an open CALL parenthesis should match across newlines, so +;; `sink(...)` matches a call whose arguments span multiple lines. The enclosing +;; `(` must be a call (preceded by an identifier): a bare grouping `(...)` keeps +;; the line-bounded form, so it does not span across whole files/statements. +(def (generic-ellipsis-inside-call-parens? source i) + (let loop ([j 0] [stack '()]) (cond - [(>= j i) (> depth 0)] - [(char=? (string-ref source j) #\() (loop (+ j 1) (+ depth 1))] - [(char=? (string-ref source j) #\)) (loop (+ j 1) (max 0 (- depth 1)))] - [else (loop (+ j 1) depth)]))) + [(>= j i) + (and (pair? stack) + (let ([open (car stack)]) + (and (> open 0) + (identifier-token-char? (string-ref source (- open 1))))))] + [(char=? (string-ref source j) #\() (loop (+ j 1) (cons j stack))] + [(char=? (string-ref source j) #\)) + (loop (+ j 1) (if (pair? stack) (cdr stack) stack))] + [else (loop (+ j 1) stack)]))) (def (generic-word-char? ch) (or (char-alphabetic? ch) @@ -1114,7 +1120,7 @@ (char=? (string-ref source (+ i 1)) #\.) (char=? (string-ref source (+ i 2)) #\.)) (loop (generic-skip-whitespace source (+ i 3)) - (cons (if (generic-ellipsis-inside-parens? source i) + (cons (if (generic-ellipsis-inside-call-parens? source i) "(?:.|\\n)*?" plain-ellipsis) parts)