Skip to content

Commit b880a60

Browse files
author
Max Schaefer
authored
Merge pull request #363 from xiemaisi/js/destructuring-assignment-cfg
JavaScript: Improve handling of destructuring assignments.
2 parents c577f6d + 38534a6 commit b880a60

File tree

10 files changed

+94
-27
lines changed

10 files changed

+94
-27
lines changed

javascript/ql/src/semmle/javascript/BasicBlocks.qll

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,27 @@ private cached module Internal {
6767
}
6868

6969
cached predicate defAt(BasicBlock bb, int i, Variable v, VarDef d) {
70-
v = d.getAVariable() and bbIndex(bb, d, i)
70+
exists (VarRef lhs |
71+
lhs = d.getTarget().(BindingPattern).getABindingVarRef() and
72+
v = lhs.getVariable() |
73+
lhs = d.getTarget() and
74+
bbIndex(bb, d, i)
75+
or
76+
exists (PropertyPattern pp |
77+
lhs = pp.getValuePattern() and
78+
bbIndex(bb, pp, i)
79+
)
80+
or
81+
exists (ObjectPattern op |
82+
lhs = op.getRest() and
83+
bbIndex(bb, lhs, i)
84+
)
85+
or
86+
exists (ArrayPattern ap |
87+
lhs = ap.getAnElement() and
88+
bbIndex(bb, lhs, i)
89+
)
90+
)
7191
}
7292

7393
cached predicate reachableBB(BasicBlock bb) {

javascript/ql/src/semmle/javascript/DefUse.qll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ private predicate lvalAux(Expr l, ControlFlowNode def) {
9898
exists (ArrayPattern ap | lvalAux(ap, def) | l = ap.getAnElement().stripParens())
9999
or
100100
exists (ObjectPattern op | lvalAux(op, def) |
101-
l = op.getAPropertyPattern().getValuePattern().stripParens()
101+
l = op.getAPropertyPattern().getValuePattern().stripParens() or
102+
l = op.getRest().stripParens()
102103
)
103104
}
104105

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

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -122,17 +122,25 @@ private cached module Internal {
122122
}
123123
or TPhi(ReachableJoinBlock bb, SsaSourceVariable v) {
124124
liveAtEntry(bb, v) and
125-
exists (ReachableBasicBlock defbb, SsaDefinition def |
126-
def.definesAt(defbb, _, v) and
127-
bb.inDominanceFrontierOf(defbb)
128-
)
125+
inDefDominanceFrontier(bb, v)
129126
}
130127
or TRefinement(ReachableBasicBlock bb, int i, GuardControlFlowNode guard, SsaSourceVariable v) {
131128
bb.getNode(i) = guard and
132129
guard.getTest().(Refinement).getRefinedVar() = v and
133130
liveAtEntry(bb, v)
134131
}
135132

133+
/**
134+
* Holds if `bb` is in the dominance frontier of a block containing a definition of `v`.
135+
*/
136+
pragma[noinline]
137+
private predicate inDefDominanceFrontier(ReachableJoinBlock bb, SsaSourceVariable v) {
138+
exists (ReachableBasicBlock defbb, SsaDefinition def |
139+
def.definesAt(defbb, _, v) and
140+
bb.inDominanceFrontierOf(defbb)
141+
)
142+
}
143+
136144
/**
137145
* Holds if `v` is a captured variable which is declared in `declContainer` and read in
138146
* `useContainer`.
@@ -216,6 +224,13 @@ private cached module Internal {
216224
ref(bb, i, v, tp)
217225
}
218226

227+
/**
228+
* Gets the maximum rank among all references to `v` in basic block `bb`.
229+
*/
230+
private int maxRefRank(ReachableBasicBlock bb, SsaSourceVariable v) {
231+
result = max(refRank(bb, _, v, _))
232+
}
233+
219234
/**
220235
* Holds if variable `v` is live after the `i`th node of basic block `bb`, where
221236
* `i` is the index of a node that may assign or capture `v`.
@@ -230,8 +245,8 @@ private cached module Internal {
230245
or
231246
// this is the last reference to `v` inside `bb`, but `v` is live at entry
232247
// to a successor basic block of `bb`
233-
r = max(refRank(bb, _, v, _)) and
234-
liveAtEntry(bb.getASuccessor(), v)
248+
r = maxRefRank(bb, v) and
249+
liveAtSuccEntry(bb, v)
235250
)
236251
}
237252

@@ -248,6 +263,13 @@ private cached module Internal {
248263
// there is no reference to `v` inside `bb`, but `v` is live at entry
249264
// to a successor basic block of `bb`
250265
not exists(refRank(bb, _, v, _)) and
266+
liveAtSuccEntry(bb, v)
267+
}
268+
269+
/**
270+
* Holds if `v` is live at the beginning of any successor of basic block `bb`.
271+
*/
272+
private predicate liveAtSuccEntry(ReachableBasicBlock bb, SsaSourceVariable v) {
251273
liveAtEntry(bb.getASuccessor(), v)
252274
}
253275

@@ -311,25 +333,32 @@ private cached module Internal {
311333
)
312334
}
313335

336+
/**
337+
* Gets an SSA definition of `v` that reaches the end of the immediate dominator of `bb`.
338+
*/
339+
pragma[noinline]
340+
private SsaDefinition getDefReachingEndOfImmediateDominator(ReachableBasicBlock bb, SsaSourceVariable v) {
341+
result = getDefReachingEndOf(bb.getImmediateDominator(), v)
342+
}
343+
314344
/**
315345
* Gets an SSA definition of `v` that reaches the end of basic block `bb`.
316346
*/
317347
cached SsaDefinition getDefReachingEndOf(ReachableBasicBlock bb, SsaSourceVariable v) {
318-
bb.getASuccessor().localIsLiveAtEntry(v) and
319-
(
320-
exists (int lastRef | lastRef = max(int i | ssaRef(bb, i, v, _)) |
321-
result = getLocalDefinition(bb, lastRef, v)
322-
or
323-
result.definesAt(bb, lastRef, v)
324-
)
348+
exists (int lastRef | lastRef = max(int i | ssaRef(bb, i, v, _)) |
349+
result = getLocalDefinition(bb, lastRef, v)
325350
or
326-
/* In SSA form, the (unique) reaching definition of a use is the closest
327-
* definition that dominates the use. If two definitions dominate a node
328-
* then one must dominate the other, so we can find the reaching definition
329-
* by following the idominance relation backwards. */
330-
result = getDefReachingEndOf(bb.getImmediateDominator(), v) and
331-
not exists (SsaDefinition ssa | ssa.definesAt(bb, _, v))
351+
result.definesAt(bb, lastRef, v) and
352+
liveAtSuccEntry(bb, v)
332353
)
354+
or
355+
/* In SSA form, the (unique) reaching definition of a use is the closest
356+
* definition that dominates the use. If two definitions dominate a node
357+
* then one must dominate the other, so we can find the reaching definition
358+
* by following the idominance relation backwards. */
359+
result = getDefReachingEndOfImmediateDominator(bb, v) and
360+
not exists (SsaDefinition ssa | ssa.definesAt(bb, _, v)) and
361+
liveAtSuccEntry(bb, v)
333362
}
334363

335364
/**

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -579,20 +579,20 @@
579579
| destructuring | 3 | o | 4 | p |
580580
| destructuring | 4 | p | 5 | {\\n v ... = p;\\n} |
581581
| destructuring | 5 | {\\n v ... = p;\\n} | 6 | var\\n ... } = o; |
582-
| destructuring | 6 | var\\n ... } = o; | 7 | {\\n ... \\n } |
582+
| destructuring | 6 | var\\n ... } = o; | 10 | o |
583583
| destructuring | 7 | {\\n ... } = o | 11 | var\\n ... ] = p; |
584584
| destructuring | 7 | {\\n ... \\n } | 8 | x |
585585
| destructuring | 8 | x | 8 | x |
586586
| destructuring | 8 | x | 9 | y |
587+
| destructuring | 9 | y | 7 | {\\n ... } = o |
587588
| destructuring | 9 | y | 9 | y |
588-
| destructuring | 9 | y | 10 | o |
589-
| destructuring | 10 | o | 7 | {\\n ... } = o |
590-
| destructuring | 11 | var\\n ... ] = p; | 12 | [\\n ... \\n ] |
589+
| destructuring | 10 | o | 7 | {\\n ... \\n } |
590+
| destructuring | 11 | var\\n ... ] = p; | 16 | p |
591591
| destructuring | 12 | [\\n ... ] = p | 17 | exit node of functio ... = p;\\n} |
592592
| destructuring | 12 | [\\n ... \\n ] | 13 | a |
593593
| destructuring | 13 | a | 15 | c |
594-
| destructuring | 15 | c | 16 | p |
595-
| destructuring | 16 | p | 12 | [\\n ... ] = p |
594+
| destructuring | 15 | c | 12 | [\\n ... ] = p |
595+
| destructuring | 16 | p | 12 | [\\n ... \\n ] |
596596
| enum | 1 | 'value' | 1 | a = 'value' |
597597
| enum | 1 | E | 1 | a |
598598
| enum | 1 | a | 1 | 'value' |

javascript/ql/test/library-tests/Flow/AbstractValues.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
| d.js:1:1:4:0 | exports object of module d |
6060
| d.js:1:1:4:0 | module object of module d |
6161
| d.js:1:18:3:1 | object literal |
62+
| destructuring.js:1:1:4:1 | function f |
63+
| destructuring.js:1:1:4:1 | instance of function f |
6264
| e.js:1:1:6:0 | exports object of module e |
6365
| e.js:1:1:6:0 | module object of module e |
6466
| es2015.js:1:1:50:0 | exports object of module es2015 |

javascript/ql/test/library-tests/Flow/abseval.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
| classAccessors.js:11:9:11:11 | myY | classAccessors.js:11:15:11:20 | this.y | file://:0:0:0:0 | indefinite value (heap) |
6969
| classAccessors.js:12:9:12:11 | myZ | classAccessors.js:12:15:12:20 | this.z | file://:0:0:0:0 | indefinite value (call) |
7070
| classAccessors.js:12:9:12:11 | myZ | classAccessors.js:12:15:12:20 | this.z | file://:0:0:0:0 | indefinite value (heap) |
71+
| destructuring.js:2:7:2:24 | { x, y = (z = x) } | destructuring.js:2:28:2:28 | o | file://:0:0:0:0 | indefinite value (call) |
72+
| destructuring.js:3:7:3:8 | z1 | destructuring.js:3:12:3:12 | z | file://:0:0:0:0 | indefinite value (heap) |
7173
| es2015.js:1:5:1:7 | Sup | es2015.js:1:11:6:1 | class { ... ;\\n }\\n} | es2015.js:1:11:6:1 | class Sup |
7274
| es2015.js:4:9:4:12 | ctor | es2015.js:4:16:4:25 | new.target | file://:0:0:0:0 | indefinite value (call) |
7375
| es2015.js:19:7:19:11 | _args | es2015.js:19:15:19:18 | args | file://:0:0:0:0 | object |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function f(o) {
2+
var { x, y = (z = x) } = o, z;
3+
var z1 = z;
4+
}

javascript/ql/test/library-tests/Flow/types.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
| classAccessors.js:10:9:10:11 | myX | classAccessors.js:10:15:10:20 | this.x | boolean, class, date, function, null, number, object, regular expression,string or undefined |
3939
| classAccessors.js:11:9:11:11 | myY | classAccessors.js:11:15:11:20 | this.y | boolean, class, date, function, null, number, object, regular expression,string or undefined |
4040
| classAccessors.js:12:9:12:11 | myZ | classAccessors.js:12:15:12:20 | this.z | boolean, class, date, function, null, number, object, regular expression,string or undefined |
41+
| destructuring.js:2:7:2:24 | { x, y = (z = x) } | destructuring.js:2:28:2:28 | o | boolean, class, date, function, null, number, object, regular expression,string or undefined |
42+
| destructuring.js:3:7:3:8 | z1 | destructuring.js:3:12:3:12 | z | boolean, class, date, function, null, number, object, regular expression,string or undefined |
4143
| es2015.js:1:5:1:7 | Sup | es2015.js:1:11:6:1 | class { ... ;\\n }\\n} | class |
4244
| es2015.js:4:9:4:12 | ctor | es2015.js:4:16:4:25 | new.target | boolean, class, date, function, null, number, object, regular expression,string or undefined |
4345
| es2015.js:19:7:19:11 | _args | es2015.js:19:15:19:18 | args | object |

javascript/ql/test/query-tests/Declarations/MissingVarDecl/MissingVarDecl.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
| test.js:54:10:54:10 | z | Variable z is used like a local variable, but is missing a declaration. |
55
| test.js:60:6:60:6 | y | Variable y is used like a local variable, but is missing a declaration. |
66
| test.js:66:2:66:2 | z | Variable z is used like a local variable, but is missing a declaration. |
7+
| tst3.js:7:10:7:10 | x | Variable x is used like a local variable, but is missing a declaration. |
8+
| tst3.js:7:16:7:19 | rest | Variable rest is used like a local variable, but is missing a declaration. |

javascript/ql/test/query-tests/Declarations/MissingVarDecl/tst3.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ function sc_alert(i) {
22
for(;i;) ;
33
foo;
44
}
5+
6+
function f(o) {
7+
for({x, ...rest} of o)
8+
console.log(x in rest);
9+
}

0 commit comments

Comments
 (0)