-
Notifications
You must be signed in to change notification settings - Fork 1
/
Modules.qll
305 lines (277 loc) · 9.73 KB
/
Modules.qll
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
/**
* Provides classes for working with JavaScript modules, both
* ECMAScript 2015-style modules, and the older CommonJS and AMD-style
* modules.
*/
import javascript
/**
* A module, which may either be an ECMAScript 2015-style module,
* a CommonJS module, or an AMD module.
*/
abstract class Module extends TopLevel {
/** Gets the full path of the file containing this module. */
string getPath() { result = getFile().getAbsolutePath() }
/** Gets the short name of this module without file extension. */
string getName() { result = getFile().getStem() }
/** Gets an import appearing in this module. */
Import getAnImport() { result.getTopLevel() = this }
/** Gets a module from which this module imports. */
Module getAnImportedModule() { result = getAnImport().getImportedModule() }
/** Gets a symbol exported by this module. */
string getAnExportedSymbol() { exists(getAnExportedValue(result)) }
/**
* Holds if this module explicitly exports symbol `name` at the
* program element `export`.
*
* Note that in some module systems (notably CommonJS and AMD)
* modules are arbitrary objects that export all their
* properties. This predicate only considers properties
* that are explicitly defined on the module object.
*
* Symbols defined in another module that are re-exported by
* this module are only sometimes considered.
*/
deprecated predicate exports(string name, ASTNode export) {
this instanceof AmdModule and
exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() |
pwn.getBase().analyze().getAValue() = this.(AmdModule).getDefine().getAModuleExportsValue() and
name = pwn.getPropertyName()
)
or
this instanceof Closure::ClosureModule and
exists(DataFlow::PropWrite write, Expr base |
write.getAstNode() = export and
write.writes(base.flow(), name, _) and
(
base = this.(Closure::ClosureModule).getExportsVariable().getAReference()
or
base = this.(Closure::ClosureModule).getExportsVariable().getAnAssignedExpr()
)
)
or
this instanceof NodeModule and
(
// a property write whose base is `exports` or `module.exports`
exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() |
pwn.getBase() = this.(NodeModule).getAModuleExportsNode() and
name = pwn.getPropertyName()
)
or
// a re-export using spread-operator. E.g. `const foo = require("./foo"); module.exports = {bar: bar, ...foo};`
exists(ObjectExpr obj | obj = this.(NodeModule).getAModuleExportsNode().asExpr() |
obj
.getAProperty()
.(SpreadProperty)
.getInit()
.(SpreadElement)
.getOperand()
.flow()
.getALocalSource()
.asExpr()
.(Import)
.getImportedModule()
.exports(name, export)
)
or
// an externs definition (where appropriate)
exists(PropAccess pacc | export = pacc |
pacc.getBase() = this.(NodeModule).getAModuleExportsNode().asExpr() and
name = pacc.getPropertyName() and
isExterns() and
exists(pacc.getDocumentation())
)
)
or
this instanceof ES2015Module and
exists(ExportDeclaration ed | ed = this.(ES2015Module).getAnExport() and ed = export |
ed.exportsAs(_, name)
)
}
/**
* Get a value that is explicitly exported from this module with under `name`.
*
* Note that in some module systems (notably CommonJS and AMD)
* modules are arbitrary objects that export all their
* properties. This predicate only considers properties
* that are explicitly defined on the module object.
*
* Symbols defined in another module that are re-exported by
* this module are only sometimes considered.
*/
abstract DataFlow::Node getAnExportedValue(string name);
/**
* Gets the root folder relative to which the given import path (which must
* appear in this module) is resolved.
*
* Each root has an associated priority, and roots with numerically smaller
* priority are preferred during import resolution.
*
* This predicate is not part of the public API, it is only exposed to allow
* overriding by subclasses.
*/
predicate searchRoot(PathExpr path, Folder searchRoot, int priority) {
path.getEnclosingModule() = this and
priority = 0 and
exists(string v | v = path.getValue() |
// paths starting with a dot are resolved relative to the module's directory
if v.matches(".%")
then searchRoot = getFile().getParentContainer()
else
// all other paths are resolved relative to the file system root
searchRoot.getBaseName() = ""
)
}
/**
* Gets the file to which the import path `path`, which must appear in this
* module, resolves.
*
* If the path resolves to a file directly, this is the result. If the path
* resolves to a folder containing a main module (such as `index.js`), then
* that file is the result.
*/
File resolve(PathExpr path) {
path.getEnclosingModule() = this and
(
// handle the case where the import path is complete
exists(Container c | c = path.resolve() |
// import may refer to a file...
result = c
or
// ...or to a directory, in which case we import index.js in that directory
result = c.(Folder).getJavaScriptFile("index")
)
or
// handle the case where the import path is missing the extension
exists(Folder f | f = path.resolveUpTo(path.getNumComponent() - 1) |
result = f.getJavaScriptFile(path.getBaseName())
or
// If a js file was not found look for a file that compiles to js
path.getExtension() = ".js" and
not exists(f.getJavaScriptFile(path.getBaseName())) and
result = f.getJavaScriptFile(path.getStem())
)
)
}
}
class InstalledNPMPackageFolder extends Folder {
string pkgName;
PackageJSON pkgJson;
InstalledNPMPackageFolder() {
exists( Folder npmF | npmF.getBaseName() = "node_modules"
and npmF.getFolder( pkgName) = this
and this.getFile("package.json") = pkgJson.getFile()
)
}
Folder getNodeModulesFolder() {
result = this.getParentContainer()
}
string getPackageName() {
result = pkgName
}
File getModuleFile() {
result = this.getFile(pkgJson.getPropStringValue("main"))
or
(not exists(pkgJson.getPropStringValue("main"))) and result = this.getFile("index.js")
}
override string toString() {
result = "node_modules: " + pkgName
}
}
/**
* An import in a module, which may be an ECMAScript 2015-style
* `import` statement, a CommonJS-style `require` import, or an AMD dependency.
*/
abstract class Import extends ASTNode {
/** Gets the module in which this import appears. */
abstract Module getEnclosingModule();
/** Gets the (unresolved) path that this import refers to. */
abstract PathExpr getImportedPath();
/**
* Gets an externs module the path of this import resolves to.
*
* Any externs module whose name exactly matches the imported
* path is assumed to be a possible target of the import.
*/
Module resolveExternsImport() {
result.isExterns() and result.getName() = getImportedPath().getValue()
}
/**
* Gets the module the path of this import resolves to.
*/
Module resolveImportedPath() {
result.getFile() = getEnclosingModule().resolve(getImportedPath())
}
/**
* Gets a module with a `@providesModule` JSDoc tag that matches
* the imported path.
*/
private Module resolveAsProvidedModule() {
exists(JSDocTag tag |
tag.getTitle() = "providesModule" and
tag.getParent().getComment().getTopLevel() = result and
tag.getDescription().trim() = getImportedPath().getValue()
)
}
/**
* Gets a module in a `node_modules/@types/` folder that matches the imported module name.
*/
private Module resolveFromTypeRoot() {
result.getFile() =
min(TypeRootFolder typeRoot |
|
typeRoot.getModuleFile(getImportedPath().getValue())
order by
typeRoot.getSearchPriority(getFile().getParentContainer())
)
}
private Module resolveFromInstalledNPMPackage() {
exists( InstalledNPMPackageFolder inpm |
result.getFile() = inpm.getModuleFile() and
this.getImportedPath().getValue() = inpm.getPackageName()
)
}
/**
* Gets the imported module, as determined by the TypeScript compiler, if any.
*/
private Module resolveFromTypeScriptSymbol() {
exists(CanonicalName symbol |
ast_node_symbol(this, symbol) and
ast_node_symbol(result, symbol)
)
}
/**
* Gets the module this import refers to.
*
* The result is either an externs module, or an actual source module;
* in cases of ambiguity, the former are preferred. This models the runtime
* behavior of Node.js imports, which prefer core modules such as `fs` over any
* source module of the same name.
*/
Module getImportedModule() {
if exists(resolveExternsImport())
then result = resolveExternsImport()
else (
result = resolveAsProvidedModule() or
result = resolveImportedPath() or
result = resolveFromTypeRoot() or
result = resolveFromTypeScriptSymbol() or
result = resolveFromInstalledNPMPackage()
)
}
/**
* Gets the data flow node that the default import of this import is available at.
*/
abstract DataFlow::Node getImportedModuleNode();
}
/**
* DEPRECATED. Use `PathExpr` instead.
*
* A path expression that appears in a module and is resolved relative to it.
*/
abstract deprecated class PathExprInModule extends PathExpr {
PathExprInModule() {
this.(Expr).getTopLevel() instanceof Module
or
this.(Comment).getTopLevel() instanceof Module
}
}