Skip to content

Commit c31ccbc

Browse files
authored
Merge pull request #925 from asger-semmle/closure-reorg
Approved by xiemaisi
2 parents 2ad0ac2 + 1aba111 commit c31ccbc

File tree

3 files changed

+129
-75
lines changed

3 files changed

+129
-75
lines changed

javascript/ql/src/semmle/javascript/Closure.qll

Lines changed: 123 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -6,103 +6,142 @@ import javascript
66

77
module Closure {
88
/**
9-
* A call to a function in the `goog` namespace such as `goog.provide` or `goog.load`.
9+
* A reference to a Closure namespace.
1010
*/
11-
class GoogFunctionCall extends CallExpr {
12-
GoogFunctionCall() {
13-
exists(GlobalVariable gv | gv.getName() = "goog" |
14-
this.getCallee().(DotExpr).getBase() = gv.getAnAccess()
15-
)
16-
}
11+
class ClosureNamespaceRef extends DataFlow::Node {
12+
ClosureNamespaceRef::Range range;
13+
14+
ClosureNamespaceRef() { this = range }
1715

18-
/** Gets the name of the invoked function. */
19-
string getFunctionName() { result = getCallee().(DotExpr).getPropertyName() }
16+
/**
17+
* Gets the namespace being referenced.
18+
*/
19+
string getClosureNamespace() { result = range.getClosureNamespace() }
20+
}
21+
22+
module ClosureNamespaceRef {
23+
/**
24+
* A reference to a Closure namespace.
25+
*
26+
* Can be subclassed to classify additional nodes as namespace references.
27+
*/
28+
abstract class Range extends DataFlow::Node {
29+
/**
30+
* Gets the namespace being referenced.
31+
*/
32+
abstract string getClosureNamespace();
33+
}
2034
}
2135

2236
/**
23-
* An expression statement consisting of a call to a function
24-
* in the `goog` namespace.
37+
* A data flow node that returns the value of a closure namespace.
2538
*/
26-
class GoogFunctionCallStmt extends ExprStmt {
27-
GoogFunctionCallStmt() { super.getExpr() instanceof GoogFunctionCall }
28-
29-
override GoogFunctionCall getExpr() { result = super.getExpr() }
39+
class ClosureNamespaceAccess extends ClosureNamespaceRef {
40+
override ClosureNamespaceAccess::Range range;
41+
}
3042

31-
/** Gets the name of the invoked function. */
32-
string getFunctionName() { result = getExpr().getFunctionName() }
43+
module ClosureNamespaceAccess {
44+
/**
45+
* A data flow node that returns the value of a closure namespace.
46+
*
47+
* Can be subclassed to classify additional nodes as namespace accesses.
48+
*/
49+
abstract class Range extends ClosureNamespaceRef::Range { }
50+
}
3351

34-
/** Gets the `i`th argument to the invoked function. */
35-
Expr getArgument(int i) { result = getExpr().getArgument(i) }
52+
/**
53+
* A call to a method on the `goog.` namespace, as a closure reference.
54+
*/
55+
abstract private class DefaultNamespaceRef extends DataFlow::MethodCallNode,
56+
ClosureNamespaceRef::Range {
57+
DefaultNamespaceRef() { this = DataFlow::globalVarRef("goog").getAMethodCall() }
3658

37-
/** Gets an argument to the invoked function. */
38-
Expr getAnArgument() { result = getArgument(_) }
59+
override string getClosureNamespace() { result = getArgument(0).asExpr().getStringValue() }
3960
}
4061

41-
abstract private class GoogNamespaceRef extends ExprOrStmt {
42-
abstract string getNamespaceId();
62+
/**
63+
* Holds if `node` is the data flow node corresponding to the expression in
64+
* a top-level expression statement.
65+
*/
66+
private predicate isTopLevelExpr(DataFlow::Node node) {
67+
node.getTopLevel().getAChildStmt().(ExprStmt).getExpr().flow() = node
4368
}
4469

4570
/**
46-
* A call to `goog.provide`.
71+
* A top-level call to `goog.provide`.
4772
*/
48-
class GoogProvide extends GoogFunctionCallStmt, GoogNamespaceRef {
49-
GoogProvide() { getFunctionName() = "provide" }
73+
private class DefaultClosureProvideCall extends DefaultNamespaceRef {
74+
DefaultClosureProvideCall() {
75+
getMethodName() = "provide" and
76+
isTopLevelExpr(this)
77+
}
78+
}
5079

51-
/** Gets the identifier of the namespace created by this call. */
52-
override string getNamespaceId() { result = getArgument(0).getStringValue() }
80+
/**
81+
* A top-level call to `goog.provide`.
82+
*/
83+
class ClosureProvideCall extends ClosureNamespaceRef, DataFlow::MethodCallNode {
84+
override DefaultClosureProvideCall range;
5385
}
5486

5587
/**
5688
* A call to `goog.require`.
5789
*/
58-
class GoogRequire extends GoogFunctionCall, GoogNamespaceRef {
59-
GoogRequire() { getFunctionName() = "require" }
60-
61-
/** Gets the identifier of the namespace imported by this call. */
62-
override string getNamespaceId() { result = getArgument(0).getStringValue() }
90+
private class DefaultClosureRequireCall extends DefaultNamespaceRef, ClosureNamespaceAccess::Range {
91+
DefaultClosureRequireCall() { getMethodName() = "require" }
6392
}
6493

65-
private class GoogRequireImport extends GoogRequire, Import {
66-
/** Gets the module in which this import appears. */
67-
override Module getEnclosingModule() { result = getTopLevel() }
68-
69-
/** Gets the (unresolved) path that this import refers to. */
70-
override PathExpr getImportedPath() { result = getArgument(0) }
94+
/**
95+
* A call to `goog.require`.
96+
*/
97+
class ClosureRequireCall extends ClosureNamespaceAccess, DataFlow::MethodCallNode {
98+
override DefaultClosureRequireCall range;
7199
}
72100

73101
/**
74-
* A call to `goog.module` or `goog.declareModuleId`.
102+
* A top-level call to `goog.module` or `goog.declareModuleId`.
75103
*/
76-
class GoogModuleDeclaration extends GoogFunctionCallStmt, GoogNamespaceRef {
77-
GoogModuleDeclaration() {
78-
getFunctionName() = "module" or
79-
getFunctionName() = "declareModuleId"
104+
private class DefaultClosureModuleDeclaration extends DefaultNamespaceRef {
105+
DefaultClosureModuleDeclaration() {
106+
(getMethodName() = "module" or getMethodName() = "declareModuleId") and
107+
isTopLevelExpr(this)
80108
}
109+
}
81110

82-
/** Gets the identifier of the namespace imported by this call. */
83-
override string getNamespaceId() { result = getArgument(0).getStringValue() }
111+
/**
112+
* A top-level call to `goog.module` or `goog.declareModuleId`.
113+
*/
114+
class ClosureModuleDeclaration extends ClosureNamespaceRef, DataFlow::MethodCallNode {
115+
override DefaultClosureModuleDeclaration range;
84116
}
85117

86118
/**
87119
* A module using the Closure module system, declared using `goog.module()` or `goog.declareModuleId()`.
88120
*/
89121
class ClosureModule extends Module {
90-
ClosureModule() { getAChildStmt() instanceof GoogModuleDeclaration }
122+
ClosureModule() {
123+
// Use AST-based predicate to cut recursive dependencies.
124+
exists(MethodCallExpr call |
125+
getAStmt().(ExprStmt).getExpr() = call and
126+
call.getReceiver().(GlobalVarAccess).getName() = "goog" and
127+
(call.getMethodName() = "module" or call.getMethodName() = "declareModuleId")
128+
)
129+
}
91130

92131
/**
93132
* Gets the call to `goog.module` or `goog.declareModuleId` in this module.
94133
*/
95-
GoogModuleDeclaration getModuleDeclaration() { result = getAChildStmt() }
134+
ClosureModuleDeclaration getModuleDeclaration() { result.getTopLevel() = this }
96135

97136
/**
98137
* Gets the namespace of this module.
99138
*/
100-
string getNamespaceId() { result = getModuleDeclaration().getNamespaceId() }
139+
string getClosureNamespace() { result = getModuleDeclaration().getClosureNamespace() }
101140

102141
override Module getAnImportedModule() {
103-
exists(GoogRequireImport imprt |
104-
imprt.getEnclosingModule() = this and
105-
result.(ClosureModule).getNamespaceId() = imprt.getNamespaceId()
142+
exists(ClosureRequireCall imprt |
143+
imprt.getTopLevel() = this and
144+
result.(ClosureModule).getClosureNamespace() = imprt.getClosureNamespace()
106145
)
107146
}
108147

@@ -115,7 +154,7 @@ module Closure {
115154
* Has no result for ES6 modules using `goog.declareModuleId`.
116155
*/
117156
Variable getExportsVariable() {
118-
getModuleDeclaration().getFunctionName() = "module" and
157+
getModuleDeclaration().getMethodName() = "module" and
119158
result = getScope().getVariable("exports")
120159
}
121160

@@ -139,42 +178,52 @@ module Closure {
139178
class ClosureScript extends TopLevel {
140179
ClosureScript() {
141180
not this instanceof ClosureModule and
142-
getAChildStmt() instanceof GoogProvide
143-
or
144-
getAChildStmt().(ExprStmt).getExpr() instanceof GoogRequire
181+
(
182+
any(ClosureProvideCall provide).getTopLevel() = this
183+
or
184+
any(ClosureRequireCall require).getTopLevel() = this
185+
)
145186
}
146187

147188
/** Gets the identifier of a namespace required by this module. */
148189
string getARequiredNamespace() {
149-
result = getAChildStmt().(ExprStmt).getExpr().(GoogRequire).getNamespaceId()
190+
exists(ClosureRequireCall require |
191+
require.getTopLevel() = this and
192+
result = require.getClosureNamespace()
193+
)
150194
}
151195

152196
/** Gets the identifer of a namespace provided by this module. */
153-
string getAProvidedNamespace() { result = getAChildStmt().(GoogProvide).getNamespaceId() }
197+
string getAProvidedNamespace() {
198+
exists(ClosureProvideCall require |
199+
require.getTopLevel() = this and
200+
result = require.getClosureNamespace()
201+
)
202+
}
154203
}
155204

156205
/**
157206
* Holds if `name` is a closure namespace, including proper namespace prefixes.
158207
*/
159208
pragma[noinline]
160-
predicate isLibraryNamespacePath(string name) {
161-
exists(string namespace | namespace = any(GoogNamespaceRef provide).getNamespaceId() |
209+
predicate isClosureNamespace(string name) {
210+
exists(string namespace | namespace = any(ClosureNamespaceRef ref).getClosureNamespace() |
162211
name = namespace.substring(0, namespace.indexOf("."))
163212
or
164213
name = namespace
165214
)
166215
}
167216

168217
/**
169-
* Gets the closure namespace path addressed by the given dataflow node, if any.
218+
* Gets the closure namespace path addressed by the given data flow node, if any.
170219
*/
171-
string getLibraryAccessPath(DataFlow::SourceNode node) {
172-
isLibraryNamespacePath(result) and
220+
string getClosureNamespaceFromSourceNode(DataFlow::SourceNode node) {
221+
isClosureNamespace(result) and
173222
node = DataFlow::globalVarRef(result)
174223
or
175224
exists(DataFlow::SourceNode base, string basePath, string prop |
176-
basePath = getLibraryAccessPath(base) and
177-
isLibraryNamespacePath(basePath) and
225+
basePath = getClosureNamespaceFromSourceNode(base) and
226+
isClosureNamespace(basePath) and
178227
node = base.getAPropertyRead(prop) and
179228
result = basePath + "." + prop
180229
)
@@ -184,21 +233,24 @@ module Closure {
184233
// foo.bar = { baz() {} }
185234
exists(DataFlow::PropWrite write |
186235
node = write.getRhs() and
187-
result = getWrittenLibraryAccessPath(write)
236+
result = getWrittenClosureNamespace(write)
188237
)
189238
or
190-
result = node.asExpr().(GoogRequire).getNamespaceId()
239+
result = node.(ClosureNamespaceAccess).getClosureNamespace()
191240
}
192241

193242
/**
194243
* Gets the closure namespace path written to by the given property write, if any.
195244
*/
196-
string getWrittenLibraryAccessPath(DataFlow::PropWrite node) {
197-
result = getLibraryAccessPath(node.getBase().getALocalSource()) + "." + node.getPropertyName()
245+
string getWrittenClosureNamespace(DataFlow::PropWrite node) {
246+
result = getClosureNamespaceFromSourceNode(node.getBase().getALocalSource()) + "." +
247+
node.getPropertyName()
198248
}
199249

200250
/**
201-
* Gets a dataflow node that refers to the given value exported from a Closure module.
251+
* Gets a data flow node that refers to the given value exported from a Closure module.
202252
*/
203-
DataFlow::SourceNode moduleImport(string moduleName) { getLibraryAccessPath(result) = moduleName }
253+
DataFlow::SourceNode moduleImport(string moduleName) {
254+
getClosureNamespaceFromSourceNode(result) = moduleName
255+
}
204256
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1113,7 +1113,7 @@ module DataFlow {
11131113
or
11141114
exists(GlobalVarAccess va |
11151115
nd = valueNode(va.(VarUse)) and
1116-
if Closure::isLibraryNamespacePath(va.getName()) then cause = "heap" else cause = "global"
1116+
if Closure::isClosureNamespace(va.getName()) then cause = "heap" else cause = "global"
11171117
)
11181118
or
11191119
exists(Expr e | e = nd.asExpr() and cause = "call" |

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,11 +377,13 @@ private class AnalyzedClosureExportAssign extends AnalyzedPropertyWrite, DataFlo
377377
private class AnalyzedClosureGlobalAccessPath extends AnalyzedNode, AnalyzedPropertyRead {
378378
string accessPath;
379379

380-
AnalyzedClosureGlobalAccessPath() { accessPath = Closure::getLibraryAccessPath(this) }
380+
AnalyzedClosureGlobalAccessPath() {
381+
accessPath = Closure::getClosureNamespaceFromSourceNode(this)
382+
}
381383

382384
override AnalyzedNode localFlowPred() {
383385
exists(DataFlow::PropWrite write |
384-
Closure::getWrittenLibraryAccessPath(write) = accessPath and
386+
Closure::getWrittenClosureNamespace(write) = accessPath and
385387
result = write.getRhs()
386388
)
387389
or
@@ -390,7 +392,7 @@ private class AnalyzedClosureGlobalAccessPath extends AnalyzedNode, AnalyzedProp
390392

391393
override predicate reads(AbstractValue base, string propName) {
392394
exists(Closure::ClosureModule mod |
393-
mod.getNamespaceId() = accessPath and
395+
mod.getClosureNamespace() = accessPath and
394396
base = TAbstractModuleObject(mod) and
395397
propName = "exports"
396398
)

0 commit comments

Comments
 (0)