Skip to content

Commit 014d4b9

Browse files
authored
Merge pull request #934 from asger-semmle/module-import
Approved by xiemaisi
2 parents c34fdda + dfe3f25 commit 014d4b9

15 files changed

+119
-8
lines changed

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

Lines changed: 56 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+
exists(decl.getASpecifier().getImportedName())
4043
}
4144

4245
/**
@@ -351,6 +354,25 @@ module DataFlow {
351354
override string toString() { result = "reflective call" }
352355
}
353356

357+
/**
358+
* A node referring to the module imported at a named or default ES2015 import declaration.
359+
*/
360+
private class DestructuredModuleImportNode extends Node, TDestructuredModuleImportNode {
361+
ImportDeclaration imprt;
362+
363+
DestructuredModuleImportNode() { this = TDestructuredModuleImportNode(imprt) }
364+
365+
override BasicBlock getBasicBlock() { result = imprt.getBasicBlock() }
366+
367+
override predicate hasLocationInfo(
368+
string filepath, int startline, int startcolumn, int endline, int endcolumn
369+
) {
370+
imprt.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
371+
}
372+
373+
override string toString() { result = imprt.toString() }
374+
}
375+
354376
/**
355377
* A data flow node that reads or writes an object property or class member.
356378
*
@@ -668,6 +690,30 @@ module DataFlow {
668690
override string getPropertyName() { none() }
669691
}
670692

693+
/**
694+
* A named import specifier seen as a property read on the imported module.
695+
*/
696+
private class ImportSpecifierAsPropRead extends PropRead {
697+
ImportDeclaration imprt;
698+
699+
ImportSpecifier spec;
700+
701+
ImportSpecifierAsPropRead() {
702+
spec = imprt.getASpecifier() and
703+
exists(spec.getImportedName()) and
704+
exists(SsaExplicitDefinition ssa |
705+
ssa.getDef() = spec and
706+
this = TSsaDefNode(ssa)
707+
)
708+
}
709+
710+
override Node getBase() { result = TDestructuredModuleImportNode(imprt) }
711+
712+
override Expr getPropertyNameExpr() { result = spec.getImported() }
713+
714+
override string getPropertyName() { result = spec.getImportedName() }
715+
}
716+
671717
/**
672718
* A data flow node representing an unused parameter.
673719
*
@@ -886,6 +932,16 @@ module DataFlow {
886932
*/
887933
DataFlow::ThisNode thisNode(StmtContainer container) { result = TThisNode(container) }
888934

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

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

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,12 @@ class ModuleImportNode extends DataFlow::SourceNode {
429429
is.getImportedName() = "default"
430430
)
431431
or
432+
// `import { createServer } from 'http'`
433+
exists(ImportDeclaration id |
434+
this = DataFlow::destructuredModuleImportNode(id) and
435+
id.getImportedPath().getValue() = path
436+
)
437+
or
432438
// declared AMD dependency
433439
exists(AMDModuleDefinition amd |
434440
this = DataFlow::parameterNode(amd.getDependencyParameter(path))
@@ -458,14 +464,6 @@ ModuleImportNode moduleImport(string path) { result.getPath() = path }
458464
*/
459465
DataFlow::SourceNode moduleMember(string path, string m) {
460466
result = moduleImport(path).getAPropertyRead(m)
461-
or
462-
exists(ImportDeclaration id, ImportSpecifier is, SsaExplicitDefinition ssa |
463-
id.getImportedPath().getValue() = path and
464-
is = id.getASpecifier() and
465-
is.getImportedName() = m and
466-
ssa.getDef() = is and
467-
result = DataFlow::ssaDefinitionNode(ssa)
468-
)
469467
}
470468

471469
/**

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 |

javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
| global.js:5:22:5:35 | "also tainted" | global.js:9:13:9:22 | g(source1) |
1919
| global.js:5:22:5:35 | "also tainted" | global.js:10:13:10:22 | g(source2) |
2020
| nodeJsLib.js:1:15:1:23 | "tainted" | esClient.js:7:13:7:18 | nj.foo |
21+
| nodeJsLib.js:1:15:1:23 | "tainted" | esClient.js:10:13:10:17 | njFoo |
2122
| nodeJsLib.js:1:15:1:23 | "tainted" | nodeJsClient.js:4:13:4:18 | nj.foo |
2223
| nodeJsLib.js:2:15:2:23 | "tainted" | esClient.js:7:13:7:18 | nj.foo |
2324
| nodeJsLib.js:2:15:2:23 | "tainted" | esClient.js:10:13:10:17 | njFoo |
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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
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 |
5+
| instanceThroughDefaultImport.js:1:1:1:82 | import ... tance'; | instanceThroughDefaultImport.js:1:8:1:42 | myDefaultImportedModuleInstanceName |
46
| moduleUses.js:1:11:1:24 | require('mod') | moduleUses.js:3:1:3:16 | mod.moduleMethod |
57
| moduleUses.js:1:11:1:24 | require('mod') | moduleUses.js:5:9:5:26 | mod.moduleFunction |
68
| moduleUses.js:1:11:1:24 | require('mod') | moduleUses.js:8:9:8:31 | mod.con ... unction |

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
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 |
5+
| instanceThroughDefaultImport.js:1:1:1:82 | import ... tance'; | myDefaultImportedModuleInstance |
46
| instanceThroughDefaultImport.js:1:8:1:42 | myDefaultImportedModuleInstanceName | myDefaultImportedModuleInstance |
57
| instanceThroughNamespaceImport.js:1:8:1:49 | myNamespaceImportedModuleInstanceName | myNamespaceImportedModuleInstance |
68
| instanceThroughRequire.js:1:36:1:70 | require ... tance') | myRequiredModuleInstance |

0 commit comments

Comments
 (0)