Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d603824

Browse files
committed
JS: add StringOps::StartsWith and StringOps::Includes
1 parent 4398670 commit d603824

6 files changed

Lines changed: 352 additions & 85 deletions

File tree

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import semmle.javascript.SSA
4141
import semmle.javascript.StandardLibrary
4242
import semmle.javascript.Stmt
4343
import semmle.javascript.StringConcatenation
44+
import semmle.javascript.StringOps
4445
import semmle.javascript.Templates
4546
import semmle.javascript.Tokens
4647
import semmle.javascript.TypeScript
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
/**
2+
* Provides classes and predicates for reasoning about string-manipulating expressions.
3+
*/
4+
import javascript
5+
6+
module StringOps {
7+
8+
/**
9+
* A expression that is equivalent to `A.startsWith(B)` or `!A.startsWith(B)`.
10+
*/
11+
abstract class StartsWith extends DataFlow::Node {
12+
/**
13+
* Gets the `A` in `A.startsWith(B)`.
14+
*/
15+
abstract DataFlow::Node getBaseString();
16+
17+
/**
18+
* Gets the `B` in `A.startsWith(B)`.
19+
*/
20+
abstract DataFlow::Node getSubstring();
21+
22+
/**
23+
* Gets the polarity if the check.
24+
*
25+
* If the polarity is `false` the check returns `true` if the string does not start
26+
* with the given substring.
27+
*/
28+
boolean getPolarity() { result = true }
29+
}
30+
31+
/**
32+
* An expression of form `A.startsWith(B)`.
33+
*/
34+
private class StartsWith_Native extends StartsWith, DataFlow::MethodCallNode {
35+
StartsWith_Native() {
36+
getMethodName() = "startsWith" and
37+
getNumArgument() = 1
38+
}
39+
40+
override DataFlow::Node getBaseString() {
41+
result = getReceiver()
42+
}
43+
44+
override DataFlow::Node getSubstring() {
45+
result = getArgument(0)
46+
}
47+
}
48+
49+
/**
50+
* An expression of form `A.indexOf(B) === 0`.
51+
*/
52+
private class StartsWith_IndexOfEquals extends StartsWith, DataFlow::ValueNode {
53+
override EqualityTest astNode;
54+
DataFlow::MethodCallNode indexOf;
55+
56+
StartsWith_IndexOfEquals() {
57+
indexOf.getMethodName() = "indexOf" and
58+
indexOf.getNumArgument() = 1 and
59+
indexOf.flowsToExpr(astNode.getAnOperand()) and
60+
astNode.getAnOperand().getIntValue() = 0
61+
}
62+
63+
override DataFlow::Node getBaseString() {
64+
result = indexOf.getReceiver()
65+
}
66+
67+
override DataFlow::Node getSubstring() {
68+
result = indexOf.getArgument(0)
69+
}
70+
71+
override boolean getPolarity() {
72+
result = astNode.getPolarity()
73+
}
74+
}
75+
76+
/**
77+
* An expression of form `A.indexOf(B)` coerced to a boolean.
78+
*
79+
* This is equivalent to `!A.startsWith(B)` since all return values other than zero map to `true`.
80+
*/
81+
private class StartsWith_IndexOfCoercion extends StartsWith, DataFlow::MethodCallNode {
82+
StartsWith_IndexOfCoercion() {
83+
getMethodName() = "indexOf" and
84+
getNumArgument() = 1 and
85+
this.flowsToExpr(any(ConditionGuardNode guard).getTest()) // check for boolean coercion
86+
}
87+
88+
override DataFlow::Node getBaseString() {
89+
result = getReceiver()
90+
}
91+
92+
override DataFlow::Node getSubstring() {
93+
result = getArgument(0)
94+
}
95+
96+
override boolean getPolarity() {
97+
result = false
98+
}
99+
}
100+
101+
/**
102+
* A call of form `_.startsWith(A, B)` or `ramda.startsWith(A, B)`.
103+
*/
104+
private class StartsWith_Library extends StartsWith, DataFlow::CallNode {
105+
StartsWith_Library() {
106+
getNumArgument() = 2 and
107+
exists (DataFlow::SourceNode callee | this = callee.getACall() |
108+
callee = LodashUnderscore::member("startsWith") or
109+
callee = DataFlow::moduleMember("ramda", "startsWith")
110+
)
111+
}
112+
113+
override DataFlow::Node getBaseString() {
114+
result = getArgument(0)
115+
}
116+
117+
override DataFlow::Node getSubstring() {
118+
result = getArgument(1)
119+
}
120+
}
121+
122+
/**
123+
* A comparison of form `x[0] === "k"` for some single-character constant `k`.
124+
*/
125+
private class StartsWith_FirstCharacter extends StartsWith, DataFlow::ValueNode {
126+
override EqualityTest astNode;
127+
DataFlow::PropRead read;
128+
Expr constant;
129+
130+
StartsWith_FirstCharacter() {
131+
read.flowsTo(astNode.getAnOperand().flow()) and
132+
read.getPropertyNameExpr().getIntValue() = 0 and
133+
constant.getStringValue().length() = 1 and
134+
astNode.getAnOperand() = constant
135+
}
136+
137+
override DataFlow::Node getBaseString() {
138+
result = read.getBase()
139+
}
140+
141+
override DataFlow::Node getSubstring() {
142+
result = constant.flow()
143+
}
144+
145+
override boolean getPolarity() {
146+
result = astNode.getPolarity()
147+
}
148+
}
149+
150+
151+
/**
152+
* A expression that is equivalent to `A.includes(B)` or `!A.includes(B)`.
153+
*
154+
* Note that this also includes calls to the array method named `includes`.
155+
*/
156+
abstract class Includes extends DataFlow::Node {
157+
/** Gets the `A` in `A.includes(B)`. */
158+
abstract DataFlow::Node getBaseString();
159+
160+
/** Gets the `B` in `A.includes(B)`. */
161+
abstract DataFlow::Node getSubstring();
162+
163+
/**
164+
* Gets the polarity if the check.
165+
*
166+
* If the polarity is `false` the check returns `true` if the string does not start
167+
* with the given substring.
168+
*/
169+
boolean getPolarity() { result = true }
170+
}
171+
172+
/**
173+
* A call to a method named `includes`, assumed to refer to `String.prototype.includes`.
174+
*/
175+
private class Includes_Native extends Includes, DataFlow::MethodCallNode {
176+
Includes_Native() {
177+
getMethodName() = "includes" and
178+
getNumArgument() = 1
179+
}
180+
181+
override DataFlow::Node getBaseString() {
182+
result = getReceiver()
183+
}
184+
185+
override DataFlow::Node getSubstring() {
186+
result = getArgument(0)
187+
}
188+
}
189+
190+
/**
191+
* A call to `_.includes`, assumed to operate on strings.
192+
*/
193+
private class Includes_Library extends Includes, DataFlow::CallNode {
194+
Includes_Library() {
195+
this = LodashUnderscore::member("includes").getACall()
196+
}
197+
198+
override DataFlow::Node getBaseString() {
199+
result = getArgument(0)
200+
}
201+
202+
override DataFlow::Node getSubstring() {
203+
result = getArgument(1)
204+
}
205+
}
206+
207+
/**
208+
* A check of form `A.indexOf(B) !== -1` or similar.
209+
*/
210+
private class Includes_IndexOfEquals extends Includes, DataFlow::ValueNode {
211+
MethodCallExpr indexOf;
212+
override EqualityTest astNode;
213+
214+
Includes_IndexOfEquals() {
215+
exists (Expr index | astNode.hasOperands(indexOf, index) |
216+
// one operand is of the form `whitelist.indexOf(x)`
217+
indexOf.getMethodName() = "indexOf" and
218+
// and the other one is -1
219+
index.getIntValue() = -1
220+
)
221+
}
222+
223+
override DataFlow::Node getBaseString() {
224+
result = indexOf.getReceiver().flow()
225+
}
226+
227+
override DataFlow::Node getSubstring() {
228+
result = indexOf.getArgument(0).flow()
229+
}
230+
231+
override boolean getPolarity() {
232+
result = astNode.getPolarity().booleanNot()
233+
}
234+
}
235+
236+
/**
237+
* A check of form `A.indexOf(B) >= 0` or similar.
238+
*/
239+
private class Includes_IndexOfRelational extends Includes, DataFlow::ValueNode {
240+
MethodCallExpr indexOf;
241+
override RelationalComparison astNode;
242+
boolean polarity;
243+
244+
Includes_IndexOfRelational() {
245+
exists (Expr lesser, Expr greater |
246+
astNode.getLesserOperand() = lesser and
247+
astNode.getGreaterOperand() = greater and
248+
indexOf.getMethodName() = "indexOf" and
249+
indexOf.getNumArgument() = 1 |
250+
polarity = true and
251+
greater = indexOf and
252+
(
253+
lesser.getIntValue() >= 0
254+
or
255+
lesser.getIntValue() = -1 and not astNode.isInclusive()
256+
)
257+
or
258+
polarity = false and
259+
lesser = indexOf and
260+
(
261+
greater.getIntValue() = -1
262+
or
263+
greater.getIntValue() = 0 and not astNode.isInclusive()
264+
)
265+
)
266+
}
267+
268+
override DataFlow::Node getBaseString() {
269+
result = indexOf.getReceiver().flow()
270+
}
271+
272+
override DataFlow::Node getSubstring() {
273+
result = indexOf.getArgument(0).flow()
274+
}
275+
276+
override boolean getPolarity() {
277+
result = polarity
278+
}
279+
}
280+
281+
/**
282+
* An expression of form `~A.indexOf(B)` which, when coerced to a boolean, is equivalent to `A.includes(B)`.
283+
*/
284+
private class Includes_IndexOfBitwise extends Includes, DataFlow::ValueNode {
285+
MethodCallExpr indexOf;
286+
override BitNotExpr astNode;
287+
288+
Includes_IndexOfBitwise() {
289+
astNode.getOperand() = indexOf and
290+
indexOf.getMethodName() = "indexOf"
291+
}
292+
293+
override DataFlow::Node getBaseString() {
294+
result = indexOf.getReceiver().flow()
295+
}
296+
297+
override DataFlow::Node getSubstring() {
298+
result = indexOf.getArgument(0).flow()
299+
}
300+
}
301+
302+
}

0 commit comments

Comments
 (0)