Skip to content

Commit de3c8bf

Browse files
committed
JS: Introduce DataFlow::lvalueNode
1 parent d4e39a2 commit de3c8bf

File tree

5 files changed

+83
-111
lines changed

5 files changed

+83
-111
lines changed

javascript/ql/src/semmle/javascript/SSA.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ class SsaExplicitDefinition extends SsaDefinition, TExplicitDef {
527527
* if any.
528528
*/
529529
DataFlow::Node getRhsNode() {
530-
result = DataFlow::defSourceNode(getDef(), getSourceVariable())
530+
result = DataFlow::ssaDefinitionNode(this).getImmediatePredecessor()
531531
}
532532
}
533533

javascript/ql/src/semmle/javascript/StringConcatenation.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module StringConcatenation {
99
private DataFlow::Node getAssignAddResult(AssignAddExpr expr) {
1010
result = expr.flow()
1111
or
12-
result = DataFlow::ssaDefinitionNode(SSA::definition(expr))
12+
result = DataFlow::lvalueNode(expr.getTarget())
1313
}
1414

1515
/** Gets the `n`th operand to the string concatenation defining `node`. */

javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll

Lines changed: 69 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -166,33 +166,27 @@ module DataFlow {
166166
* Gets the immediate predecessor of this node, if any.
167167
*
168168
* A node with an immediate predecessor can usually only have the value that flows
169-
* into its from its immediate predecessor, currently with two exceptions:
169+
* into its from its immediate predecessor, currently with one exception:
170170
*
171171
* - An immediately-invoked function expression with a single return expression `e`
172172
* has `e` as its immediate predecessor, even if the function can fall over the
173173
* end and return `undefined`.
174-
*
175-
* - A destructuring property pattern or element pattern with a default value has
176-
* both the `PropRead` and its default value as immediate predecessors.
177174
*/
178175
cached
179176
DataFlow::Node getImmediatePredecessor() {
177+
lvalueFlowStep(result, this) and
178+
not lvalueDefaultFlowStep(_, this)
179+
or
180180
// Use of variable -> definition of variable
181181
exists(SsaVariable var |
182-
this = DataFlow::valueNode(var.getAUse()) and
183-
result.(DataFlow::SsaDefinitionNode).getSsaVariable() = var
182+
this = valueNode(var.getAUse()) and
183+
result = TSsaDefNode(var)
184184
)
185185
or
186186
// Refinement of variable -> original definition of variable
187187
exists(SsaRefinementNode refinement |
188-
this.(DataFlow::SsaDefinitionNode).getSsaVariable() = refinement.getVariable() and
189-
result.(DataFlow::SsaDefinitionNode).getSsaVariable() = refinement.getAnInput()
190-
)
191-
or
192-
// Definition of variable -> RHS of definition
193-
exists(SsaExplicitDefinition def |
194-
this = TSsaDefNode(def) and
195-
result = def.getRhsNode()
188+
this = TSsaDefNode(refinement) and
189+
result = TSsaDefNode(refinement.getAnInput())
196190
)
197191
or
198192
// IIFE call -> return value of IIFE
@@ -202,24 +196,6 @@ module DataFlow {
202196
result = fun.getAReturnedExpr().flow() and
203197
strictcount(fun.getAReturnedExpr()) = 1
204198
)
205-
or
206-
// IIFE parameter -> IIFE call
207-
exists(Parameter param |
208-
this = DataFlow::parameterNode(param) and
209-
localArgumentPassing(result.asExpr(), param)
210-
)
211-
or
212-
// `{ x } -> e` in `let { x } = e`
213-
exists(DestructuringPattern pattern |
214-
this = TDestructuringPatternNode(pattern)
215-
|
216-
exists(VarDef def |
217-
pattern = def.getTarget() and
218-
result = DataFlow::valueNode(def.getDestructuringSource())
219-
)
220-
or
221-
result = patternPropRead(pattern)
222-
)
223199
}
224200
}
225201

@@ -1106,11 +1082,7 @@ module DataFlow {
11061082
* INTERNAL: Use `parameterNode(Parameter)` instead.
11071083
*/
11081084
predicate parameterNode(DataFlow::Node nd, Parameter p) {
1109-
nd = ssaDefinitionNode(SSA::definition((SimpleParameter)p))
1110-
or
1111-
nd = TDestructuringPatternNode(p)
1112-
or
1113-
nd = TUnusedParameterNode(p)
1085+
nd = lvalueNode(p)
11141086
}
11151087

11161088
/**
@@ -1150,24 +1122,21 @@ module DataFlow {
11501122
}
11511123

11521124
/**
1153-
* INTERNAL. DO NOT USE.
1154-
*
1155-
* Gets the `PropRead` node corresponding to the value stored in the given
1156-
* binding pattern due to destructuring.
1125+
* Gets the data flow node corresponding the given l-value expression.
11571126
*
1158-
* For example, in `let { p: value } = f()`, the `value` pattern maps to a `PropRead`
1159-
* extracting the `p` property.
1127+
* This differs from `DataFlow::valueNode()`, which represents the value
1128+
* _before_ the l-value is assigned to, whereas `DataFlow::lvalueNode()`
1129+
* represents the value _after_ the assignment.
11601130
*/
1161-
private DataFlow::PropRead patternPropRead(BindingPattern value) {
1162-
exists(PropertyPattern prop |
1163-
value = prop.getValuePattern() and
1164-
result = TPropNode(prop)
1131+
Node lvalueNode(BindingPattern lvalue) {
1132+
exists(SsaExplicitDefinition ssa |
1133+
ssa.defines(lvalue.(LValue).getDefNode(), lvalue.(VarRef).getVariable()) and
1134+
result = TSsaDefNode(ssa)
11651135
)
11661136
or
1167-
exists(ArrayPattern array |
1168-
value = array.getAnElement() and
1169-
result = TElementPatternNode(array, value)
1170-
)
1137+
result = TDestructuringPatternNode(lvalue)
1138+
or
1139+
result = TUnusedParameterNode(lvalue)
11711140
}
11721141

11731142
/**
@@ -1212,18 +1181,60 @@ module DataFlow {
12121181
any(ImmediatelyInvokedFunctionExpr iife).argumentPassing(parm, arg)
12131182
}
12141183

1184+
/**
1185+
* Holds if there is a step from `pred -> succ` due to an assignment
1186+
* to an expression in l-value position.
1187+
*/
1188+
private predicate lvalueFlowStep(Node pred, Node succ) {
1189+
exists(VarDef def |
1190+
pred = valueNode(defSourceNode(def)) and
1191+
succ = lvalueNode(def.getTarget())
1192+
)
1193+
or
1194+
exists(PropertyPattern pattern |
1195+
pred = TPropNode(pattern) and
1196+
succ = lvalueNode(pattern.getValuePattern())
1197+
)
1198+
or
1199+
exists(Expr element |
1200+
pred = TElementPatternNode(_, element) and
1201+
succ = lvalueNode(element)
1202+
)
1203+
}
1204+
1205+
/**
1206+
* Holds if there is a step from `pred -> succ` from the default
1207+
* value of a destructuring pattern or parameter.
1208+
*/
1209+
private predicate lvalueDefaultFlowStep(Node pred, Node succ) {
1210+
exists(PropertyPattern pattern |
1211+
pred = valueNode(pattern.getDefault()) and
1212+
succ = lvalueNode(pattern.getValuePattern())
1213+
)
1214+
or
1215+
exists(ArrayPattern array, int i |
1216+
pred = valueNode(array.getDefault(i)) and
1217+
succ = lvalueNode(array.getElement(i))
1218+
)
1219+
or
1220+
exists(Parameter param |
1221+
pred = valueNode(param.getDefault()) and
1222+
succ = parameterNode(param)
1223+
)
1224+
}
1225+
12151226
/**
12161227
* Holds if data can flow from `pred` to `succ` in one local step.
12171228
*/
12181229
cached
12191230
predicate localFlowStep(Node pred, Node succ) {
1220-
// flow into local variables
1221-
exists(SsaDefinition ssa | succ = TSsaDefNode(ssa) |
1222-
// from the rhs of an explicit definition into the variable
1223-
exists(SsaExplicitDefinition def | def = ssa |
1224-
pred = defSourceNode(def.getDef(), def.getSourceVariable())
1225-
)
1226-
or
1231+
// flow from RHS into LHS
1232+
lvalueFlowStep(pred, succ)
1233+
or
1234+
lvalueDefaultFlowStep(pred, succ)
1235+
or
1236+
// Flow through implicit SSA nodes
1237+
exists(SsaImplicitDefinition ssa | succ = TSsaDefNode(ssa) |
12271238
// from any explicit definition or implicit init of a captured variable into
12281239
// the capturing definition
12291240
exists(SsaSourceVariable v, SsaDefinition predDef |
@@ -1270,29 +1281,6 @@ module DataFlow {
12701281
)
12711282
)
12721283
or
1273-
exists(VarDef def |
1274-
// from `e` to `{ p: x }` in `{ p: x } = e`
1275-
pred = valueNode(defSourceNode(def)) and
1276-
succ = TDestructuringPatternNode(def.getTarget())
1277-
)
1278-
or
1279-
// flow from the value read from a property pattern to the value being
1280-
// destructured in the child pattern. For example, for
1281-
//
1282-
// let { p: { q: x } } = obj
1283-
//
1284-
// add edge from the 'p:' pattern to '{ q:x }'.
1285-
exists(PropertyPattern pattern |
1286-
pred = TPropNode(pattern) and
1287-
succ = TDestructuringPatternNode(pattern.getValuePattern())
1288-
)
1289-
or
1290-
// Like the step above, but for array destructuring patterns.
1291-
exists(Expr elm |
1292-
pred = TElementPatternNode(_, elm) and
1293-
succ = TDestructuringPatternNode(elm)
1294-
)
1295-
or
12961284
// flow from 'this' parameter into 'this' expressions
12971285
exists(ThisExpr thiz |
12981286
pred = TThisNode(thiz.getBindingContainer()) and
@@ -1323,31 +1311,6 @@ module DataFlow {
13231311
localArgumentPassing(result, def)
13241312
}
13251313

1326-
/**
1327-
* INTERNAL. DO NOT USE.
1328-
*
1329-
* Gets the data flow node representing the source of the definition of `v` at `def`,
1330-
* if any.
1331-
*/
1332-
Node defSourceNode(VarDef def, SsaSourceVariable v) {
1333-
exists(BindingPattern lhs, VarRef r |
1334-
lhs = def.getTarget() and r = lhs.getABindingVarRef() and r.getVariable() = v
1335-
|
1336-
// follow one step of the def-use chain if the lhs is a simple variable reference
1337-
lhs = r and
1338-
result = TValueNode(defSourceNode(def))
1339-
or
1340-
// handle destructuring assignments
1341-
exists(PropertyPattern pp | r = pp.getValuePattern() |
1342-
result = TPropNode(pp) or result = pp.getDefault().flow()
1343-
)
1344-
or
1345-
result = TElementPatternNode(_, r)
1346-
or
1347-
exists(ArrayPattern ap, int i | ap.getElement(i) = r and result = ap.getDefault(i).flow())
1348-
)
1349-
}
1350-
13511314
/**
13521315
* Holds if the flow information for this node is incomplete.
13531316
*

javascript/ql/test/library-tests/GlobalAccessPaths/GlobalAccessPaths.expected

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,12 @@ test_fromReference
5151
| test.js:24:9:24:16 | NS \|\| {} | NS |
5252
| test.js:26:1:26:8 | Conflict | Conflict |
5353
| test.js:33:7:33:18 | { bar = {} } | foo |
54-
| test.js:33:7:33:24 | bar | foo.bar |
5554
| test.js:33:9:33:16 | bar = {} | foo.bar |
5655
| test.js:33:22:33:24 | foo | foo |
57-
| test.js:34:11:34:13 | bar | foo.bar |
58-
| test.js:34:11:34:17 | bar.baz | foo.bar.baz |
56+
| test.js:39:3:39:20 | lazyInit | foo.bar |
57+
| test.js:39:14:39:16 | foo | foo |
58+
| test.js:39:14:39:20 | foo.bar | foo.bar |
59+
| test.js:40:3:40:10 | lazyInit | foo.bar |
5960
test_fromRhs
6061
| other_ns.js:4:9:4:16 | NS \|\| {} | NS |
6162
| other_ns.js:6:12:6:13 | {} | Conflict |
@@ -65,8 +66,10 @@ test_fromRhs
6566
| test.js:28:1:28:20 | class GlobalClass {} | GlobalClass |
6667
| test.js:30:1:30:28 | functio ... on() {} | globalFunction |
6768
| test.js:32:1:35:1 | functio ... .baz'\\n} | destruct |
69+
| test.js:37:1:41:1 | functio ... Init;\\n} | lazy |
6870
test_assignedUnique
6971
| GlobalClass |
7072
| destruct |
7173
| f |
7274
| globalFunction |
75+
| lazy |

javascript/ql/test/library-tests/GlobalAccessPaths/test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,9 @@ function destruct() {
3333
let { bar = {} } = foo;
3434
let v = bar.baz; // 'foo.bar.baz'
3535
}
36+
37+
function lazy() {
38+
var lazyInit;
39+
lazyInit = foo.bar; // 'foo.bar'
40+
lazyInit;
41+
}

0 commit comments

Comments
 (0)