PHP: let `$FUNC(...)` match include/require language constructs
ober
bcbae7dc65a8ccfaad966d4a37dc1192e39d0251
--- a/lib/semgrep/match/structural.sls +++ b/lib/semgrep/match/structural.sls @@ -1153,6 +1153,38 @@ target bindings)]) (and (not (null? matches)) (car matches)))) + (def (php-include-like-target-type? node-type) + (or (string=? node-type "include_expression") + (string=? node-type "include_once_expression") + (string=? node-type "require_expression") + (string=? node-type "require_once_expression"))) + (def (node-contains-ellipsis-arg? n) + (let loop ([i 0]) + (and (< i (node-child-count n)) + (let ([c (node-child n i)]) + (or (string=? (node-type c) "variadic_placeholder") + (ellipsis-node? c) + (node-contains-ellipsis-arg? c) + (loop (+ i 1))))))) + (def (php-include-call-matches + language + pattern + target + bindings) + (and (php-language? language) + (string=? (node-type pattern) "function_call_expression") + (php-include-like-target-type? (node-type target)) + (let ([fn (node-named-child pattern 0)] + [args (node-named-child pattern 1)]) + (and fn + args + (php-metavariable-name-from-node fn) + (node-contains-ellipsis-arg? args) + (let ([next (bind-metavariable + (php-metavariable-name-from-node fn) + (node-child target 0) + bindings)]) + (and next (list next))))))) (def (structural-node-matches language pattern @@ -1161,6 +1193,8 @@ (let ([text (node-text pattern)]) (cond [(ellipsis-node? pattern) (list bindings)] + [(php-include-call-matches language pattern target bindings) => + (lambda (matches) matches)] [(and (php-language? language) (php-metavariable-name-from-node pattern)) => (lambda (name) --- a/src/.jerbuild-hashes +++ b/src/.jerbuild-hashes @@ -8,6 +8,6 @@ ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1") ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0") ("src/semgrep/rule.ss" . "E12C108153C181FA") - ("src/semgrep/match/structural.ss" . "8144F1297C91CF9F") + ("src/semgrep/match/structural.ss" . "DF9CD3E0C00E5D40") ("src/semgrep/main.ss" . "A4EC9E7F2A09D25E") ("src/semgrep/cli.ss" . "EBDC4B1DAD3F13CC")) --- a/src/semgrep/match/structural.ss +++ b/src/semgrep/match/structural.ss @@ -1172,10 +1172,47 @@ (and (not (null? matches)) (car matches)))) +;; PHP include/require/include_once/require_once are language constructs, not +;; function calls, so they parse as *_expression nodes. Semgrep still lets a +;; call pattern `$FUNC(...)` match them, binding $FUNC to the keyword token. +(def (php-include-like-target-type? node-type) + (or (string=? node-type "include_expression") + (string=? node-type "include_once_expression") + (string=? node-type "require_expression") + (string=? node-type "require_once_expression"))) + +;; The pattern's argument list `(...)` is variadic when it contains an ellipsis +;; (a variadic_placeholder, or the rewritten __sg_ellipsis__ sentinel node). +(def (node-contains-ellipsis-arg? n) + (let loop ([i 0]) + (and (< i (node-child-count n)) + (let ([c (node-child n i)]) + (or (string=? (node-type c) "variadic_placeholder") + (ellipsis-node? c) + (node-contains-ellipsis-arg? c) + (loop (+ i 1))))))) + +(def (php-include-call-matches language pattern target bindings) + (and (php-language? language) + (string=? (node-type pattern) "function_call_expression") + (php-include-like-target-type? (node-type target)) + (let ([fn (node-named-child pattern 0)] + [args (node-named-child pattern 1)]) + (and fn args + (php-metavariable-name-from-node fn) + (node-contains-ellipsis-arg? args) + (let ([next (bind-metavariable + (php-metavariable-name-from-node fn) + (node-child target 0) + bindings)]) + (and next (list next))))))) + (def (structural-node-matches language pattern target bindings) (let ([text (node-text pattern)]) (cond [(ellipsis-node? pattern) (list bindings)] + [(php-include-call-matches language pattern target bindings) + => (lambda (matches) matches)] ;; PHP metavariable `$NAME` (uppercase) -> bind to the target node. [(and (php-language? language) (php-metavariable-name-from-node pattern))