Skip to content
Merged
132 changes: 76 additions & 56 deletions javascript/ql/lib/semmle/javascript/ES2015Modules.qll
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand All@@ -369,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) }
Expand DownExpand Up@@ -421,20 +441,20 @@ 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, name)
not isShadowedFromBulkExport(this.getEnclosingModule(), name)
}

overlay[global]
override DataFlow::Node getSourceNode(string name){
override DataFlow::Node getReExportedSourceNode(string name){
result = this.getReExportedES2015Module().getAnExport().getSourceNode(name)
}
}

/**
* 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:
Expand All@@ -446,8 +466,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
Expand All@@ -468,8 +488,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()
}

Expand All@@ -481,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())
}
}
Expand DownExpand Up@@ -524,21 +542,20 @@ 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){
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()
override predicate exportsDirectlyAs(LexicalName v, string name){
(
exists(LexicalDecl vd | vd = this.getAnExportedDecl() |
name = vd.getName() and v = vd.getALexicalName()
)
or
this.(ReExportDeclaration).getReExportedES2015Module().exportsAs(v, spec.getLocalName())
)
exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() |
v = spec.getLocal().(LexicalAccess).getALexicalName()
)
) and
not (this.isTypeOnly() and v instanceof Variable)
}

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())
Expand All@@ -554,12 +571,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)
)
}

Expand DownExpand Up@@ -587,19 +603,6 @@ private class ExportNamespaceStep extends PreCallGraphStep{
}
}

/**
* An export declaration with the `type` modifier.
*/
private class TypeOnlyExportDeclaration extends ExportNamedDeclaration{
TypeOnlyExportDeclaration(){this.isTypeOnly() }

overlay[global]
override predicate exportsAs(LexicalName v, string name){
super.exportsAs(v, name) and
not v instanceof Variable
}
}

/**
* An export specifier in an export declaration.
*
Expand DownExpand Up@@ -777,6 +780,20 @@ abstract class ReExportDeclaration extends ExportDeclaration{
Stages::Imports::ref() and
result.getFile() = ImportPathResolver::resolveExpr(this.getImportedPath())
}

/**
* Holds if this re-export declaration ultimately re-exports `v` (from another module)
* under the given `name`.
*/
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. */
Expand All@@ -803,6 +820,21 @@ 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())
) and
not (this.isTypeOnly() and v instanceof Variable)
}

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())
)
}
}

/**
Expand All@@ -819,16 +851,4 @@ 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
result = this.(ExportNamedDeclaration).getSourceNode(name)
}
}