@@ -101,6 +101,34 @@ predicate allBackslashesEscaped(DataFlow::Node nd) {
101
101
allBackslashesEscaped ( nd .getAPredecessor ( ) )
102
102
}
103
103
104
+ /**
105
+ * Holds if `repl` looks like a call to "String.prototype.replace" that deliberately removes the first occurrence of `str`.
106
+ */
107
+ predicate removesFirstOccurence ( DataFlow:: MethodCallNode repl , string str ) {
108
+ repl .getMethodName ( ) = "replace" and
109
+ repl .getArgument ( 0 ) .getStringValue ( ) = str and
110
+ repl .getArgument ( 1 ) .getStringValue ( ) = ""
111
+ }
112
+
113
+ /**
114
+ * Holds if `leftUnwrap` and `rightUnwrap` unwraps a string from a pair of surrounding delimiters.
115
+ */
116
+ predicate isDelimiterUnwrapper (
117
+ DataFlow:: MethodCallNode leftUnwrap , DataFlow:: MethodCallNode rightUnwrap
118
+ ) {
119
+ exists ( string left , string right |
120
+ left = "[" and right = "]"
121
+ or
122
+ left = "{" and right = "}"
123
+ or
124
+ left = "(" and right = ")"
125
+ |
126
+ removesFirstOccurence ( leftUnwrap , left ) and
127
+ removesFirstOccurence ( rightUnwrap , right ) and
128
+ leftUnwrap .getAMethodCall ( ) = rightUnwrap
129
+ )
130
+ }
131
+
104
132
from MethodCallExpr repl , Expr old , string msg
105
133
where
106
134
repl .getMethodName ( ) = "replace" and
@@ -122,7 +150,10 @@ where
122
150
)
123
151
) and
124
152
// don't flag replace operations in a loop
125
- not DataFlow:: valueNode ( repl .getReceiver ( ) ) = DataFlow:: valueNode ( repl ) .getASuccessor + ( )
153
+ not DataFlow:: valueNode ( repl .getReceiver ( ) ) = DataFlow:: valueNode ( repl ) .getASuccessor + ( ) and
154
+ // dont' flag unwrapper
155
+ not isDelimiterUnwrapper ( repl .flow ( ) , _) and
156
+ not isDelimiterUnwrapper ( _, repl .flow ( ) )
126
157
or
127
158
exists ( RegExpLiteral rel |
128
159
isBackslashEscape ( repl , rel ) and
0 commit comments