Skip to content

Commit be10f24

Browse files
committed
JS: make moduleImport() work for named imports
1 parent c133362 commit be10f24

13 files changed

+122
-8
lines changed

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ module DataFlow {
3737
TThisNode(StmtContainer f) { f.(Function).getThisBinder() = f or f instanceof TopLevel } or
3838
TUnusedParameterNode(SimpleParameter p) {
3939
not exists(SsaExplicitDefinition ssa | p = ssa.getDef())
40+
} or
41+
TDestructuredModuleImportNode(ImportDeclaration decl) {
42+
decl.getASpecifier() instanceof NamedImportSpecifier
4043
}
4144

4245
/**
@@ -342,6 +345,28 @@ module DataFlow {
342345
override string toString() { result = "reflective call" }
343346
}
344347

348+
/**
349+
* A node referring to the module imported at a named ES2015 import declaration.
350+
*
351+
* Default imports and namespace imports do not fall into this category, as the
352+
* SSA definition of the local variable is used as the source of the module instead.
353+
*/
354+
private class DestructuredModuleImportNode extends Node, TDestructuredModuleImportNode {
355+
ImportDeclaration imprt;
356+
357+
DestructuredModuleImportNode() { this = TDestructuredModuleImportNode(imprt) }
358+
359+
override BasicBlock getBasicBlock() { result = imprt.getBasicBlock() }
360+
361+
override predicate hasLocationInfo(
362+
string filepath, int startline, int startcolumn, int endline, int endcolumn
363+
) {
364+
imprt.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
365+
}
366+
367+
override string toString() { result = imprt.toString() }
368+
}
369+
345370
/**
346371
* A data flow node that reads or writes an object property or class member.
347372
*
@@ -372,6 +397,12 @@ module DataFlow {
372397
*/
373398
abstract string getPropertyName();
374399

400+
/**
401+
* Holds if this creates an alias for the property, as opposed to
402+
* just being a read or write of the property.
403+
*/
404+
predicate isES2015PropertyBinding() { none() }
405+
375406
/**
376407
* Holds if this data flow node accesses property `p` on base node `base`.
377408
*/
@@ -659,6 +690,31 @@ module DataFlow {
659690
override string getPropertyName() { none() }
660691
}
661692

693+
/**
694+
* A named import specifier seen as a property read on the imported module.
695+
*/
696+
private class NamedImportSpecifierAsPropRead extends PropRead {
697+
ImportDeclaration imprt;
698+
699+
NamedImportSpecifier spec;
700+
701+
NamedImportSpecifierAsPropRead() {
702+
spec = imprt.getASpecifier() and
703+
exists(SsaExplicitDefinition ssa |
704+
ssa.getDef() = spec and
705+
this = TSsaDefNode(ssa)
706+
)
707+
}
708+
709+
override Node getBase() { result = TDestructuredModuleImportNode(imprt) }
710+
711+
override Expr getPropertyNameExpr() { result = spec.getImported() }
712+
713+
override string getPropertyName() { result = spec.getImportedName() }
714+
715+
override predicate isES2015PropertyBinding() { any() }
716+
}
717+
662718
/**
663719
* A data flow node representing an unused parameter.
664720
*
@@ -875,6 +931,16 @@ module DataFlow {
875931
*/
876932
DataFlow::ThisNode thisNode(StmtContainer container) { result = TThisNode(container) }
877933

934+
/**
935+
* INTERNAL. DO NOT USE.
936+
*
937+
* Gets the data flow node holding the reference to the module being destructured at
938+
* the given import declaration.
939+
*/
940+
DataFlow::Node destructuredModuleImportNode(ImportDeclaration imprt) {
941+
result = TDestructuredModuleImportNode(imprt)
942+
}
943+
878944
/**
879945
* A classification of flows that are not modeled, or only modeled incompletely, by
880946
* `DataFlowNode`:

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

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,12 @@ class ModuleImportNode extends DataFlow::SourceNode {
427427
is.getImportedName() = "default"
428428
)
429429
or
430+
// `import { createServer } from 'http'`
431+
exists(ImportDeclaration id |
432+
this = DataFlow::destructuredModuleImportNode(id) and
433+
id.getImportedPath().getValue() = path
434+
)
435+
or
430436
// declared AMD dependency
431437
exists(AMDModuleDefinition amd |
432438
this = DataFlow::parameterNode(amd.getDependencyParameter(path))
@@ -456,14 +462,6 @@ ModuleImportNode moduleImport(string path) { result.getPath() = path }
456462
*/
457463
DataFlow::SourceNode moduleMember(string path, string m) {
458464
result = moduleImport(path).getAPropertyRead(m)
459-
or
460-
exists(ImportDeclaration id, ImportSpecifier is, SsaExplicitDefinition ssa |
461-
id.getImportedPath().getValue() = path and
462-
is = id.getASpecifier() and
463-
is.getImportedName() = m and
464-
ssa.getDef() = is and
465-
result = DataFlow::ssaDefinitionNode(ssa)
466-
)
467465
}
468466

469467
/**

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ module SourceNode {
207207
this instanceof DataFlow::Impl::InvokeNodeDef
208208
or
209209
DataFlow::thisNode(this, _)
210+
or
211+
this = DataFlow::destructuredModuleImportNode(_)
210212
}
211213
}
212214
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,25 @@ private class AnalyzedNamespaceImport extends AnalyzedImport {
175175
}
176176
}
177177

178+
/**
179+
* Flow analysis for namespace imports.
180+
*/
181+
private class AnalyzedDestructuredImport extends AnalyzedPropertyRead {
182+
Module imported;
183+
184+
AnalyzedDestructuredImport() {
185+
exists(ImportDeclaration id |
186+
this = DataFlow::destructuredModuleImportNode(id) and
187+
imported = id.getImportedModule()
188+
)
189+
}
190+
191+
override predicate reads(AbstractValue base, string propName) {
192+
base = TAbstractModuleObject(imported) and
193+
propName = "exports"
194+
}
195+
}
196+
178197
/**
179198
* Flow analysis for `require` calls, interpreted as an implicit read of
180199
* the `module.exports` property of the imported module.

javascript/ql/test/library-tests/DataFlow/sources.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
| sources.js:3:2:5:1 | functio ... x+19;\\n} |
1313
| sources.js:3:11:3:11 | x |
1414
| tst.js:1:1:1:0 | this |
15+
| tst.js:1:1:1:24 | import ... m 'fs'; |
1516
| tst.js:1:10:1:11 | fs |
1617
| tst.js:16:1:20:9 | (functi ... ("arg") |
1718
| tst.js:16:2:16:1 | this |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
| destructuringES6.js:1:1:1:41 | import ... ctron'; | destructuringES6.js:2:1:2:19 | new BrowserWindow() |
12
| destructuringRequire.js:1:27:1:45 | require('electron') | destructuringRequire.js:2:1:2:19 | new BrowserWindow() |
23
| moduleUses.js:1:11:1:24 | require('mod') | moduleUses.js:9:1:9:7 | new K() |

javascript/ql/test/library-tests/ModuleImportNodes/ModuleImportNode_getAMemberInvocation.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
| amd1.js:1:25:1:26 | fs | amd1.js:2:3:2:29 | fs.read ... a.txt") |
22
| amd2.js:2:12:2:24 | require('fs') | amd2.js:3:3:3:29 | fs.read ... a.txt") |
3+
| destructuringES6.js:1:1:1:41 | import ... ctron'; | destructuringES6.js:2:1:2:19 | new BrowserWindow() |
34
| destructuringRequire.js:1:27:1:45 | require('electron') | destructuringRequire.js:2:1:2:19 | new BrowserWindow() |
45
| moduleUses.js:1:11:1:24 | require('mod') | moduleUses.js:3:1:3:18 | mod.moduleMethod() |
56
| moduleUses.js:1:11:1:24 | require('mod') | moduleUses.js:6:1:6:3 | f() |

javascript/ql/test/library-tests/ModuleImportNodes/ModuleImportNode_getAPropertyRead.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
| amd1.js:1:25:1:26 | fs | amd1.js:2:3:2:17 | fs.readFileSync |
22
| amd2.js:2:12:2:24 | require('fs') | amd2.js:3:3:3:17 | fs.readFileSync |
3+
| destructuringES6.js:1:1:1:41 | import ... ctron'; | destructuringES6.js:1:10:1:22 | BrowserWindow |
34
| destructuringRequire.js:1:27:1:45 | require('electron') | destructuringRequire.js:1:9:1:21 | BrowserWindow |
45
| moduleUses.js:1:11:1:24 | require('mod') | moduleUses.js:3:1:3:16 | mod.moduleMethod |
56
| moduleUses.js:1:11:1:24 | require('mod') | moduleUses.js:5:9:5:26 | mod.moduleFunction |

javascript/ql/test/library-tests/ModuleImportNodes/ModuleImportNode_getPath.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
| amd1.js:1:25:1:26 | fs | fs |
22
| amd2.js:2:12:2:24 | require('fs') | fs |
3+
| destructuringES6.js:1:1:1:41 | import ... ctron'; | electron |
34
| destructuringRequire.js:1:27:1:45 | require('electron') | electron |
45
| instanceThroughDefaultImport.js:1:8:1:42 | myDefaultImportedModuleInstanceName | myDefaultImportedModuleInstance |
56
| instanceThroughNamespaceImport.js:1:8:1:49 | myNamespaceImportedModuleInstanceName | myNamespaceImportedModuleInstance |
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
| electron | destructuringES6.js:1:1:1:41 | import ... ctron'; |
2+
| electron | destructuringRequire.js:1:27:1:45 | require('electron') |
3+
| fs | amd1.js:1:25:1:26 | fs |
4+
| fs | amd2.js:2:12:2:24 | require('fs') |
5+
| mod | moduleUses.js:1:11:1:24 | require('mod') |
6+
| myDefaultImportedModuleInstance | instanceThroughDefaultImport.js:1:8:1:42 | myDefaultImportedModuleInstanceName |
7+
| myNamespaceImportedModuleInstance | instanceThroughNamespaceImport.js:1:8:1:49 | myNamespaceImportedModuleInstanceName |
8+
| myRequiredModuleInstance | instanceThroughRequire.js:1:36:1:70 | require ... tance') |

0 commit comments

Comments
 (0)