diff --git a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java index c52dd5c1c20..ddba9c50157 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java @@ -306,7 +306,7 @@ public class ASTExtractor { public V(Platform platform, SourceType sourceType) { this.platform = platform; this.sourceType = sourceType; - this.isStrict = sourceType == SourceType.MODULE; + this.isStrict = sourceType == SourceType.ES6_MODULE || sourceType == SourceType.CLOSURE_MODULE; } private Label visit(INode child, Label parent, int childIndex) { @@ -557,10 +557,12 @@ public class ASTExtractor { if (!".mjs".equals(locationManager.getSourceFileExtension())) scopeManager.addVariables("require", "module", "exports", "__filename", "__dirname", "arguments"); trapwriter.addTuple("isModule", toplevelLabel); - } else if (sourceType == SourceType.MODULE) { + } else if (sourceType == SourceType.ES6_MODULE || sourceType == SourceType.CLOSURE_MODULE) { Label moduleScopeKey = trapwriter.globalID("module;{" + locationManager.getFileLabel() + "}," + locationManager.getStartLine() + "," + locationManager.getStartColumn()); scopeManager.enterScope(3, moduleScopeKey, toplevelLabel); - scopeManager.addVariables("exports"); // needed for Closure modules - spuriously added for ES6 modules + if (sourceType == SourceType.CLOSURE_MODULE) { + scopeManager.addVariables("exports"); + } trapwriter.addTuple("isModule", toplevelLabel); } @@ -571,7 +573,7 @@ public class ASTExtractor { visitAll(nd.getBody(), toplevelLabel); // if we're extracting a Node.js/ES2015 module, leave its scope - if (platform == Platform.NODE || sourceType == SourceType.MODULE) + if (platform == Platform.NODE || sourceType == SourceType.ES6_MODULE || sourceType == SourceType.CLOSURE_MODULE) scopeManager.leaveScope(); contextManager.leaveContainer(); diff --git a/javascript/extractor/src/com/semmle/js/extractor/ExtractorConfig.java b/javascript/extractor/src/com/semmle/js/extractor/ExtractorConfig.java index dfb031021f3..f7e0e46b184 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/ExtractorConfig.java +++ b/javascript/extractor/src/com/semmle/js/extractor/ExtractorConfig.java @@ -51,7 +51,7 @@ public class ExtractorConfig { }; public static enum SourceType { - SCRIPT, MODULE, AUTO; + SCRIPT, ES6_MODULE, CLOSURE_MODULE, AUTO; @Override public String toString() { diff --git a/javascript/extractor/src/com/semmle/js/extractor/HTMLExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/HTMLExtractor.java index 5d47fff222d..f7413a7383b 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/HTMLExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/HTMLExtractor.java @@ -140,14 +140,14 @@ public class HTMLExtractor implements IExtractor { if ("text/babel".equals(scriptType)) { String plugins = getAttributeValueLC(script, "data-plugins"); if (plugins != null && plugins.contains("transform-es2015-modules-umd")) { - return SourceType.MODULE; + return SourceType.ES6_MODULE; } return config.getSourceType(); } // if `type` is "module", extract as module if ("module".equals(scriptType)) - return SourceType.MODULE; + return SourceType.ES6_MODULE; return null; } diff --git a/javascript/extractor/src/com/semmle/js/extractor/JSExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/JSExtractor.java index 7f4861e544d..f00eefa04fc 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/JSExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/JSExtractor.java @@ -70,8 +70,9 @@ public class JSExtractor { return sourceType; if (config.getEcmaVersion().compareTo(ECMAVersion.ECMA2015) >= 0) { Matcher m = containsModuleIndicator.matcher(source); - if (m.find() && (allowLeadingWS || m.group(1).isEmpty())) - return SourceType.MODULE; + if (m.find() && (allowLeadingWS || m.group(1).isEmpty())) { + return m.group(2).startsWith("goog") ? SourceType.CLOSURE_MODULE : SourceType.ES6_MODULE; + } } return SourceType.SCRIPT; } @@ -125,7 +126,7 @@ public class JSExtractor { if (config.isExterns()) textualExtractor.getTrapwriter().addTuple("isExterns", toplevelLabel); - if (platform == Platform.NODE && sourceType != SourceType.MODULE) + if (platform == Platform.NODE && sourceType != SourceType.ES6_MODULE && sourceType != SourceType.CLOSURE_MODULE) textualExtractor.getTrapwriter().addTuple("isNodejs", toplevelLabel); return Pair.make(toplevelLabel, loc); diff --git a/javascript/extractor/src/com/semmle/js/extractor/ScriptExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/ScriptExtractor.java index d526a8fae2d..d90e83fbea1 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/ScriptExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/ScriptExtractor.java @@ -52,7 +52,7 @@ public class ScriptExtractor implements IExtractor { // Some file extensions are interpreted as modules by default. if (isAlwaysModule(locationManager.getSourceFileExtension())) { if (config.getSourceType() == SourceType.AUTO) - config = config.withSourceType(SourceType.MODULE); + config = config.withSourceType(SourceType.ES6_MODULE); } ScopeManager scopeManager = new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion()); diff --git a/javascript/ql/test/library-tests/Modules/GlobalVariableRef.expected b/javascript/ql/test/library-tests/Modules/GlobalVariableRef.expected new file mode 100644 index 00000000000..90cf72071ae --- /dev/null +++ b/javascript/ql/test/library-tests/Modules/GlobalVariableRef.expected @@ -0,0 +1,3 @@ +| a.js:5:31:5:31 | o | +| exports.js:3:9:3:15 | exports | +| tst.html:6:3:6:7 | alert | diff --git a/javascript/ql/test/library-tests/Modules/GlobalVariableRef.ql b/javascript/ql/test/library-tests/Modules/GlobalVariableRef.ql new file mode 100644 index 00000000000..5b06a07a226 --- /dev/null +++ b/javascript/ql/test/library-tests/Modules/GlobalVariableRef.ql @@ -0,0 +1,5 @@ +import javascript + +from VarAccess access +where access.getVariable() instanceof GlobalVariable +select access diff --git a/javascript/ql/test/library-tests/Modules/ImportNamespaceSpecifier.expected b/javascript/ql/test/library-tests/Modules/ImportNamespaceSpecifier.expected index d7305160b05..61093b96451 100644 --- a/javascript/ql/test/library-tests/Modules/ImportNamespaceSpecifier.expected +++ b/javascript/ql/test/library-tests/Modules/ImportNamespaceSpecifier.expected @@ -1 +1,2 @@ +| exports.js:1:8:1:17 | * as dummy | | m/c.js:1:8:1:13 | * as b | diff --git a/javascript/ql/test/library-tests/Modules/ImportSpecifiers.expected b/javascript/ql/test/library-tests/Modules/ImportSpecifiers.expected index 7abe8cb7244..73763be7450 100644 --- a/javascript/ql/test/library-tests/Modules/ImportSpecifiers.expected +++ b/javascript/ql/test/library-tests/Modules/ImportSpecifiers.expected @@ -1,6 +1,7 @@ | b.js:1:8:1:8 | f | b.js:1:8:1:8 | f | | d.js:1:10:1:21 | default as g | d.js:1:21:1:21 | g | | d.js:1:24:1:29 | x as y | d.js:1:29:1:29 | y | +| exports.js:1:8:1:17 | * as dummy | exports.js:1:13:1:17 | dummy | | f.ts:1:8:1:8 | g | f.ts:1:8:1:8 | g | | g.ts:1:9:1:11 | foo | g.ts:1:9:1:11 | foo | | import-in-mjs.mjs:1:8:1:24 | exported_from_mjs | import-in-mjs.mjs:1:8:1:24 | exported_from_mjs | diff --git a/javascript/ql/test/library-tests/Modules/Imports.expected b/javascript/ql/test/library-tests/Modules/Imports.expected index d3ef73bcc5b..723f3542039 100644 --- a/javascript/ql/test/library-tests/Modules/Imports.expected +++ b/javascript/ql/test/library-tests/Modules/Imports.expected @@ -1,6 +1,7 @@ | b.js:1:1:1:20 | import f from './a'; | b.js:1:15:1:19 | './a' | 1 | | d.js:1:1:1:43 | import ... './a'; | d.js:1:38:1:42 | './a' | 2 | | d.js:2:1:2:13 | import './b'; | d.js:2:8:2:12 | './b' | 0 | +| exports.js:1:1:1:31 | import ... dummy'; | exports.js:1:24:1:30 | 'dummy' | 1 | | f.ts:1:1:1:19 | import g from './e' | f.ts:1:15:1:19 | './e' | 1 | | g.ts:1:1:1:23 | import ... m './f' | g.ts:1:19:1:23 | './f' | 1 | | import-in-mjs.mjs:1:1:1:46 | import ... n-mjs'; | import-in-mjs.mjs:1:31:1:45 | 'export-in-mjs' | 1 | diff --git a/javascript/ql/test/library-tests/Modules/exports.js b/javascript/ql/test/library-tests/Modules/exports.js new file mode 100644 index 00000000000..c4a90d9da65 --- /dev/null +++ b/javascript/ql/test/library-tests/Modules/exports.js @@ -0,0 +1,4 @@ +import * as dummy from 'dummy'; // Recognize as ES6 module + +let x = exports; // global variable +x.foo();