@@ -20,8 +20,8 @@ import javascript
2020string metachar ( ) { result = "'\"\\&<>\n\r\t*|{}[]%$" .charAt ( _) }
2121
2222/** Gets a string matched by `e` in a `replace` call. */
23- string getAMatchedString ( Expr e ) {
24- result = e .( RegExpLiteral ) .getRoot ( ) .getAMatchedString ( )
23+ string getAMatchedString ( DataFlow :: Node e ) {
24+ result = e .( DataFlow :: RegExpLiteralNode ) .getRoot ( ) .getAMatchedString ( )
2525 or
2626 result = e .getStringValue ( )
2727}
@@ -44,16 +44,15 @@ predicate isSimple(RegExpTerm t) {
4444 * Holds if `mce` is of the form `x.replace(re, new)`, where `re` is a global
4545 * regular expression and `new` prefixes the matched string with a backslash.
4646 */
47- predicate isBackslashEscape ( MethodCallExpr mce , RegExpLiteral re ) {
48- mce .getMethodName ( ) = "replace" and
49- re .flow ( ) .( DataFlow:: SourceNode ) .flowsToExpr ( mce .getArgument ( 0 ) ) and
50- re .isGlobal ( ) and
51- exists ( string new | new = mce .getArgument ( 1 ) .getStringValue ( ) |
52- // `new` is `\$&`, `\$1` or similar
53- new .regexpMatch ( "\\\\\\$(&|\\d)" )
47+ predicate isBackslashEscape ( StringReplaceCall mce , DataFlow:: RegExpLiteralNode re ) {
48+ mce .isGlobal ( ) and
49+ re = mce .getRegExp ( ) and
50+ (
51+ // replacement with `\$&`, `\$1` or similar
52+ mce .getRawReplacement ( ) .getStringValue ( ) .regexpMatch ( "\\\\\\$(&|\\d)" )
5453 or
55- // `new` is `\c`, where `c` is a constant matched by `re `
56- new . regexpMatch ( "\\\\\\Q " + getAMatchedString ( re ) + "\\E" )
54+ // replacement of `c` with `\c `
55+ exists ( string c | mce . replaces ( c , "\\" + c ) )
5756 )
5857}
5958
@@ -65,7 +64,7 @@ predicate allBackslashesEscaped(DataFlow::Node nd) {
6564 nd = DataFlow:: globalVarRef ( "JSON" ) .getAMemberCall ( "stringify" )
6665 or
6766 // check whether `nd` itself escapes backslashes
68- exists ( RegExpLiteral rel | isBackslashEscape ( nd . asExpr ( ) , rel ) |
67+ exists ( DataFlow :: RegExpLiteralNode rel | isBackslashEscape ( nd , rel ) |
6968 // if it's a complex regexp, we conservatively assume that it probably escapes backslashes
7069 not isSimple ( rel .getRoot ( ) ) or
7170 getAMatchedString ( rel ) = "\\"
@@ -91,10 +90,8 @@ predicate allBackslashesEscaped(DataFlow::Node nd) {
9190/**
9291 * Holds if `repl` looks like a call to "String.prototype.replace" that deliberately removes the first occurrence of `str`.
9392 */
94- predicate removesFirstOccurence ( DataFlow:: MethodCallNode repl , string str ) {
95- repl .getMethodName ( ) = "replace" and
96- repl .getArgument ( 0 ) .getStringValue ( ) = str and
97- repl .getArgument ( 1 ) .getStringValue ( ) = ""
93+ predicate removesFirstOccurence ( StringReplaceCall repl , string str ) {
94+ not exists ( repl .getRegExp ( ) ) and repl .replaces ( str , "" )
9895}
9996
10097/**
@@ -124,22 +121,20 @@ predicate isDelimiterUnwrapper(
124121 * Holds if `repl` is a standalone use of `String.prototype.replace` to remove a single newline.
125122 */
126123
127- predicate removesTrailingNewLine ( DataFlow:: MethodCallNode repl ) {
128- repl .getMethodName ( ) = "replace" and
129- repl .getArgument ( 0 ) .mayHaveStringValue ( "\n" ) and
130- repl .getArgument ( 1 ) .mayHaveStringValue ( "" ) and
131- not exists ( DataFlow:: MethodCallNode other | other .getMethodName ( ) = "replace" |
124+ predicate removesTrailingNewLine ( StringReplaceCall repl ) {
125+ not repl .isGlobal ( ) and
126+ repl .replaces ( "\n" , "" ) and
127+ not exists ( StringReplaceCall other |
132128 repl .getAMethodCall ( ) = other or
133129 other .getAMethodCall ( ) = repl
134130 )
135131}
136132
137- from MethodCallExpr repl , Expr old , string msg
133+ from StringReplaceCall repl , DataFlow :: Node old , string msg
138134where
139- repl .getMethodName ( ) = "replace" and
140- ( old = repl .getArgument ( 0 ) or old .flow ( ) .( DataFlow:: SourceNode ) .flowsToExpr ( repl .getArgument ( 0 ) ) ) and
135+ ( old = repl .getArgument ( 0 ) or old = repl .getRegExp ( ) ) and
141136 (
142- not old . ( RegExpLiteral ) .isGlobal ( ) and
137+ not repl .isGlobal ( ) and
143138 msg = "This replaces only the first occurrence of " + old + "." and
144139 // only flag if this is likely to be a sanitizer or URL encoder or decoder
145140 exists ( string m | m = getAMatchedString ( old ) |
@@ -158,17 +153,17 @@ where
158153 ( m = ".." or m = "/.." or m = "../" or m = "/../" )
159154 ) and
160155 // don't flag replace operations in a loop
161- not DataFlow :: valueNode ( repl .getReceiver ( ) ) = DataFlow :: valueNode ( repl ) .getASuccessor + ( ) and
156+ not repl .getReceiver ( ) = repl .getASuccessor + ( ) and
162157 // dont' flag unwrapper
163- not isDelimiterUnwrapper ( repl . flow ( ) , _) and
164- not isDelimiterUnwrapper ( _, repl . flow ( ) ) and
158+ not isDelimiterUnwrapper ( repl , _) and
159+ not isDelimiterUnwrapper ( _, repl ) and
165160 // dont' flag the removal of trailing newlines
166- not removesTrailingNewLine ( repl . flow ( ) )
161+ not removesTrailingNewLine ( repl )
167162 or
168- exists ( RegExpLiteral rel |
163+ exists ( DataFlow :: RegExpLiteralNode rel |
169164 isBackslashEscape ( repl , rel ) and
170- not allBackslashesEscaped ( DataFlow :: valueNode ( repl ) ) and
165+ not allBackslashesEscaped ( repl ) and
171166 msg = "This does not escape backslash characters in the input."
172167 )
173168 )
174- select repl .getCallee ( ) , msg
169+ select repl .getCalleeNode ( ) , msg
0 commit comments