From ce9986c4492acdfb2b8ac39060cd7f456b3c94f0 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 21 Nov 2025 12:30:31 +0100 Subject: [PATCH 1/8] JS: Change signature of isShadowedFromBulkExport --- javascript/ql/lib/semmle/javascript/ES2015Modules.qll | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll index 6eee9ea56e50..eee808ce020a 100644 --- a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll @@ -423,7 +423,7 @@ class BulkReExportDeclaration extends ReExportDeclaration, @export_all_declarati overlay[global] override predicate exportsAs(LexicalName v, string name) { this.getReExportedES2015Module().exportsAs(v, name) and - not isShadowedFromBulkExport(this, name) + not isShadowedFromBulkExport(this.getEnclosingModule(), name) } overlay[global] @@ -433,8 +433,8 @@ class BulkReExportDeclaration extends ReExportDeclaration, @export_all_declarati } /** - * Holds if the given bulk export `reExport` should not re-export `name` because there is an explicit export - * of that name in the same module. + * Holds if bulk re-exports in `mod` should not re-export `name` because there is an explicit export + * of that name in `mod`. * * At compile time, shadowing works across declaration spaces. * For instance, directly exporting an interface `X` will block a variable `X` from being re-exported: @@ -446,8 +446,8 @@ class BulkReExportDeclaration extends ReExportDeclaration, @export_all_declarati * but we ignore this subtlety. */ overlay[global] -private predicate isShadowedFromBulkExport(BulkReExportDeclaration reExport, string name) { - exists(ExportNamedDeclaration other | other.getTopLevel() = reExport.getEnclosingModule() | +private predicate isShadowedFromBulkExport(Module mod, string name) { + exists(ExportNamedDeclaration other | other.getTopLevel() = mod | other.getAnExportedDecl().getName() = name or other.getASpecifier().getExportedName() = name From 74d725ef2108c40564154db870a4c378344cdf74 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 21 Nov 2025 12:31:09 +0100 Subject: [PATCH 2/8] JS: Remove unnecessary override in OriginalExportDeclaration --- javascript/ql/lib/semmle/javascript/ES2015Modules.qll | 6 ------ 1 file changed, 6 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll index eee808ce020a..bd6e8071cc52 100644 --- a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll @@ -820,12 +820,6 @@ class SelectiveReExportDeclaration extends ReExportDeclaration, ExportNamedDecla class OriginalExportDeclaration extends ExportDeclaration { OriginalExportDeclaration() { not this instanceof ReExportDeclaration } - overlay[global] - override predicate exportsAs(LexicalName v, string name) { - this.(ExportDefaultDeclaration).exportsAs(v, name) or - this.(ExportNamedDeclaration).exportsAs(v, name) - } - overlay[global] override DataFlow::Node getSourceNode(string name) { result = this.(ExportDefaultDeclaration).getSourceNode(name) or From 78c8ab11f2293addbf327c954373a40912d23b5f Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 21 Nov 2025 12:31:34 +0100 Subject: [PATCH 3/8] JS: Split exportsAs into exportsDirectlyAs and reExportsAs --- .../lib/semmle/javascript/ES2015Modules.qll | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll index bd6e8071cc52..8634b6539f7c 100644 --- a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll @@ -345,7 +345,17 @@ abstract class ExportDeclaration extends Stmt, @export_declaration { /** Holds if this export declaration exports variable `v` under the name `name`. */ overlay[global] - abstract predicate exportsAs(LexicalName v, string name); + final predicate exportsAs(LexicalName v, string name) { + this.exportsDirectlyAs(v, name) + or + this.(ReExportDeclaration).reExportsAs(v, name) + } + + /** + * Holds if this export declaration exports variable `v` under the name `name`, + * not counting re-exports. + */ + predicate exportsDirectlyAs(LexicalName v, string name) { none() } /** * Gets the data flow node corresponding to the value this declaration exports @@ -421,7 +431,7 @@ class BulkReExportDeclaration extends ReExportDeclaration, @export_all_declarati override ConstantString getImportedPath() { result = this.getChildExpr(0) } overlay[global] - override predicate exportsAs(LexicalName v, string name) { + override predicate reExportsAs(LexicalName v, string name) { this.getReExportedES2015Module().exportsAs(v, name) and not isShadowedFromBulkExport(this.getEnclosingModule(), name) } @@ -468,8 +478,7 @@ class ExportDefaultDeclaration extends ExportDeclaration, @export_default_declar /** Gets the operand statement or expression that is exported by this declaration. */ ExprOrStmt getOperand() { result = this.getChild(0) } - overlay[global] - override predicate exportsAs(LexicalName v, string name) { + override predicate exportsDirectlyAs(LexicalName v, string name) { name = "default" and v = this.getADecl().getVariable() } @@ -524,16 +533,13 @@ class ExportNamedDeclaration extends ExportDeclaration, @export_named_declaratio /** Gets the variable declaration, if any, exported by this named export. */ VarDecl getADecl() { result = this.getAnExportedDecl() } - overlay[global] - override predicate exportsAs(LexicalName v, string name) { + override predicate exportsDirectlyAs(LexicalName v, string name) { exists(LexicalDecl vd | vd = this.getAnExportedDecl() | name = vd.getName() and v = vd.getALexicalName() ) or exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() | v = spec.getLocal().(LexicalAccess).getALexicalName() - or - this.(ReExportDeclaration).getReExportedES2015Module().exportsAs(v, spec.getLocalName()) ) } @@ -593,9 +599,8 @@ private class ExportNamespaceStep extends PreCallGraphStep { private class TypeOnlyExportDeclaration extends ExportNamedDeclaration { TypeOnlyExportDeclaration() { this.isTypeOnly() } - overlay[global] - override predicate exportsAs(LexicalName v, string name) { - super.exportsAs(v, name) and + override predicate exportsDirectlyAs(LexicalName v, string name) { + super.exportsDirectlyAs(v, name) and not v instanceof Variable } } @@ -777,6 +782,9 @@ abstract class ReExportDeclaration extends ExportDeclaration { Stages::Imports::ref() and result.getFile() = ImportPathResolver::resolveExpr(this.getImportedPath()) } + + overlay[global] + abstract predicate reExportsAs(LexicalName v, string name); } /** A literal path expression appearing in a re-export declaration. */ @@ -803,6 +811,13 @@ class SelectiveReExportDeclaration extends ReExportDeclaration, ExportNamedDecla override ConstantString getImportedPath() { result = ExportNamedDeclaration.super.getImportedPath() } + + overlay[global] + override predicate reExportsAs(LexicalName v, string name) { + exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() | + this.getReExportedES2015Module().exportsAs(v, spec.getLocalName()) + ) + } } /** From 3a393ecd540218d96d59551741955a476cdad8b5 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 21 Nov 2025 12:41:34 +0100 Subject: [PATCH 4/8] Fixup! qldoc for reExportsAs --- javascript/ql/lib/semmle/javascript/ES2015Modules.qll | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll index 8634b6539f7c..2c1ec9cc95d7 100644 --- a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll @@ -783,6 +783,10 @@ abstract class ReExportDeclaration extends ExportDeclaration { result.getFile() = ImportPathResolver::resolveExpr(this.getImportedPath()) } + /** + * Holds if this re-export destination ultimately re-exports `v` (from another module) + * under the given `name`. + */ overlay[global] abstract predicate reExportsAs(LexicalName v, string name); } From 94566e5e1e410114f78b88bdf7c4c7ffb403a351 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 21 Nov 2025 12:42:10 +0100 Subject: [PATCH 5/8] JS: Remove unnecessary override in OriginalExportDeclaration --- javascript/ql/lib/semmle/javascript/ES2015Modules.qll | 6 ------ 1 file changed, 6 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll index 2c1ec9cc95d7..87913bf7b47f 100644 --- a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll @@ -838,10 +838,4 @@ class SelectiveReExportDeclaration extends ReExportDeclaration, ExportNamedDecla */ class OriginalExportDeclaration extends ExportDeclaration { OriginalExportDeclaration() { not this instanceof ReExportDeclaration } - - overlay[global] - override DataFlow::Node getSourceNode(string name) { - result = this.(ExportDefaultDeclaration).getSourceNode(name) or - result = this.(ExportNamedDeclaration).getSourceNode(name) - } } From 9c4f85f15bce9be28c3413fa1ba64f863ea9e5fe Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 21 Nov 2025 12:42:30 +0100 Subject: [PATCH 6/8] JS: Split getSourceNode into getDirectSourceNode and getReExportedSourceNode --- .../lib/semmle/javascript/ES2015Modules.qll | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll index 87913bf7b47f..de9bb022afcc 100644 --- a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll @@ -379,7 +379,17 @@ abstract class ExportDeclaration extends Stmt, @export_declaration { * to module `a` or possibly to some other module from which `a` re-exports. */ overlay[global] - abstract DataFlow::Node getSourceNode(string name); + final DataFlow::Node getSourceNode(string name) { + result = this.getDirectSourceNode(name) + or + result = this.(ReExportDeclaration).getReExportedSourceNode(name) + } + + /** + * Gets the data flow node corresponding to the value this declaration exports + * under the name `name`, not including sources that come from a re-export. + */ + DataFlow::Node getDirectSourceNode(string name) { none() } /** Holds if is declared with the `type` keyword, so only types are exported. */ predicate isTypeOnly() { has_type_keyword(this) } @@ -437,7 +447,7 @@ class BulkReExportDeclaration extends ReExportDeclaration, @export_all_declarati } overlay[global] - override DataFlow::Node getSourceNode(string name) { + override DataFlow::Node getReExportedSourceNode(string name) { result = this.getReExportedES2015Module().getAnExport().getSourceNode(name) } } @@ -490,8 +500,7 @@ class ExportDefaultDeclaration extends ExportDeclaration, @export_default_declar ) } - overlay[global] - override DataFlow::Node getSourceNode(string name) { + override DataFlow::Node getDirectSourceNode(string name) { name = "default" and result = DataFlow::valueNode(this.getOperand()) } } @@ -543,8 +552,7 @@ class ExportNamedDeclaration extends ExportDeclaration, @export_named_declaratio ) } - overlay[global] - override DataFlow::Node getSourceNode(string name) { + override DataFlow::Node getDirectSourceNode(string name) { exists(VarDef d | d.getTarget() = this.getADecl() | name = d.getTarget().(VarDecl).getName() and result = DataFlow::valueNode(d.getSource()) @@ -560,12 +568,11 @@ class ExportNamedDeclaration extends ExportDeclaration, @export_named_declaratio exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() | not exists(this.getImportedPath()) and result = DataFlow::valueNode(spec.getLocal()) or - exists(ReExportDeclaration red | red = this | - result = red.getReExportedES2015Module().getAnExport().getSourceNode(spec.getLocalName()) - or - spec instanceof ExportNamespaceSpecifier and - result = DataFlow::valueNode(spec) - ) + // For `export * as B from ".."`, we use the ExportNamespaceSpecifier as a representative for the + // object that gets exposed as `B`. + this instanceof ReExportDeclaration and + spec instanceof ExportNamespaceSpecifier and + result = DataFlow::valueNode(spec) ) } @@ -789,6 +796,13 @@ abstract class ReExportDeclaration extends ExportDeclaration { */ overlay[global] abstract predicate reExportsAs(LexicalName v, string name); + + /** + * Gets the data flow node (from another module) corresponding to the value that is re-exported + * under the name `name`. + */ + overlay[global] + abstract DataFlow::Node getReExportedSourceNode(string name); } /** A literal path expression appearing in a re-export declaration. */ @@ -822,6 +836,13 @@ class SelectiveReExportDeclaration extends ReExportDeclaration, ExportNamedDecla this.getReExportedES2015Module().exportsAs(v, spec.getLocalName()) ) } + + overlay[global] + override DataFlow::Node getReExportedSourceNode(string name) { + exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() | + result = this.getReExportedES2015Module().getAnExport().getSourceNode(spec.getLocalName()) + ) + } } /** From 51e1bda608d066ef12cec3a1ce5bd9162519be75 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 21 Nov 2025 13:10:32 +0100 Subject: [PATCH 7/8] JS: Move isTypeOnly() check into a direct check, instead of an override --- .../lib/semmle/javascript/ES2015Modules.qll | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll index de9bb022afcc..ecce9621c249 100644 --- a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll @@ -543,13 +543,16 @@ class ExportNamedDeclaration extends ExportDeclaration, @export_named_declaratio VarDecl getADecl() { result = this.getAnExportedDecl() } override predicate exportsDirectlyAs(LexicalName v, string name) { - exists(LexicalDecl vd | vd = this.getAnExportedDecl() | - name = vd.getName() and v = vd.getALexicalName() - ) - or - exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() | - v = spec.getLocal().(LexicalAccess).getALexicalName() - ) + ( + exists(LexicalDecl vd | vd = this.getAnExportedDecl() | + name = vd.getName() and v = vd.getALexicalName() + ) + or + exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() | + v = spec.getLocal().(LexicalAccess).getALexicalName() + ) + ) and + not (this.isTypeOnly() and v instanceof Variable) } override DataFlow::Node getDirectSourceNode(string name) { @@ -600,18 +603,6 @@ private class ExportNamespaceStep extends PreCallGraphStep { } } -/** - * An export declaration with the `type` modifier. - */ -private class TypeOnlyExportDeclaration extends ExportNamedDeclaration { - TypeOnlyExportDeclaration() { this.isTypeOnly() } - - override predicate exportsDirectlyAs(LexicalName v, string name) { - super.exportsDirectlyAs(v, name) and - not v instanceof Variable - } -} - /** * An export specifier in an export declaration. * @@ -834,7 +825,8 @@ class SelectiveReExportDeclaration extends ReExportDeclaration, ExportNamedDecla override predicate reExportsAs(LexicalName v, string name) { exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() | this.getReExportedES2015Module().exportsAs(v, spec.getLocalName()) - ) + ) and + not (this.isTypeOnly() and v instanceof Variable) } overlay[global] From b33af5b377044a9c313851f9f49647506077215b Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 26 Nov 2025 11:40:41 +0100 Subject: [PATCH 8/8] JS: Fix typo --- javascript/ql/lib/semmle/javascript/ES2015Modules.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll index ecce9621c249..9f37e3082b89 100644 --- a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll @@ -782,7 +782,7 @@ abstract class ReExportDeclaration extends ExportDeclaration { } /** - * Holds if this re-export destination ultimately re-exports `v` (from another module) + * Holds if this re-export declaration ultimately re-exports `v` (from another module) * under the given `name`. */ overlay[global]