Skip to content

Commit c3c003b

Browse files
committed
JS: Fix post-update flow into 'this'
1 parent 9fc99d6 commit c3c003b

5 files changed

Lines changed: 104 additions & 17 deletions

File tree

javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowNode.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ private module Cached {
3030
TValueNode(AST::ValueNode nd) or
3131
/** An SSA node from the legacy SSA library */
3232
TSsaDefNode(SsaDefinition d) or
33-
/** Use of a variable with flow from a post-update node (from an earlier use) */
34-
TSsaUseNode(VarUse use) { use.getVariable() instanceof PurelyLocalVariable } or
33+
/** Use of a variable or 'this', with flow from a post-update node (from an earlier use) */
34+
TSsaUseNode(Expr use) { use = any(Ssa2::SsaConfig::SourceVariable v).getAnAccess() } or
3535
/** Phi-read node (new SSA library). Ordinary phi nodes are represented by TSsaDefNode. */
3636
TSsaPhiReadNode(Ssa2::PhiReadNode phi) or
3737
/** Input to a phi node (new SSA library) */

javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
55
private import semmle.javascript.dataflow.internal.AdditionalFlowInternal
66
private import semmle.javascript.dataflow.internal.Contents::Private
77
private import semmle.javascript.dataflow.internal.VariableCapture
8+
private import semmle.javascript.dataflow.internal.VariableOrThis
89
private import semmle.javascript.dataflow.internal.sharedlib.DataFlowImplCommon as DataFlowImplCommon
910
private import semmle.javascript.dataflow.internal.sharedlib.Ssa as Ssa2
1011
private import semmle.javascript.internal.flow_summaries.AllFlowSummaries
@@ -20,18 +21,18 @@ private class Node = DataFlow::Node;
2021
class PostUpdateNode = DataFlow::PostUpdateNode;
2122

2223
class SsaUseNode extends DataFlow::Node, TSsaUseNode {
23-
private VarAccess access;
24+
private Expr expr;
2425

25-
SsaUseNode() { this = TSsaUseNode(access) }
26+
SsaUseNode() { this = TSsaUseNode(expr) }
2627

2728
cached
28-
override string toString() { result = "[ssa-use] " + access.toString() }
29+
override string toString() { result = "[ssa-use] " + expr.toString() }
2930

3031
cached
31-
override StmtContainer getContainer() { result = access.getContainer() }
32+
override StmtContainer getContainer() { result = expr.getContainer() }
3233

3334
cached
34-
override Location getLocation() { result = access.getLocation() }
35+
override Location getLocation() { result = expr.getLocation() }
3536
}
3637

3738
class SsaPhiReadNode extends DataFlow::Node, TSsaPhiReadNode {
@@ -1056,9 +1057,9 @@ predicate knownSourceModel(Node sink, string model) { none() }
10561057
predicate knownSinkModel(Node sink, string model) { none() }
10571058

10581059
private predicate samePhi(SsaPhiNode legacyPhi, Ssa2::PhiNode newPhi) {
1059-
exists(BasicBlock bb, PurelyLocalVariable v |
1060+
exists(BasicBlock bb, LocalVariableOrThis v |
10601061
newPhi.definesAt(v, bb, _) and
1061-
legacyPhi.definesAt(bb, _, v)
1062+
legacyPhi.definesAt(bb, _, v.asLocalVariable())
10621063
)
10631064
}
10641065

@@ -1082,10 +1083,11 @@ private predicate useUseFlow(Node node1, Node node2) {
10821083
exists(Ssa2::DefinitionExt def, Ssa2::Node ssa1, Ssa2::Node ssa2, boolean isUseStep |
10831084
Ssa2::localFlowStep(def, ssa1, ssa2, isUseStep) and
10841085
node1 = getNodeFromSsa2(ssa1) and
1085-
node2 = getNodeFromSsa2(ssa2)
1086+
node2 = getNodeFromSsa2(ssa2) and
1087+
not node1.getTopLevel().isExterns()
10861088
)
10871089
or
1088-
exists(VarUse use |
1090+
exists(Expr use |
10891091
node1 = TSsaUseNode(use) and
10901092
node2 = TValueNode(use)
10911093
)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
private import javascript
2+
3+
cached
4+
private newtype TLocalVariableOrThis =
5+
TLocalVariable(LocalVariable var) or
6+
TThis(StmtContainer container) { not container instanceof ArrowFunctionExpr }
7+
8+
/** A local variable or `this` in a particular container. */
9+
class LocalVariableOrThis extends TLocalVariableOrThis {
10+
/** Gets the local variable represented by this newtype, if any. */
11+
LocalVariable asLocalVariable() { this = TLocalVariable(result) }
12+
13+
/** If this represents `this`, gets the enclosing container */
14+
StmtContainer asThisContainer() { this = TThis(result) }
15+
16+
/** Gets the name of the variable or the string `"this"`. */
17+
string toString() { result = this.getName() }
18+
19+
/** Gets the name of the variable or the string `"this"`. */
20+
string getName() {
21+
result = this.asLocalVariable().getName()
22+
or
23+
this instanceof TThis and result = "this"
24+
}
25+
26+
/** Gets the location of a declaration of this variable, or the declaring container if this is `this`. */
27+
DbLocation getLocation() {
28+
result = this.asLocalVariable().getLocation()
29+
or
30+
result = this.asThisContainer().getLocation()
31+
}
32+
33+
/** Holds if this is a captured variable or captured `this`. */
34+
predicate isCaptured() {
35+
this.asLocalVariable().isCaptured()
36+
or
37+
hasCapturedThis(this.asThisContainer())
38+
}
39+
40+
/** Gets the container declaring this variable or is the enclosing container for `this`. */
41+
StmtContainer getDeclaringContainer() {
42+
result = this.asLocalVariable().getDeclaringContainer()
43+
or
44+
result = this.asThisContainer()
45+
}
46+
47+
/** Gets an access to `this` represented by this value. */
48+
ThisExpr getAThisAccess() { result.getBindingContainer() = this.asThisContainer() }
49+
50+
/** Gets an access to variable or `this`. */
51+
Expr getAnAccess() {
52+
result = this.asLocalVariable().getAnAccess()
53+
or
54+
result = this.getAThisAccess()
55+
}
56+
}
57+
58+
bindingset[c1, c2]
59+
pragma[inline_late]
60+
private predicate sameContainer(StmtContainer c1, StmtContainer c2) { c1 = c2 }
61+
62+
pragma[nomagic]
63+
private predicate hasCapturedThis(StmtContainer c) {
64+
exists(ThisExpr expr |
65+
expr.getBindingContainer() = c and
66+
not sameContainer(c, expr.getContainer())
67+
)
68+
}
69+
70+
module LocalVariableOrThis {
71+
/** Gets the representation of the given local variable. */
72+
LocalVariableOrThis variable(LocalVariable v) { result.asLocalVariable() = v }
73+
74+
/** Gets the representation of `this` in the given container. */
75+
LocalVariableOrThis thisInContainer(StmtContainer c) { result = TThis(c) }
76+
}

javascript/ql/lib/semmle/javascript/dataflow/internal/sharedlib/Ssa.qll

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
private import javascript as js
88
private import codeql.ssa.Ssa
99
private import semmle.javascript.internal.BasicBlockInternal as BasicBlockInternal
10+
private import semmle.javascript.dataflow.internal.VariableOrThis
1011

11-
private module SsaConfig implements InputSig<js::DbLocation> {
12+
module SsaConfig implements InputSig<js::DbLocation> {
1213
class ControlFlowNode = js::ControlFlowNode;
1314

1415
class BasicBlock = js::BasicBlock;
@@ -17,7 +18,9 @@ private module SsaConfig implements InputSig<js::DbLocation> {
1718
ExitBasicBlock() { this.isExitBlock() }
1819
}
1920

20-
class SourceVariable = js::PurelyLocalVariable; // TODO: include 'this' as it is relevant for use-use flow
21+
class SourceVariable extends LocalVariableOrThis {
22+
SourceVariable() { not this.isCaptured() }
23+
}
2124

2225
pragma[nomagic]
2326
private js::EntryBasicBlock getEntryBlock(js::StmtContainer container) {
@@ -27,7 +30,7 @@ private module SsaConfig implements InputSig<js::DbLocation> {
2730
predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) {
2831
certain = true and
2932
(
30-
bb.defAt(i, v, _)
33+
bb.defAt(i, v.asLocalVariable(), _)
3134
or
3235
// Implicit initialization and function parameters
3336
bb = getEntryBlock(v.getDeclaringContainer()) and
@@ -36,7 +39,11 @@ private module SsaConfig implements InputSig<js::DbLocation> {
3639
}
3740

3841
predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) {
39-
bb.useAt(i, v, _) and certain = true
42+
bb.useAt(i, v.asLocalVariable(), _) and certain = true
43+
or
44+
certain = true and
45+
bb.getNode(i) = v.getAThisAccess()
46+
// TODO: also account for: super() and field initialisers
4047
}
4148

4249
predicate getImmediateBasicBlockDominator = BasicBlockInternal::immediateDominator/1;
@@ -48,7 +55,9 @@ private module SsaConfig implements InputSig<js::DbLocation> {
4855
import Make<js::DbLocation, SsaConfig>
4956

5057
private module SsaDataflowInput implements DataFlowIntegrationInputSig {
51-
class Expr extends js::VarUse {
58+
class Expr extends js::Expr {
59+
Expr() { this = any(SsaConfig::SourceVariable v).getAnAccess() }
60+
5261
predicate hasCfgNode(js::BasicBlock bb, int i) { this = bb.getNode(i) }
5362
}
5463

javascript/ql/test/library-tests/TripleDot/useuse.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ function t4() {
4545
class C {
4646
constructor(x) {
4747
this.foo = x;
48-
sink(this.foo); // $ MISSING: hasValueFlow=t4.1
48+
sink(this.foo); // $ hasValueFlow=t4.1
4949
}
5050
}
5151
const c = new C(source('t4.1'));

0 commit comments

Comments
 (0)