Skip to content

Commit 45eff3d

Browse files
authored
Merge pull request #20399 from asgerf/js/default-interop2
JS: Refactor handling of ambiguous default imports
2 parents 78bfdfd + 7a2391f commit 45eff3d

File tree

26 files changed

+53
-2882
lines changed

26 files changed

+53
-2882
lines changed

javascript/ql/lib/semmle/javascript/ApiGraphs.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,7 @@ module API {
822822
or
823823
// special case: from `require('m')` to an export of `prop` in `m`
824824
exists(Import imp, Module m, string prop |
825-
pred = imp.getImportedModuleNode() and
825+
pred = imp.getImportedModuleNodeStrict() and
826826
m = imp.getImportedModule() and
827827
lbl = Label::member(prop) and
828828
rhs = m.getAnExportedValue(prop)
@@ -1337,7 +1337,7 @@ module API {
13371337
result = nd.getALocalSource()
13381338
or
13391339
// additional backwards step from `require('m')` to `exports` or `module.exports` in m
1340-
exists(Import imp | imp.getImportedModuleNode() = trackDefNode(nd, t.continue()) |
1340+
exists(Import imp | imp.getImportedModuleNodeStrict() = trackDefNode(nd, t.continue()) |
13411341
result = DataFlow::exportsVarNode(imp.getImportedModule())
13421342
or
13431343
result = DataFlow::moduleVarNode(imp.getImportedModule()).getAPropertyRead("exports")

javascript/ql/lib/semmle/javascript/ES2015Modules.qll

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,17 +137,26 @@ class ImportDeclaration extends Stmt, Import, @import_declaration {
137137
is instanceof ImportNamespaceSpecifier and
138138
count(this.getASpecifier()) = 1
139139
or
140-
// For compatibility with the non-standard implementation of default imports,
141-
// treat default imports as namespace imports in cases where it can't cause ambiguity
142-
// between named exports and the properties of a default-exported object.
143-
not this.getImportedModule().(ES2015Module).hasBothNamedAndDefaultExports() and
144-
is.getImportedName() = "default"
140+
result = this.getAmbiguousDefaultImportNode()
145141
)
146142
or
147143
// `import { createServer } from 'http'`
148144
result = DataFlow::destructuredModuleImportNode(this)
149145
}
150146

147+
/**
148+
* Gets the data flow node corresponding to the `foo` in `import foo from "somewhere"`.
149+
*
150+
* This refers to the default import, but some non-standard compilers will treat it as a namespace
151+
* import. In order to support both interpretations, it is considered an "ambiguous default import".
152+
*
153+
* Note that renamed default imports, such as `import { default as foo } from "somewhere"`,
154+
* are not considered ambiguous, and will not be reported by this predicate.
155+
*/
156+
DataFlow::Node getAmbiguousDefaultImportNode() {
157+
result = DataFlow::valueNode(this.getASpecifier().(ImportDefaultSpecifier))
158+
}
159+
151160
/** Holds if this is declared with the `type` keyword, so it only imports types. */
152161
predicate isTypeOnly() { has_type_keyword(this) }
153162

javascript/ql/lib/semmle/javascript/Modules.qll

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,42 @@ abstract class Import extends AstNode {
179179
}
180180

181181
/**
182-
* Gets the data flow node that the default import of this import is available at.
182+
* Gets the data flow node corresponding to the imported module.
183+
*
184+
* For example:
185+
* ```js
186+
* // ES2015 style
187+
* import * as foo from "fs"; // Gets the node for `foo`
188+
* import { readSync } from "fs"; // Gets a node representing the destructured import
189+
*
190+
* // CommonJS style
191+
* require("fs"); // Gets the return value
192+
*
193+
* // AMD style
194+
* define(["fs"], function(fs) { // Gets the node for the `fs` parameter
195+
* });
196+
* ```
197+
*
198+
* For default imports, this gets two nodes: the default import node, and a node representing the imported module:
199+
* ```js
200+
* import foo from "fs"; // gets both `foo` and a node representing the imported module
201+
* ```
202+
* This behaviour is to support non-standard compilers that treat default imports
203+
* as namespace imports. Use `getImportedModuleNodeStrict()` to avoid this behaviour in cases
204+
* where it would cause ambiguous data flow.
183205
*/
184206
abstract DataFlow::Node getImportedModuleNode();
207+
208+
/**
209+
* Gets the same as `getImportedModuleNode()` except ambiguous default imports are excluded
210+
* in cases where it would cause ambiguity between named exports and properties
211+
* of a default export.
212+
*/
213+
final DataFlow::Node getImportedModuleNodeStrict() {
214+
result = this.getImportedModuleNode() and
215+
not (
216+
result = this.(ImportDeclaration).getAmbiguousDefaultImportNode() and
217+
this.getImportedModule().(ES2015Module).hasBothNamedAndDefaultExports()
218+
)
219+
}
185220
}

javascript/ql/lib/semmle/javascript/dataflow/Portals.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*
1212
* The API of this library is not stable yet and may change.
1313
*/
14+
deprecated module;
1415

1516
import javascript
1617

javascript/ql/test/library-tests/Portals/PortalEntry.expected

Lines changed: 0 additions & 1715 deletions
This file was deleted.

javascript/ql/test/library-tests/Portals/PortalEntry.ql

Lines changed: 0 additions & 5 deletions
This file was deleted.

javascript/ql/test/library-tests/Portals/PortalExit.expected

Lines changed: 0 additions & 1060 deletions
This file was deleted.

javascript/ql/test/library-tests/Portals/PortalExit.ql

Lines changed: 0 additions & 5 deletions
This file was deleted.

javascript/ql/test/library-tests/Portals/src/bluebird/index.js

Lines changed: 0 additions & 9 deletions
This file was deleted.

javascript/ql/test/library-tests/Portals/src/bluebird/package.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)