Skip to content

Commit d8027fb

Browse files
authored
Merge pull request #20885 from asgerf/js/local-module-exports
JS: Split module exports into a local and global variant
2 parents f0cac32 + b33af5b commit d8027fb

File tree

1 file changed

+76
-56
lines changed

1 file changed

+76
-56
lines changed

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

Lines changed: 76 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,17 @@ abstract class ExportDeclaration extends Stmt, @export_declaration {
345345

346346
/** Holds if this export declaration exports variable `v` under the name `name`. */
347347
overlay[global]
348-
abstract predicate exportsAs(LexicalName v, string name);
348+
final predicate exportsAs(LexicalName v, string name) {
349+
this.exportsDirectlyAs(v, name)
350+
or
351+
this.(ReExportDeclaration).reExportsAs(v, name)
352+
}
353+
354+
/**
355+
* Holds if this export declaration exports variable `v` under the name `name`,
356+
* not counting re-exports.
357+
*/
358+
predicate exportsDirectlyAs(LexicalName v, string name) { none() }
349359

350360
/**
351361
* Gets the data flow node corresponding to the value this declaration exports
@@ -369,7 +379,17 @@ abstract class ExportDeclaration extends Stmt, @export_declaration {
369379
* to module `a` or possibly to some other module from which `a` re-exports.
370380
*/
371381
overlay[global]
372-
abstract DataFlow::Node getSourceNode(string name);
382+
final DataFlow::Node getSourceNode(string name) {
383+
result = this.getDirectSourceNode(name)
384+
or
385+
result = this.(ReExportDeclaration).getReExportedSourceNode(name)
386+
}
387+
388+
/**
389+
* Gets the data flow node corresponding to the value this declaration exports
390+
* under the name `name`, not including sources that come from a re-export.
391+
*/
392+
DataFlow::Node getDirectSourceNode(string name) { none() }
373393

374394
/** Holds if is declared with the `type` keyword, so only types are exported. */
375395
predicate isTypeOnly() { has_type_keyword(this) }
@@ -421,20 +441,20 @@ class BulkReExportDeclaration extends ReExportDeclaration, @export_all_declarati
421441
override ConstantString getImportedPath() { result = this.getChildExpr(0) }
422442

423443
overlay[global]
424-
override predicate exportsAs(LexicalName v, string name) {
444+
override predicate reExportsAs(LexicalName v, string name) {
425445
this.getReExportedES2015Module().exportsAs(v, name) and
426-
not isShadowedFromBulkExport(this, name)
446+
not isShadowedFromBulkExport(this.getEnclosingModule(), name)
427447
}
428448

429449
overlay[global]
430-
override DataFlow::Node getSourceNode(string name) {
450+
override DataFlow::Node getReExportedSourceNode(string name) {
431451
result = this.getReExportedES2015Module().getAnExport().getSourceNode(name)
432452
}
433453
}
434454

435455
/**
436-
* Holds if the given bulk export `reExport` should not re-export `name` because there is an explicit export
437-
* of that name in the same module.
456+
* Holds if bulk re-exports in `mod` should not re-export `name` because there is an explicit export
457+
* of that name in `mod`.
438458
*
439459
* At compile time, shadowing works across declaration spaces.
440460
* For instance, directly exporting an interface `X` will block a variable `X` from being re-exported:
@@ -446,8 +466,8 @@ class BulkReExportDeclaration extends ReExportDeclaration, @export_all_declarati
446466
* but we ignore this subtlety.
447467
*/
448468
overlay[global]
449-
private predicate isShadowedFromBulkExport(BulkReExportDeclaration reExport, string name) {
450-
exists(ExportNamedDeclaration other | other.getTopLevel() = reExport.getEnclosingModule() |
469+
private predicate isShadowedFromBulkExport(Module mod, string name) {
470+
exists(ExportNamedDeclaration other | other.getTopLevel() = mod |
451471
other.getAnExportedDecl().getName() = name
452472
or
453473
other.getASpecifier().getExportedName() = name
@@ -468,8 +488,7 @@ class ExportDefaultDeclaration extends ExportDeclaration, @export_default_declar
468488
/** Gets the operand statement or expression that is exported by this declaration. */
469489
ExprOrStmt getOperand() { result = this.getChild(0) }
470490

471-
overlay[global]
472-
override predicate exportsAs(LexicalName v, string name) {
491+
override predicate exportsDirectlyAs(LexicalName v, string name) {
473492
name = "default" and v = this.getADecl().getVariable()
474493
}
475494

@@ -481,8 +500,7 @@ class ExportDefaultDeclaration extends ExportDeclaration, @export_default_declar
481500
)
482501
}
483502

484-
overlay[global]
485-
override DataFlow::Node getSourceNode(string name) {
503+
override DataFlow::Node getDirectSourceNode(string name) {
486504
name = "default" and result = DataFlow::valueNode(this.getOperand())
487505
}
488506
}
@@ -524,21 +542,20 @@ class ExportNamedDeclaration extends ExportDeclaration, @export_named_declaratio
524542
/** Gets the variable declaration, if any, exported by this named export. */
525543
VarDecl getADecl() { result = this.getAnExportedDecl() }
526544

527-
overlay[global]
528-
override predicate exportsAs(LexicalName v, string name) {
529-
exists(LexicalDecl vd | vd = this.getAnExportedDecl() |
530-
name = vd.getName() and v = vd.getALexicalName()
531-
)
532-
or
533-
exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() |
534-
v = spec.getLocal().(LexicalAccess).getALexicalName()
545+
override predicate exportsDirectlyAs(LexicalName v, string name) {
546+
(
547+
exists(LexicalDecl vd | vd = this.getAnExportedDecl() |
548+
name = vd.getName() and v = vd.getALexicalName()
549+
)
535550
or
536-
this.(ReExportDeclaration).getReExportedES2015Module().exportsAs(v, spec.getLocalName())
537-
)
551+
exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() |
552+
v = spec.getLocal().(LexicalAccess).getALexicalName()
553+
)
554+
) and
555+
not (this.isTypeOnly() and v instanceof Variable)
538556
}
539557

540-
overlay[global]
541-
override DataFlow::Node getSourceNode(string name) {
558+
override DataFlow::Node getDirectSourceNode(string name) {
542559
exists(VarDef d | d.getTarget() = this.getADecl() |
543560
name = d.getTarget().(VarDecl).getName() and
544561
result = DataFlow::valueNode(d.getSource())
@@ -554,12 +571,11 @@ class ExportNamedDeclaration extends ExportDeclaration, @export_named_declaratio
554571
exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() |
555572
not exists(this.getImportedPath()) and result = DataFlow::valueNode(spec.getLocal())
556573
or
557-
exists(ReExportDeclaration red | red = this |
558-
result = red.getReExportedES2015Module().getAnExport().getSourceNode(spec.getLocalName())
559-
or
560-
spec instanceof ExportNamespaceSpecifier and
561-
result = DataFlow::valueNode(spec)
562-
)
574+
// For `export * as B from ".."`, we use the ExportNamespaceSpecifier as a representative for the
575+
// object that gets exposed as `B`.
576+
this instanceof ReExportDeclaration and
577+
spec instanceof ExportNamespaceSpecifier and
578+
result = DataFlow::valueNode(spec)
563579
)
564580
}
565581

@@ -587,19 +603,6 @@ private class ExportNamespaceStep extends PreCallGraphStep {
587603
}
588604
}
589605

590-
/**
591-
* An export declaration with the `type` modifier.
592-
*/
593-
private class TypeOnlyExportDeclaration extends ExportNamedDeclaration {
594-
TypeOnlyExportDeclaration() { this.isTypeOnly() }
595-
596-
overlay[global]
597-
override predicate exportsAs(LexicalName v, string name) {
598-
super.exportsAs(v, name) and
599-
not v instanceof Variable
600-
}
601-
}
602-
603606
/**
604607
* An export specifier in an export declaration.
605608
*
@@ -777,6 +780,20 @@ abstract class ReExportDeclaration extends ExportDeclaration {
777780
Stages::Imports::ref() and
778781
result.getFile() = ImportPathResolver::resolveExpr(this.getImportedPath())
779782
}
783+
784+
/**
785+
* Holds if this re-export declaration ultimately re-exports `v` (from another module)
786+
* under the given `name`.
787+
*/
788+
overlay[global]
789+
abstract predicate reExportsAs(LexicalName v, string name);
790+
791+
/**
792+
* Gets the data flow node (from another module) corresponding to the value that is re-exported
793+
* under the name `name`.
794+
*/
795+
overlay[global]
796+
abstract DataFlow::Node getReExportedSourceNode(string name);
780797
}
781798

782799
/** A literal path expression appearing in a re-export declaration. */
@@ -803,6 +820,21 @@ class SelectiveReExportDeclaration extends ReExportDeclaration, ExportNamedDecla
803820
override ConstantString getImportedPath() {
804821
result = ExportNamedDeclaration.super.getImportedPath()
805822
}
823+
824+
overlay[global]
825+
override predicate reExportsAs(LexicalName v, string name) {
826+
exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() |
827+
this.getReExportedES2015Module().exportsAs(v, spec.getLocalName())
828+
) and
829+
not (this.isTypeOnly() and v instanceof Variable)
830+
}
831+
832+
overlay[global]
833+
override DataFlow::Node getReExportedSourceNode(string name) {
834+
exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() |
835+
result = this.getReExportedES2015Module().getAnExport().getSourceNode(spec.getLocalName())
836+
)
837+
}
806838
}
807839

808840
/**
@@ -819,16 +851,4 @@ class SelectiveReExportDeclaration extends ReExportDeclaration, ExportNamedDecla
819851
*/
820852
class OriginalExportDeclaration extends ExportDeclaration {
821853
OriginalExportDeclaration() { not this instanceof ReExportDeclaration }
822-
823-
overlay[global]
824-
override predicate exportsAs(LexicalName v, string name) {
825-
this.(ExportDefaultDeclaration).exportsAs(v, name) or
826-
this.(ExportNamedDeclaration).exportsAs(v, name)
827-
}
828-
829-
overlay[global]
830-
override DataFlow::Node getSourceNode(string name) {
831-
result = this.(ExportDefaultDeclaration).getSourceNode(name) or
832-
result = this.(ExportNamedDeclaration).getSourceNode(name)
833-
}
834854
}

0 commit comments

Comments
 (0)