@@ -26,6 +26,49 @@ class FunctionLikeMacro extends Macro {
2626 exists ( this .getBody ( ) .regexpFind ( "\\#?\\b" + parameter + "\\b" , _, result ) )
2727 )
2828 }
29+
30+ /**
31+ * Holds if the parameter is used in a way that may make it vulnerable to precedence issues.
32+ *
33+ * Typically, parameters are wrapped in parentheses to protect them from precedence issues, but
34+ * that is not always possible.
35+ */
36+ predicate parameterPrecedenceUnprotected ( int index ) {
37+ // Check if the parameter is used in a way that requires parentheses
38+ exists ( string parameter | parameter = getParameter ( index ) |
39+ // Finds any occurence of the parameter that is not preceded by, or followed by, either a
40+ // parenthesis or the '#' token operator.
41+ //
42+ // Note the following cases:
43+ // - "(x + 1)" is preceded by a parenthesis, but not followed by one, so SHOULD be matched.
44+ // - "x # 1" is followed by "#" (though not preceded by #) and SHOULD be matched.
45+ // - "(1 + x)" is followed by a parenthesis, but not preceded by one, so SHOULD be matched.
46+ // - "1 # x" is preceded by "#" (though not followed by #) and SHOULD NOT be matched.
47+ //
48+ // So the regex is structured as follows:
49+ // - paramMatch: Matches the parameter at a word boundary, with optional whitespace
50+ // - notHashed: Finds parameters not used with a leading # operator.
51+ // - The final regex finds cases of `notHashed` that are not preceded by a parenthesis,
52+ // and cases of `notHashed` that are not followed by a parenthesis.
53+ //
54+ // Therefore, a parameter with parenthesis on both sides is not matched, a parameter with
55+ // parenthesis missing on one or both sides is only matched if there is no leading or trailing
56+ // ## operator.
57+ exists ( string noBeforeParen , string noAfterParen , string paramMatch , string notHashed |
58+ // Not preceded by a parenthesis
59+ noBeforeParen = "(?<!\\(\\s*)" and
60+ // Not followed by a parenthesis
61+ noAfterParen = "(?!\\s*\\))" and
62+ // Parameter at word boundary in optional whitespace
63+ paramMatch = "\\s*\\b" + parameter + "\\b\\s*" and
64+ // A parameter is #'d if it is preceded or followed by the # or ## operators.
65+ notHashed = "(?<!#)" + paramMatch and
66+ // Parameter is used without a leading or trailing parenthesis, and without #.
67+ getBody ( )
68+ .regexpMatch ( ".*(" + noBeforeParen + notHashed + "|" + notHashed + noAfterParen + ").*" )
69+ )
70+ )
71+ }
2972}
3073
3174newtype TMacroOperator =
0 commit comments