diff --git a/javascript/extractor/src/com/semmle/jcorn/Parser.java b/javascript/extractor/src/com/semmle/jcorn/Parser.java index 53d71c92d506..79a63e9a1217 100644 --- a/javascript/extractor/src/com/semmle/jcorn/Parser.java +++ b/javascript/extractor/src/com/semmle/jcorn/Parser.java @@ -3547,7 +3547,19 @@ protected List parseExportSpecifiers(Set exports) { SourceLocation loc = new SourceLocation(this.startLoc); Identifier local = this.parseIdent(this.type == TokenType._default); - Identifier exported = this.eatContextual("as") ? this.parseIdent(true) : local; + Identifier exported; + if (!this.eatContextual("as")) { + exported = local; + } else { + if (this.type == TokenType.string) { + // e.g. `export { Foo_new as "Foo::new" }` + Expression string = this.parseExprAtom(null); + String str = ((Literal)string).getStringValue(); + exported = this.finishNode(new Identifier(loc, str)); + } else { + exported = this.parseIdent(true); + } + } checkExport(exports, exported.getName(), exported.getLoc().getStart()); nodes.add(this.finishNode(new ExportSpecifier(loc, local, exported))); } @@ -3629,7 +3641,22 @@ protected List parseImportSpecifiers() { protected ImportSpecifier parseImportSpecifier() { SourceLocation loc = new SourceLocation(this.startLoc); - Identifier imported = this.parseIdent(true), local; + Identifier imported, local; + + if (this.type == TokenType.string) { + // Arbitrary Module Namespace Identifiers + // e.g. `import { "Foo::new" as Foo_new } from "./foo.wasm"` + Expression string = this.parseExprAtom(null); + String str = ((Literal)string).getStringValue(); + imported = this.finishNode(new Identifier(loc, str)); + // only makes sense if there is a local identifier + if (!this.isContextual("as")) { + this.raiseRecoverable(this.start, "Unexpected string"); + } + } else { + imported = this.parseIdent(true); + } + if (this.eatContextual("as")) { local = this.parseIdent(false); } else { diff --git a/javascript/extractor/tests/flow/input/arbitaryModuleSpecifier.js b/javascript/extractor/tests/flow/input/arbitaryModuleSpecifier.js new file mode 100644 index 000000000000..6a8ac6c80d87 --- /dev/null +++ b/javascript/extractor/tests/flow/input/arbitaryModuleSpecifier.js @@ -0,0 +1 @@ +import { "foo" } from "foo"; // syntax-error, but it shouldn't crash the extractor diff --git a/javascript/extractor/tests/flow/output/trap/arbitaryModuleSpecifier.js.trap b/javascript/extractor/tests/flow/output/trap/arbitaryModuleSpecifier.js.trap new file mode 100644 index 000000000000..50ec6a88c657 --- /dev/null +++ b/javascript/extractor/tests/flow/output/trap/arbitaryModuleSpecifier.js.trap @@ -0,0 +1,143 @@ +#10000=@"/arbitaryModuleSpecifier.js;sourcefile" +files(#10000,"/arbitaryModuleSpecifier.js") +#10001=@"/;folder" +folders(#10001,"/") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +#20002=* +comments(#20002,0,#20001," syntax-error, but it shouldn't crash the extractor","// synt ... tractor") +#20003=@"loc,{#10000},1,30,1,82" +locations_default(#20003,#10000,1,30,1,82) +hasLocation(#20002,#20003) +#20004=* +lines(#20004,#20001,"import { ""foo"" } from ""foo""; // syntax-error, but it shouldn't crash the extractor"," +") +#20005=@"loc,{#10000},1,1,1,82" +locations_default(#20005,#10000,1,1,1,82) +hasLocation(#20004,#20005) +numlines(#20001,1,1,1) +#20006=* +tokeninfo(#20006,7,#20001,0,"import") +#20007=@"loc,{#10000},1,1,1,6" +locations_default(#20007,#10000,1,1,1,6) +hasLocation(#20006,#20007) +#20008=* +tokeninfo(#20008,8,#20001,1,"{") +#20009=@"loc,{#10000},1,8,1,8" +locations_default(#20009,#10000,1,8,1,8) +hasLocation(#20008,#20009) +#20010=* +tokeninfo(#20010,4,#20001,2,"""foo""") +#20011=@"loc,{#10000},1,10,1,14" +locations_default(#20011,#10000,1,10,1,14) +hasLocation(#20010,#20011) +#20012=* +tokeninfo(#20012,8,#20001,3,"}") +#20013=@"loc,{#10000},1,16,1,16" +locations_default(#20013,#10000,1,16,1,16) +hasLocation(#20012,#20013) +#20014=* +tokeninfo(#20014,6,#20001,4,"from") +#20015=@"loc,{#10000},1,18,1,21" +locations_default(#20015,#10000,1,18,1,21) +hasLocation(#20014,#20015) +#20016=* +tokeninfo(#20016,4,#20001,5,"""foo""") +#20017=@"loc,{#10000},1,23,1,27" +locations_default(#20017,#10000,1,23,1,27) +hasLocation(#20016,#20017) +#20018=* +tokeninfo(#20018,8,#20001,6,";") +#20019=@"loc,{#10000},1,28,1,28" +locations_default(#20019,#10000,1,28,1,28) +hasLocation(#20018,#20019) +#20020=* +tokeninfo(#20020,0,#20001,7,"") +#20021=@"loc,{#10000},2,1,2,0" +locations_default(#20021,#10000,2,1,2,0) +hasLocation(#20020,#20021) +next_token(#20002,#20020) +toplevels(#20001,0) +#20022=@"loc,{#10000},1,1,2,0" +locations_default(#20022,#10000,1,1,2,0) +hasLocation(#20001,#20022) +#20023=@"module;{#10000},1,1" +scopes(#20023,3) +scopenodes(#20001,#20023) +scopenesting(#20023,#20000) +is_module(#20001) +is_es2015_module(#20001) +#20024=@"var;{foo};{#20023}" +variables(#20024,"foo",#20023) +#20025=@"local_type_name;{foo};{#20023}" +local_type_names(#20025,"foo",#20023) +#20026=@"local_namespace_name;{foo};{#20023}" +local_namespace_names(#20026,"foo",#20023) +variables(#20024,"foo",#20023) +local_type_names(#20025,"foo",#20023) +local_namespace_names(#20026,"foo",#20023) +#20027=* +stmts(#20027,27,#20001,0,"import ... ""foo"";") +#20028=@"loc,{#10000},1,1,1,28" +locations_default(#20028,#10000,1,1,1,28) +hasLocation(#20027,#20028) +stmt_containers(#20027,#20001) +#20029=* +exprs(#20029,4,#20027,-1,"""foo""") +hasLocation(#20029,#20017) +enclosing_stmt(#20029,#20027) +expr_containers(#20029,#20001) +literals("foo","""foo""",#20029) +#20030=* +regexpterm(#20030,14,#20029,0,"foo") +#20031=@"loc,{#10000},1,24,1,26" +locations_default(#20031,#10000,1,24,1,26) +hasLocation(#20030,#20031) +regexp_const_value(#20030,"foo") +#20032=* +exprs(#20032,83,#20027,0,"""foo""") +hasLocation(#20032,#20011) +enclosing_stmt(#20032,#20027) +expr_containers(#20032,#20001) +#20033=* +exprs(#20033,0,#20032,0,"""foo""") +hasLocation(#20033,#20011) +enclosing_stmt(#20033,#20027) +expr_containers(#20033,#20001) +literals("foo","foo",#20033) +#20034=* +exprs(#20034,78,#20032,1,"""foo""") +hasLocation(#20034,#20011) +enclosing_stmt(#20034,#20027) +expr_containers(#20034,#20001) +literals("foo","foo",#20034) +decl(#20034,#20024) +typedecl(#20034,#20025) +namespacedecl(#20034,#20026) +#20035=* +entry_cfg_node(#20035,#20001) +#20036=@"loc,{#10000},1,1,1,0" +locations_default(#20036,#10000,1,1,1,0) +hasLocation(#20035,#20036) +#20037=* +exit_cfg_node(#20037,#20001) +hasLocation(#20037,#20021) +successor(#20027,#20037) +successor(#20032,#20027) +successor(#20035,#20032) +#20038=* +js_parse_errors(#20038,#20001,"Error: Unexpected string","import { ""foo"" } from ""foo""; // syntax-error, but it shouldn't crash the extractor +") +hasLocation(#20038,#20013) +#20039=* +lines(#20039,#20001,"import { ""foo"" } from ""foo""; // syntax-error, but it shouldn't crash the extractor"," +") +hasLocation(#20039,#20005) +numlines(#20001,1,0,0) +numlines(#10000,1,1,1) +filetype(#10000,"javascript") diff --git a/javascript/ql/test/library-tests/Modules/arbitarySpecifier.js b/javascript/ql/test/library-tests/Modules/arbitarySpecifier.js new file mode 100644 index 000000000000..64f88bd1bed6 --- /dev/null +++ b/javascript/ql/test/library-tests/Modules/arbitarySpecifier.js @@ -0,0 +1,5 @@ +import { "Foo::new" as Foo_new } from "./foo.wasm" + +const foo = Foo_new() + +export { Foo_new as "Foo::new" } \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Modules/tests.expected b/javascript/ql/test/library-tests/Modules/tests.expected index a594fdcf1104..a742e604218f 100644 --- a/javascript/ql/test/library-tests/Modules/tests.expected +++ b/javascript/ql/test/library-tests/Modules/tests.expected @@ -3,6 +3,7 @@ test_BulkReExportDeclarations test_ExportDeclarations | a.js:1:1:3:1 | export ... n 23;\\n} | | a.js:5:1:5:32 | export ... } = o; | +| arbitarySpecifier.js:5:1:5:32 | export ... :new" } | | b.js:5:1:5:18 | export { f as g }; | | b.js:7:1:7:21 | export ... './a'; | | d.js:4:1:4:20 | export * from 'm/c'; | @@ -18,6 +19,7 @@ test_ExportDefaultDeclarations | a.js:1:1:3:1 | export ... n 23;\\n} | | es2015_require.js:3:1:3:25 | export ... ss C {} | test_ExportSpecifiers +| arbitarySpecifier.js:5:10:5:30 | Foo_new ... o::new" | arbitarySpecifier.js:5:10:5:16 | Foo_new | arbitarySpecifier.js:5:10:5:30 | Foo_new ... o::new" | | b.js:5:10:5:15 | f as g | b.js:5:10:5:10 | f | b.js:5:15:5:15 | g | | e.js:2:10:2:10 | x | e.js:2:10:2:10 | x | e.js:2:10:2:10 | x | | e.js:2:13:2:13 | y | e.js:2:13:2:13 | y | e.js:2:13:2:13 | y | @@ -41,6 +43,7 @@ test_ImportNamespaceSpecifier | exports.js:1:8:1:17 | * as dummy | | m/c.js:1:8:1:13 | * as b | test_ImportSpecifiers +| arbitarySpecifier.js:1:10:1:30 | "Foo::n ... Foo_new | arbitarySpecifier.js:1:24:1:30 | Foo_new | | 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 | @@ -55,6 +58,7 @@ test_ImportSpecifiers | tst.html:5:10:5:10 | f | tst.html:5:10:5:10 | f | | unresolved.js:1:8:1:8 | f | unresolved.js:1:8:1:8 | f | test_Imports +| arbitarySpecifier.js:1:1:1:50 | import ... o.wasm" | arbitarySpecifier.js:1:39:1:50 | "./foo.wasm" | 1 | | 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 | @@ -72,6 +76,7 @@ test_Module_exports | a.js:1:1:5:32 | | default | a.js:1:16:3:1 | functio ... n 23;\\n} | | a.js:1:1:5:32 | | x | a.js:5:18:5:20 | f() | | a.js:1:1:5:32 | | y | a.js:5:25:5:25 | y | +| arbitarySpecifier.js:1:1:5:32 | | Foo::new | arbitarySpecifier.js:5:10:5:16 | Foo_new | | b.js:1:1:8:0 | | f2 | a.js:1:16:3:1 | functio ... n 23;\\n} | | b.js:1:1:8:0 | | g | b.js:5:10:5:10 | f | | e.js:1:1:4:0 | | g | a.js:1:16:3:1 | functio ... n 23;\\n} | @@ -84,6 +89,7 @@ test_Module_exports | reExportNamespace.js:1:1:2:0 | | ns | reExportNamespace.js:1:8:1:14 | * as ns | | tst.html:4:23:8:0 | | y | tst.html:7:20:7:21 | 42 | test_NamedImportSpecifier +| arbitarySpecifier.js:1:10:1:30 | "Foo::n ... Foo_new | | d.js:1:10:1:21 | default as g | | d.js:1:24:1:29 | x as y | | g.ts:1:9:1:11 | foo | @@ -111,6 +117,7 @@ test_getAnImportedModule | library-tests/Modules/m/c.js | library-tests/Modules/b.js | | library-tests/Modules/reExportNamespaceClient.js | library-tests/Modules/reExportNamespace.js | test_getExportedName +| arbitarySpecifier.js:5:10:5:30 | Foo_new ... o::new" | Foo::new | | b.js:5:10:5:15 | f as g | g | | b.js:7:8:7:9 | f2 | f2 | | e.js:2:10:2:10 | x | x | @@ -119,6 +126,7 @@ test_getExportedName | m/c.js:5:10:5:15 | g as h | h | | reExportNamespace.js:1:8:1:14 | * as ns | ns | test_getImportedName +| arbitarySpecifier.js:1:10:1:30 | "Foo::n ... Foo_new | Foo::new | | b.js:1:8:1:8 | f | default | | d.js:1:10:1:21 | default as g | default | | d.js:1:24:1:29 | x as y | x | @@ -131,6 +139,7 @@ test_getImportedName | tst.html:5:10:5:10 | f | default | | unresolved.js:1:8:1:8 | f | default | test_getLocalName +| arbitarySpecifier.js:5:10:5:30 | Foo_new ... o::new" | Foo_new | | b.js:5:10:5:15 | f as g | f | | b.js:7:8:7:9 | f2 | default | | e.js:2:10:2:10 | x | x | @@ -141,6 +150,7 @@ test_getSourceNode | a.js:1:1:3:1 | export ... n 23;\\n} | default | a.js:1:16:3:1 | functio ... n 23;\\n} | | a.js:5:1:5:32 | export ... } = o; | x | a.js:5:18:5:20 | f() | | a.js:5:1:5:32 | export ... } = o; | y | a.js:5:25:5:25 | y | +| arbitarySpecifier.js:5:1:5:32 | export ... :new" } | Foo::new | arbitarySpecifier.js:5:10:5:16 | Foo_new | | b.js:5:1:5:18 | export { f as g }; | g | b.js:5:10:5:10 | f | | b.js:7:1:7:21 | export ... './a'; | f2 | a.js:1:16:3:1 | functio ... n 23;\\n} | | e.js:2:1:2:16 | export { x, y }; | x | e.js:2:10:2:10 | x |