Skip to content

Commit 327d5ac

Browse files
authored
Merge pull request #1686 from asger-semmle/lvalue-node
Approved by xiemaisi
2 parents 927e00b + 8bec2fe commit 327d5ac

File tree

12 files changed

+132
-149
lines changed

12 files changed

+132
-149
lines changed

javascript/ql/src/semmle/javascript/RangeAnalysis.qll

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -98,29 +98,6 @@ module RangeAnalysis {
9898
)
9999
}
100100

101-
/**
102-
* Holds if the given node has a unique data flow predecessor.
103-
*/
104-
pragma[noinline]
105-
private predicate hasUniquePredecessor(DataFlow::Node node) {
106-
isRelevant(node) and
107-
strictcount(node.getAPredecessor()) = 1 and
108-
// exclude parameters with default values
109-
not exists(Parameter p |
110-
DataFlow::parameterNode(p) = node and
111-
exists(p.getDefault())
112-
)
113-
}
114-
115-
/**
116-
* Gets the definition of `node`, without unfolding phi nodes.
117-
*/
118-
DataFlow::Node getDefinition(DataFlow::Node node) {
119-
if hasUniquePredecessor(node)
120-
then result = getDefinition(node.getAPredecessor())
121-
else result = node
122-
}
123-
124101
/**
125102
* Gets a data flow node holding the result of the add/subtract operation in
126103
* the given increment/decrement expression.
@@ -229,8 +206,8 @@ module RangeAnalysis {
229206
* Holds if `r` can be modelled as `r = root * sign + bias`.
230207
*/
231208
predicate linearDefinition(DataFlow::Node r, DataFlow::Node root, int sign, Bias bias) {
232-
if hasUniquePredecessor(r)
233-
then linearDefinition(r.getAPredecessor(), root, sign, bias)
209+
if exists(r.getImmediatePredecessor())
210+
then linearDefinition(r.getImmediatePredecessor(), root, sign, bias)
234211
else
235212
if linearDefinitionStep(r, _, _, _)
236213
then
@@ -257,8 +234,8 @@ module RangeAnalysis {
257234
predicate linearDefinitionSum(
258235
DataFlow::Node r, DataFlow::Node xroot, int xsign, DataFlow::Node yroot, int ysign, Bias bias
259236
) {
260-
if hasUniquePredecessor(r)
261-
then linearDefinitionSum(r.getAPredecessor(), xroot, xsign, yroot, ysign, bias)
237+
if exists(r.getImmediatePredecessor())
238+
then linearDefinitionSum(r.getImmediatePredecessor(), xroot, xsign, yroot, ysign, bias)
262239
else
263240
if exists(r.asExpr().getIntValue())
264241
then none() // do not model constants as sums
@@ -336,7 +313,8 @@ module RangeAnalysis {
336313
ConditionGuardNode guard, DataFlow::Node a, int asign, string operator, DataFlow::Node b,
337314
int bsign, Bias bias
338315
) {
339-
exists(Comparison compare | compare = getDefinition(guard.getTest().flow()).asExpr() |
316+
exists(Comparison compare |
317+
compare = guard.getTest().flow().getImmediatePredecessor*().asExpr() and
340318
linearComparison(compare, a, asign, b, bsign, bias) and
341319
(
342320
guard.getOutcome() = true and operator = compare.getOperator()

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/Stmt.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,17 @@ abstract class EnhancedForLoop extends LoopStmt {
818818
result = getIterator().(DeclStmt).getADecl()
819819
}
820820

821+
/**
822+
* Gets the property, variable, or destructuring pattern occurring as the iterator
823+
* expression in this `for`-`in` or `for`-`of` loop.
824+
*/
825+
Expr getLValue() {
826+
result = getIterator() and
827+
(result instanceof BindingPattern or result instanceof PropAccess)
828+
or
829+
result = getIterator().(DeclStmt).getADecl().getBindingPattern()
830+
}
831+
821832
/**
822833
* Gets an iterator variable of this `for`-`in` or `for`-`of` loop.
823834
*/

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: 72 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -166,59 +166,31 @@ 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:
170-
*
171-
* - An immediately-invoked function expression with a single return expression `e`
172-
* has `e` as its immediate predecessor, even if the function can fall over the
173-
* 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.
169+
* into its from its immediate predecessor.
177170
*/
178171
cached
179172
DataFlow::Node getImmediatePredecessor() {
173+
lvalueFlowStep(result, this) and
174+
not lvalueDefaultFlowStep(_, this)
175+
or
180176
// Use of variable -> definition of variable
181177
exists(SsaVariable var |
182-
this = DataFlow::valueNode(var.getAUse()) and
183-
result.(DataFlow::SsaDefinitionNode).getSsaVariable() = var
178+
this = valueNode(var.getAUse()) and
179+
result = TSsaDefNode(var)
184180
)
185181
or
186182
// Refinement of variable -> original definition of variable
187183
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()
184+
this = TSsaDefNode(refinement) and
185+
result = TSsaDefNode(refinement.getAnInput())
196186
)
197187
or
198188
// IIFE call -> return value of IIFE
199-
// Note: not sound in case function falls over end and returns 'undefined'
200189
exists(Function fun |
201190
localCall(this.asExpr(), fun) and
202191
result = fun.getAReturnedExpr().flow() and
203-
strictcount(fun.getAReturnedExpr()) = 1
204-
)
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)
192+
strictcount(fun.getAReturnedExpr()) = 1 and
193+
not fun.getExit().isJoin() // can only reach exit by the return statement
222194
)
223195
}
224196
}
@@ -1106,11 +1078,7 @@ module DataFlow {
11061078
* INTERNAL: Use `parameterNode(Parameter)` instead.
11071079
*/
11081080
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)
1081+
nd = lvalueNode(p)
11141082
}
11151083

11161084
/**
@@ -1150,24 +1118,22 @@ module DataFlow {
11501118
}
11511119

11521120
/**
1153-
* INTERNAL. DO NOT USE.
1121+
* Gets the data flow node corresponding the given l-value expression, if
1122+
* such a node exists.
11541123
*
1155-
* Gets the `PropRead` node corresponding to the value stored in the given
1156-
* binding pattern due to destructuring.
1157-
*
1158-
* For example, in `let { p: value } = f()`, the `value` pattern maps to a `PropRead`
1159-
* extracting the `p` property.
1124+
* This differs from `DataFlow::valueNode()`, which represents the value
1125+
* _before_ the l-value is assigned to, whereas `DataFlow::lvalueNode()`
1126+
* represents the value _after_ the assignment.
11601127
*/
1161-
private DataFlow::PropRead patternPropRead(BindingPattern value) {
1162-
exists(PropertyPattern prop |
1163-
value = prop.getValuePattern() and
1164-
result = TPropNode(prop)
1128+
Node lvalueNode(BindingPattern lvalue) {
1129+
exists(SsaExplicitDefinition ssa |
1130+
ssa.defines(lvalue.(LValue).getDefNode(), lvalue.(VarRef).getVariable()) and
1131+
result = TSsaDefNode(ssa)
11651132
)
11661133
or
1167-
exists(ArrayPattern array |
1168-
value = array.getAnElement() and
1169-
result = TElementPatternNode(array, value)
1170-
)
1134+
result = TDestructuringPatternNode(lvalue)
1135+
or
1136+
result = TUnusedParameterNode(lvalue)
11711137
}
11721138

11731139
/**
@@ -1212,18 +1178,60 @@ module DataFlow {
12121178
any(ImmediatelyInvokedFunctionExpr iife).argumentPassing(parm, arg)
12131179
}
12141180

1181+
/**
1182+
* Holds if there is a step from `pred -> succ` due to an assignment
1183+
* to an expression in l-value position.
1184+
*/
1185+
private predicate lvalueFlowStep(Node pred, Node succ) {
1186+
exists(VarDef def |
1187+
pred = valueNode(defSourceNode(def)) and
1188+
succ = lvalueNode(def.getTarget())
1189+
)
1190+
or
1191+
exists(PropertyPattern pattern |
1192+
pred = TPropNode(pattern) and
1193+
succ = lvalueNode(pattern.getValuePattern())
1194+
)
1195+
or
1196+
exists(Expr element |
1197+
pred = TElementPatternNode(_, element) and
1198+
succ = lvalueNode(element)
1199+
)
1200+
}
1201+
1202+
/**
1203+
* Holds if there is a step from `pred -> succ` from the default
1204+
* value of a destructuring pattern or parameter.
1205+
*/
1206+
private predicate lvalueDefaultFlowStep(Node pred, Node succ) {
1207+
exists(PropertyPattern pattern |
1208+
pred = valueNode(pattern.getDefault()) and
1209+
succ = lvalueNode(pattern.getValuePattern())
1210+
)
1211+
or
1212+
exists(ArrayPattern array, int i |
1213+
pred = valueNode(array.getDefault(i)) and
1214+
succ = lvalueNode(array.getElement(i))
1215+
)
1216+
or
1217+
exists(Parameter param |
1218+
pred = valueNode(param.getDefault()) and
1219+
succ = parameterNode(param)
1220+
)
1221+
}
1222+
12151223
/**
12161224
* Holds if data can flow from `pred` to `succ` in one local step.
12171225
*/
12181226
cached
12191227
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
1228+
// flow from RHS into LHS
1229+
lvalueFlowStep(pred, succ)
1230+
or
1231+
lvalueDefaultFlowStep(pred, succ)
1232+
or
1233+
// Flow through implicit SSA nodes
1234+
exists(SsaImplicitDefinition ssa | succ = TSsaDefNode(ssa) |
12271235
// from any explicit definition or implicit init of a captured variable into
12281236
// the capturing definition
12291237
exists(SsaSourceVariable v, SsaDefinition predDef |
@@ -1270,29 +1278,6 @@ module DataFlow {
12701278
)
12711279
)
12721280
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
12961281
// flow from 'this' parameter into 'this' expressions
12971282
exists(ThisExpr thiz |
12981283
pred = TThisNode(thiz.getBindingContainer()) and
@@ -1323,31 +1308,6 @@ module DataFlow {
13231308
localArgumentPassing(result, def)
13241309
}
13251310

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-
13511311
/**
13521312
* Holds if the flow information for this node is incomplete.
13531313
*

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ module TaintTracking {
232232
exists(ForOfStmt fos |
233233
this = DataFlow::valueNode(fos.getIterationDomain()) and
234234
pred = this and
235-
succ = DataFlow::ssaDefinitionNode(SSA::definition(fos.getIteratorExpr()))
235+
succ = DataFlow::lvalueNode(fos.getLValue())
236236
)
237237
}
238238
}

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)