Skip to content

Commit bc35f24

Browse files
committed
JS: Generalize access paths to arbitrary root nodes
1 parent 7a7a8b2 commit bc35f24

4 files changed

Lines changed: 198 additions & 45 deletions

File tree

javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll

Lines changed: 138 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55
import javascript
66

77
module GlobalAccessPath {
8+
/**
9+
* A source node that can be the root of an access path.
10+
*/
11+
private class Root extends DataFlow::SourceNode {
12+
Root() {
13+
not this.accessesGlobal(_) and
14+
not this instanceof DataFlow::PropRead and
15+
not this instanceof PropertyProjection and
16+
not this instanceof Closure::ClosureNamespaceAccess and
17+
not this = DataFlow::parameterNode(any(ImmediatelyInvokedFunctionExpr iife).getAParameter())
18+
}
19+
20+
/** Holds if this represents the root of the global access path. */
21+
predicate isGlobal() {
22+
this = DataFlow::globalAccessPathRootPseudoNode()
23+
}
24+
}
25+
826
/**
927
* A local variable with exactly one definition, not counting implicit initialization.
1028
*/
@@ -21,52 +39,56 @@ module GlobalAccessPath {
2139
}
2240

2341
/**
24-
* Gets the global access path referred to by `node`.
42+
* Gets the access path relative to `root` referred to by `node`.
2543
*
2644
* This holds for direct references as well as for aliases
2745
* established through local data flow.
2846
*
2947
* Examples:
3048
* ```
31-
* function f() {
32-
* let v = foo.bar; // reference to 'foo.bar'
33-
* v.baz; // reference to 'foo.bar.baz'
49+
* function f(x) {
50+
* let a = x.f.g; // access path relative to 'x' is '.f.g'
51+
* let b = a.h; // access path relative to 'x' is '.f.g.h'
3452
* }
35-
*
36-
* (function(ns) {
37-
* ns.x; // reference to 'NS.x'
38-
* })(NS = NS || {});
3953
* ```
4054
*/
4155
cached
42-
string fromReference(DataFlow::Node node) {
43-
result = fromReference(node.getImmediatePredecessor())
56+
string fromReference(DataFlow::Node node, Root root) {
57+
root = node and
58+
not root.isGlobal() and
59+
result = ""
60+
or
61+
result = fromReference(node.getImmediatePredecessor(), root)
4462
or
4563
exists(EffectivelyConstantVariable var |
4664
var.isCaptured() and
4765
node.asExpr() = var.getAnAccess() and
48-
result = fromReference(var.getValue())
66+
result = fromReference(var.getValue(), root)
4967
)
5068
or
5169
node.accessesGlobal(result) and
52-
result != "undefined"
70+
result != "undefined" and
71+
root.isGlobal()
5372
or
5473
not node.accessesGlobal(_) and
5574
exists(DataFlow::PropRead prop | node = prop |
56-
result = fromReference(prop.getBase()) + "." + prop.getPropertyName()
75+
result = fromReference(prop.getBase(), root) + "." + prop.getPropertyName()
5776
)
5877
or
59-
exists(Closure::ClosureNamespaceAccess acc | node = acc | result = acc.getClosureNamespace())
78+
exists(Closure::ClosureNamespaceAccess acc | node = acc |
79+
result = acc.getClosureNamespace() and
80+
root.isGlobal()
81+
)
6082
or
6183
exists(PropertyProjection proj | node = proj |
6284
proj.isSingletonProjection() and
63-
result = fromReference(proj.getObject()) + "." + proj.getASelector()
85+
result = fromReference(proj.getObject(), root) + "." + proj.getASelector()
6486
)
6587
or
6688
// Treat 'e || {}' as having the same name as 'e'
6789
exists(LogOrExpr e | node.asExpr() = e |
6890
e.getRightOperand().(ObjectExpr).getNumProperty() = 0 and
69-
result = fromReference(e.getLeftOperand().flow())
91+
result = fromReference(e.getLeftOperand().flow(), root)
7092
)
7193
or
7294
// Treat 'e && e.f' as having the same name as 'e.f'
@@ -84,10 +106,33 @@ module GlobalAccessPath {
84106
rhs.getBase().(PropAccess).getQualifiedName() = name
85107
)
86108
) and
87-
result = fromReference(rhs.flow())
109+
result = fromReference(rhs.flow(), root)
88110
)
89111
}
90112

113+
/**
114+
* Gets the global access path referred to by `node`.
115+
*
116+
* This holds for direct references as well as for aliases
117+
* established through local data flow.
118+
*
119+
* Examples:
120+
* ```
121+
* function f() {
122+
* let v = foo.bar; // reference to 'foo.bar'
123+
* v.baz; // reference to 'foo.bar.baz'
124+
* }
125+
*
126+
* (function(ns) {
127+
* ns.x; // reference to 'NS.x'
128+
* })(NS = NS || {});
129+
* ```
130+
*/
131+
cached
132+
string fromReference(DataFlow::Node node) {
133+
result = fromReference(node, DataFlow::globalAccessPathRootPseudoNode())
134+
}
135+
91136
/**
92137
* Holds if `rhs` is the right-hand side of a self-assignment.
93138
*
@@ -96,90 +141,143 @@ module GlobalAccessPath {
96141
* foo = foo || {};
97142
* ```
98143
*/
99-
private predicate isSelfAssignment(DataFlow::Node rhs) { fromRhs(rhs) = fromReference(rhs) }
144+
private predicate isSelfAssignment(DataFlow::Node rhs) {
145+
fromRhs(rhs, DataFlow::globalAccessPathRootPseudoNode()) = fromReference(rhs, DataFlow::globalAccessPathRootPseudoNode())
146+
}
100147

101148
/**
102149
* Holds if there is an assignment to `accessPath` in `file`, not counting
103150
* self-assignments.
104151
*/
105152
private predicate isAssignedInFile(string accessPath, File file) {
106153
exists(DataFlow::Node rhs |
107-
fromRhs(rhs) = accessPath and
154+
fromRhs(rhs, DataFlow::globalAccessPathRootPseudoNode()) = accessPath and
108155
not isSelfAssignment(rhs) and
109156
// Note: Avoid unneeded materialization of DataFlow::Node.getFile()
110157
rhs.getAstNode().getFile() = file
111158
)
112159
}
113160

114161
/**
115-
* Holds if `accessPath` is only assigned to from one file, not counting
162+
* Holds if the global `accessPath` is only assigned to from one file, not counting
116163
* self-assignments.
117164
*/
118165
predicate isAssignedInUniqueFile(string accessPath) {
119166
strictcount(File f | isAssignedInFile(accessPath, f)) = 1
120167
}
121168

122169
/**
123-
* Gets the global access path `node` is being assigned to, if any.
170+
* Gets the access path relative to `root`, which `node` is being assigned to, if any.
124171
*
125172
* Only holds for the immediate right-hand side of an assignment or property, not
126173
* for nodes that transitively flow there.
127174
*
128-
* For example, the class nodes below all map to `foo.bar`:
175+
* For example, the class nodes below all map to `.foo.bar` relative to `x`:
129176
* ```
130-
* foo.bar = class {};
131-
*
132-
* foo = { bar: class {} };
133-
*
134-
* (function(f) {
135-
* f.bar = class {}
136-
* })(foo = foo || {});
177+
* function f(x) {
178+
* x.foo.bar = class {};
179+
* x.foo = { bar: class() };
180+
* let alias = x;
181+
* alias.foo.bar = class {};
182+
* }
137183
* ```
138184
*/
139185
cached
140-
string fromRhs(DataFlow::Node node) {
186+
string fromRhs(DataFlow::Node node, Root root) {
141187
exists(DataFlow::SourceNode base, string baseName, string name |
142188
node = base.getAPropertyWrite(name).getRhs() and
143189
result = baseName + "." + name
144190
|
145-
baseName = fromReference(base)
191+
baseName = fromReference(base, root)
146192
or
147-
baseName = fromRhs(base)
193+
baseName = fromRhs(base, root)
148194
)
149195
or
150196
exists(GlobalVariable var |
151197
node = var.getAnAssignedExpr().flow() and
152-
result = var.getName()
198+
result = var.getName() and
199+
root.isGlobal()
153200
)
154201
or
155202
exists(FunctionDeclStmt fun |
156203
node = DataFlow::valueNode(fun) and
157-
result = fun.getId().(GlobalVarDecl).getName()
204+
result = fun.getId().(GlobalVarDecl).getName() and
205+
root.isGlobal()
158206
)
159207
or
160208
exists(ClassDeclStmt cls |
161209
node = DataFlow::valueNode(cls) and
162-
result = cls.getIdentifier().(GlobalVarDecl).getName()
210+
result = cls.getIdentifier().(GlobalVarDecl).getName() and
211+
root.isGlobal()
163212
)
164213
or
165214
exists(EnumDeclaration decl |
166215
node = DataFlow::valueNode(decl) and
167-
result = decl.getIdentifier().(GlobalVarDecl).getName()
216+
result = decl.getIdentifier().(GlobalVarDecl).getName() and
217+
root.isGlobal()
168218
)
169219
or
170220
exists(NamespaceDeclaration decl |
171221
node = DataFlow::valueNode(decl) and
172-
result = decl.getId().(GlobalVarDecl).getName()
222+
result = decl.getId().(GlobalVarDecl).getName() and
223+
root.isGlobal()
173224
)
174225
}
175226

227+
/**
228+
* Gets the global access path `node` is being assigned to, if any.
229+
*
230+
* Only holds for the immediate right-hand side of an assignment or property, not
231+
* for nodes that transitively flow there.
232+
*
233+
* For example, the class nodes below all map to `foo.bar`:
234+
* ```
235+
* foo.bar = class {};
236+
*
237+
* foo = { bar: class {} };
238+
*
239+
* (function(f) {
240+
* f.bar = class {}
241+
* })(foo = foo || {});
242+
* ```
243+
*/
244+
cached
245+
string fromRhs(DataFlow::Node node) {
246+
result = fromRhs(node, DataFlow::globalAccessPathRootPseudoNode())
247+
}
248+
249+
/**
250+
* Gets the access path relative to `root` referenced by or assigned to `node`.
251+
*/
252+
string getAccessPath(DataFlow::Node node, Root root) {
253+
result = fromReference(node, root)
254+
or
255+
not exists(fromReference(node, root)) and
256+
result = fromRhs(node, root)
257+
}
258+
176259
/**
177260
* Gets the global access path referenced by or assigned to `node`.
178261
*/
179262
string getAccessPath(DataFlow::Node node) {
180-
result = fromReference(node)
263+
result = getAccessPath(node, DataFlow::globalAccessPathRootPseudoNode())
264+
}
265+
266+
/**
267+
* Holds if there is a step from `pred` to `succ` through an assignment to an access path.
268+
*/
269+
pragma[inline]
270+
predicate step(DataFlow::Node pred, DataFlow::Node succ) {
271+
exists(string name, Root root |
272+
name = fromRhs(pred, root) and
273+
name = fromReference(succ, root) and
274+
not root.isGlobal()
275+
)
181276
or
182-
not exists(fromReference(node)) and
183-
result = fromRhs(node)
277+
exists(string name |
278+
name = fromRhs(pred) and
279+
name = fromReference(succ) and
280+
isAssignedInUniqueFile(name)
281+
)
184282
}
185283
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ private class PropertyName extends string {
1414
this = any(DataFlow::PropRef pr).getPropertyName()
1515
or
1616
GlobalAccessPath::isAssignedInUniqueFile(this)
17+
or
18+
this = GlobalAccessPath::fromRhs(_, _) and
19+
this != ""
1720
}
1821
}
1922

@@ -95,20 +98,37 @@ module StepSummary {
9598
any(AdditionalTypeTrackingStep st).step(pred, succ) and
9699
summary = LevelStep()
97100
or
101+
// Store to global access path
98102
exists(string name |
99103
name = GlobalAccessPath::fromRhs(pred) and
100104
GlobalAccessPath::isAssignedInUniqueFile(name) and
101105
succ = DataFlow::globalAccessPathRootPseudoNode() and
102106
summary = StoreStep(name)
103107
)
104108
or
109+
// Load from global access path
105110
exists(string name |
106111
name = GlobalAccessPath::fromReference(succ) and
107112
GlobalAccessPath::isAssignedInUniqueFile(name) and
108113
pred = DataFlow::globalAccessPathRootPseudoNode() and
109114
summary = LoadStep(name)
110115
)
111116
or
117+
// Store to non-global access path
118+
exists(string name |
119+
name = GlobalAccessPath::fromRhs(pred, succ) and
120+
succ != DataFlow::globalAccessPathRootPseudoNode() and
121+
summary = StoreStep(name)
122+
)
123+
or
124+
// Load from non-global access path
125+
exists(string name |
126+
name = GlobalAccessPath::fromReference(succ, pred) and
127+
pred != DataFlow::globalAccessPathRootPseudoNode() and
128+
summary = LoadStep(name) and
129+
name != ""
130+
)
131+
or
112132
// Summarize calls with flow directly from a parameter to a return.
113133
exists(DataFlow::ParameterNode param, DataFlow::FunctionNode fun |
114134
(

javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,7 @@ module CallGraph {
4343
or
4444
imprecision = 0 and
4545
t.start() and
46-
exists(string name |
47-
GlobalAccessPath::isAssignedInUniqueFile(name) and
48-
GlobalAccessPath::fromRhs(function) = name and
49-
GlobalAccessPath::fromReference(result) = name
50-
)
46+
GlobalAccessPath::step(function, result)
5147
or
5248
imprecision = 0 and
5349
exists(DataFlow::ClassNode cls |

0 commit comments

Comments
 (0)