diff --git a/javascript/ql/integration-tests/query-suite/not_included_in_qls.expected b/javascript/ql/integration-tests/query-suite/not_included_in_qls.expected index fa52a97a4e4a..cb3ab8646b33 100644 --- a/javascript/ql/integration-tests/query-suite/not_included_in_qls.expected +++ b/javascript/ql/integration-tests/query-suite/not_included_in_qls.expected @@ -44,7 +44,6 @@ ql/javascript/ql/src/Metrics/FUseOfES6.ql ql/javascript/ql/src/Metrics/FunCyclomaticComplexity.ql ql/javascript/ql/src/Metrics/FunLinesOfCode.ql ql/javascript/ql/src/NodeJS/CyclicImport.ql -ql/javascript/ql/src/NodeJS/DubiousImport.ql ql/javascript/ql/src/NodeJS/UnresolvableImport.ql ql/javascript/ql/src/NodeJS/UnusedDependency.ql ql/javascript/ql/src/Performance/NonLocalForIn.ql diff --git a/javascript/ql/lib/semmle/javascript/AMD.qll b/javascript/ql/lib/semmle/javascript/AMD.qll index 4828aff27cc4..996e2ea3fdfb 100644 --- a/javascript/ql/lib/semmle/javascript/AMD.qll +++ b/javascript/ql/lib/semmle/javascript/AMD.qll @@ -191,30 +191,12 @@ class AmdModuleDefinition extends CallExpr instanceof AmdModuleDefinition::Range } /** + * DEPRECATED. Abstract values are no longer used to track module exports. + * * Gets an abstract value representing one or more values that may flow * into this module's `module.exports` property. */ - DefiniteAbstractValue getAModuleExportsValue() { - result = [this.getAnImplicitExportsValue(), this.getAnExplicitExportsValue()] - } - - pragma[noinline, nomagic] - private AbstractValue getAnImplicitExportsValue() { - // implicit exports: anything that is returned from the factory function - result = this.getModuleExpr().analyze().getAValue() - } - - pragma[noinline] - private AbstractValue getAnExplicitExportsValue() { - // explicit exports: anything assigned to `module.exports` - exists(AbstractProperty moduleExports, AmdModule m | - this = m.getDefine() and - moduleExports.getBase().(AbstractModuleObject).getModule() = m and - moduleExports.getPropertyName() = "exports" - | - result = moduleExports.getAValue() - ) - } + deprecated DefiniteAbstractValue getAModuleExportsValue() { none() } /** * Gets a call to `require` inside this module. @@ -357,21 +339,19 @@ class AmdModule extends Module { AmdModuleDefinition getDefine() { amdModuleTopLevel(result, this) } override DataFlow::Node getAnExportedValue(string name) { - exists(DataFlow::PropWrite pwn | result = pwn.getRhs() | - pwn.getBase().analyze().getAValue() = this.getDefine().getAModuleExportsValue() and - name = pwn.getPropertyName() - ) + none() // TODO: AMD getAnExportedValue } override DataFlow::Node getABulkExportedNode() { + // TODO: AMD getABulkExportedNode // Assigned to `module.exports` via the factory's `module` parameter - exists(AbstractModuleObject m, DataFlow::PropWrite write | - m.getModule() = this and - write.getPropertyName() = "exports" and - write.getBase().analyze().getAValue() = m and - result = write.getRhs() - ) - or + // exists(AbstractModuleObject m, DataFlow::PropWrite write | + // m.getModule() = this and + // write.getPropertyName() = "exports" and + // write.getBase().analyze().getAValue() = m and + // result = write.getRhs() + // ) + // or // Returned from factory function result = this.getDefine().getModuleExpr().flow() } diff --git a/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll b/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll index 4a461961f8af..ce79212a38a2 100644 --- a/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll +++ b/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll @@ -272,6 +272,23 @@ module AccessPath { result = decl.getIdentifier().(GlobalVarDecl).getName() and root.isGlobal() ) + or + exists(Closure::ClosureModule mod | root.isGlobal() | + node = mod.getExportsVariable().getAnAssignedExpr().flow() and + result = mod.getClosureNamespace() + or + exists(ExportNamedDeclaration decl, string name | + decl.getContainer() = mod and + node = decl.getSourceNode(name) and + result = join(mod.getClosureNamespace(), name) + ) + or + exists(ExportDefaultDeclaration decl | + decl.getContainer() = mod and + node = DataFlow::valueNode(decl.getOperand()) and + result = mod.getClosureNamespace() + ) + ) } /** A module for computing an access to a variable that happens after a property has been written onto it */ diff --git a/javascript/ql/lib/semmle/javascript/Modules.qll b/javascript/ql/lib/semmle/javascript/Modules.qll index 8b0330b708ba..28577c4503e5 100644 --- a/javascript/ql/lib/semmle/javascript/Modules.qll +++ b/javascript/ql/lib/semmle/javascript/Modules.qll @@ -25,8 +25,8 @@ abstract class Module extends TopLevel { /** Gets a module from which this module imports. */ Module getAnImportedModule() { result = this.getAnImport().getImportedModule() } - /** Gets a symbol exported by this module. */ - string getAnExportedSymbol() { exists(this.getAnExportedValue(result)) } + /** DEPRECATED. Use `exists(getAnExportedValue(name))` instead. */ + deprecated string getAnExportedSymbol() { exists(this.getAnExportedValue(result)) } /** * Get a value that is explicitly exported from this module with under `name`. diff --git a/javascript/ql/lib/semmle/javascript/NodeJS.qll b/javascript/ql/lib/semmle/javascript/NodeJS.qll index 7e9e2fdea904..ae1ef8438301 100644 --- a/javascript/ql/lib/semmle/javascript/NodeJS.qll +++ b/javascript/ql/lib/semmle/javascript/NodeJS.qll @@ -37,14 +37,27 @@ class NodeModule extends Module { * into this module's `module.exports` property. */ pragma[noinline] - DefiniteAbstractValue getAModuleExportsValue() { - result = this.getAModuleExportsProperty().getAValue() + deprecated DefiniteAbstractValue getAModuleExportsValue() { none() } + + /** + * Gets the `SourceNode` corresponding to the value of `module`. + */ + private DataFlow::SourceNode getModuleSourceNode() { + result = DataFlow::ssaDefinitionNode(Ssa::implicitInit(this.getModuleVariable())) } - pragma[noinline] - private AbstractProperty getAModuleExportsProperty() { - result.getBase().(AbstractModuleObject).getModule() = this and - result.getPropertyName() = "exports" + /** + * Gets a `SourceNode` corresponding to the initial value of `module.exports` or + * anything assigned to `module.exports`. + */ + private DataFlow::SourceNode getExportsSourceNode() { + result = DataFlow::ssaDefinitionNode(Ssa::implicitInit(this.getExportsVariable())) + or + result = DataFlow::thisNode(this) // `this` is an alias for `module.exports` + or + result = this.getModuleSourceNode().getAPropertyWrite("exports").getRhs().getALocalSource() + or + result = this.getModuleSourceNode().getAPropertyRead("exports") } /** @@ -52,33 +65,16 @@ class NodeModule extends Module { * For performance this predicate only computes relevant expressions (in `getAModuleExportsCandidate`). * So if using this predicate - consider expanding the list of relevant expressions. */ - DataFlow::AnalyzedNode getAModuleExportsNode() { - result = getAModuleExportsCandidate() and - result.getAValue() = this.getAModuleExportsValue() - } - - /** Gets a symbol exported by this module. */ - override string getAnExportedSymbol() { - result = super.getAnExportedSymbol() - or - result = this.getAnImplicitlyExportedSymbol() - or - // getters and the like. - exists(DataFlow::PropWrite pwn | - pwn.getBase() = this.getAModuleExportsNode() and - result = pwn.getPropertyName() - ) + deprecated DataFlow::AnalyzedNode getAModuleExportsNode() { + result = this.getExportsSourceNode().getALocalUse() } override DataFlow::Node getAnExportedValue(string name) { // a property write whose base is `exports` or `module.exports` - exists(DataFlow::PropWrite pwn | result = pwn.getRhs() | - pwn.getBase() = this.getAModuleExportsNode() and - name = pwn.getPropertyName() - ) + result = this.getExportsSourceNode().getAPropertyWrite(name).getRhs() or // a re-export using spread-operator. E.g. `const foo = require("./foo"); module.exports = {bar: bar, ...foo};` - exists(ObjectExpr obj | obj = this.getAModuleExportsNode().asExpr() | + exists(ObjectExpr obj | obj = this.getExportsSourceNode().asExpr() | result = obj.getAProperty() .(SpreadProperty) @@ -99,16 +95,15 @@ class NodeModule extends Module { // } exists(DynamicPropertyAccess::EnumeratedPropName read, Import imp, DataFlow::PropWrite write | read.getSourceObject().getALocalSource().asExpr() = imp and + write = this.getExportsSourceNode().getAPropertyWrite() and getASourceProp(read) = write.getRhs() and - write.getBase() = this.getAModuleExportsNode() and write.getPropertyNameExpr().flow().getImmediatePredecessor*() = read and result = imp.getImportedModule().getAnExportedValue(name) ) or // an externs definition (where appropriate) exists(PropAccess pacc | result = DataFlow::valueNode(pacc) | - pacc.getBase() = this.getAModuleExportsNode().asExpr() and - name = pacc.getPropertyName() and + pacc = this.getExportsSourceNode().getAPropertyRead(name).asExpr() and this.isExterns() and exists(pacc.getDocumentation()) ) @@ -123,29 +118,6 @@ class NodeModule extends Module { ) } - /** Gets a symbol that the module object inherits from its prototypes. */ - private string getAnImplicitlyExportedSymbol() { - exists(ExternalConstructor ec | ec = this.getPrototypeOfExportedExpr() | - result = ec.getAMember().getName() - or - ec instanceof FunctionExternal and result = "prototype" - or - ec instanceof ArrayExternal and - exists(NumberLiteral nl | result = nl.getValue() and exists(result.toInt())) - ) - } - - /** Gets an externs declaration of the prototype object of a value exported by this module. */ - private ExternalConstructor getPrototypeOfExportedExpr() { - exists(AbstractValue exported | exported = this.getAModuleExportsValue() | - result instanceof ObjectExternal - or - exported instanceof AbstractFunction and result instanceof FunctionExternal - or - exported instanceof AbstractOtherObject and result instanceof ArrayExternal - ) - } - deprecated override predicate searchRoot(PathExpr path, Folder searchRoot, int priority) { path.getEnclosingModule() = this and exists(string pathval | pathval = path.getValue() | @@ -181,21 +153,6 @@ private DataFlow::SourceNode getASourceProp(DynamicPropertyAccess::EnumeratedPro ) } -/** - * Gets an expression that syntactically could be a alias for `module.exports`. - * This predicate exists to reduce the size of `getAModuleExportsNode`, - * while keeping all the tuples that could be relevant in later computations. - */ -pragma[noinline] -private DataFlow::Node getAModuleExportsCandidate() { - // A bit of manual magic - result = any(DataFlow::PropWrite w).getBase() - or - result = DataFlow::valueNode(any(PropAccess p | exists(p.getPropertyName())).getBase()) - or - result = DataFlow::valueNode(any(ObjectExpr obj)) -} - /** * Holds if `nodeModules` is a folder of the form `/node_modules`, where * `` is a (not necessarily proper) prefix of `f` and does not end in `/node_modules`, diff --git a/javascript/ql/lib/semmle/javascript/PackageExports.qll b/javascript/ql/lib/semmle/javascript/PackageExports.qll index b4032980688f..826e815d4c5c 100644 --- a/javascript/ql/lib/semmle/javascript/PackageExports.qll +++ b/javascript/ql/lib/semmle/javascript/PackageExports.qll @@ -237,8 +237,7 @@ private DataFlow::Node getAnExportFromModule(Module mod) { // exports saved to the global object result = DataFlow::globalObjectRef().getAPropertyWrite().getRhs() and result.getTopLevel() = mod - or - result.analyze().getAValue() = TAbstractModuleObject(mod) + // TODO: perhaps rely on name resolution here? } /** diff --git a/javascript/ql/lib/semmle/javascript/dataflow/AbstractValues.qll b/javascript/ql/lib/semmle/javascript/dataflow/AbstractValues.qll index 41509516cc16..9d474e39219d 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/AbstractValues.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/AbstractValues.qll @@ -390,7 +390,7 @@ class AbstractGlobalObject extends DefiniteAbstractValue, TAbstractGlobalObject /** * An abstract value representing a CommonJS `module` object. */ -class AbstractModuleObject extends DefiniteAbstractValue, TAbstractModuleObject { +deprecated class AbstractModuleObject extends DefiniteAbstractValue, TAbstractModuleObject { /** Gets the module whose `module` object this abstract value represents. */ Module getModule() { this = TAbstractModuleObject(result) } @@ -414,7 +414,7 @@ class AbstractModuleObject extends DefiniteAbstractValue, TAbstractModuleObject /** * An abstract value representing a CommonJS `exports` object. */ -class AbstractExportsObject extends DefiniteAbstractValue, TAbstractExportsObject { +deprecated class AbstractExportsObject extends DefiniteAbstractValue, TAbstractExportsObject { /** Gets the module whose `exports` object this abstract value represents. */ Module getModule() { this = TAbstractExportsObject(result) } diff --git a/javascript/ql/lib/semmle/javascript/dataflow/TypeInference.qll b/javascript/ql/lib/semmle/javascript/dataflow/TypeInference.qll index 32ad78eb2c6d..58021952a224 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/TypeInference.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/TypeInference.qll @@ -30,7 +30,6 @@ import AbstractValues private import InferredTypes private import Refinements import internal.BasicExprTypeInference -import internal.InterModuleTypeInference import internal.InterProceduralTypeInference import internal.PropertyTypeInference import internal.VariableTypeInference @@ -170,6 +169,8 @@ class AnalyzedNode extends DataFlow::Node { class AnalyzedValueNode extends AnalyzedNode, DataFlow::ValueNode { } /** + * DEPRECATED. Type inference is no longer used for reasoning about module exports. + * * A module for which analysis results are available. * * The type inference supports AMD, CommonJS and ES2015 modules. All three @@ -178,7 +179,7 @@ class AnalyzedValueNode extends AnalyzedNode, DataFlow::ValueNode { } * exports are modeled as property writes on `module.exports`, and imports * as property reads on any potential value of `module.exports`. */ -class AnalyzedModule extends TopLevel instanceof Module { +deprecated class AnalyzedModule extends TopLevel instanceof Module { /** Gets the name of this module. */ string getName() { result = super.getName() } diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/AbstractPropertiesImpl.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/AbstractPropertiesImpl.qll index 6d86e7ea3f25..8d41dd52d091 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/AbstractPropertiesImpl.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/AbstractPropertiesImpl.qll @@ -30,13 +30,6 @@ newtype TAbstractProperty = * of the concrete objects represented by `baseVal`. */ AbstractValue getAnInitialPropertyValue(DefiniteAbstractValue baseVal, string propertyName) { - // initially, `module.exports === exports` - exists(Module m | - baseVal = TAbstractModuleObject(m) and - propertyName = "exports" and - result = TAbstractExportsObject(m) - ) - or // class members result = getAnInitialMemberValue(getMember(baseVal, propertyName)) or @@ -77,11 +70,7 @@ private AbstractValue getAnInitialMemberValue(MemberDefinition m) { * Holds if `baseVal` is an abstract value whose properties we track for the purposes * of `getALocalValue`. */ -predicate shouldAlwaysTrackProperties(AbstractValue baseVal) { - baseVal instanceof AbstractModuleObject or - baseVal instanceof AbstractExportsObject or - baseVal instanceof AbstractCallable -} +predicate shouldAlwaysTrackProperties(AbstractValue baseVal) { baseVal instanceof AbstractCallable } /** Holds if `baseVal` is an abstract value whose properties we track. */ predicate shouldTrackProperties(AbstractValue baseVal) { diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/AbstractValuesImpl.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/AbstractValuesImpl.qll index 6bcef1dc4128..31e8c1ac9ed7 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/AbstractValuesImpl.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/AbstractValuesImpl.qll @@ -43,10 +43,10 @@ newtype TAbstractValue = TAbstractArguments(Function f) { exists(f.getArgumentsVariable().getAnAccess()) } or /** An abstract representation of the global object. */ TAbstractGlobalObject() or - /** An abstract representation of a `module` object. */ - TAbstractModuleObject(Module m) or - /** An abstract representation of a `exports` object. */ - TAbstractExportsObject(Module m) or + /** DEPRECATED. This abstract value is no longer tracked. */ + deprecated TAbstractModuleObject(Module m) { none() } or + /** DEPRECATED. This abstract value is no longer tracked. */ + deprecated TAbstractExportsObject(Module m) { none() } or /** An abstract representation of all objects arising from an object literal expression. */ TAbstractObjectLiteral(ObjectExpr oe) or /** diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll index cc4c883381ea..9ccef8fd5682 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll @@ -5,13 +5,96 @@ private import javascript private import semmle.javascript.dataflow.internal.StepSummary private import semmle.javascript.dataflow.internal.PreCallGraphStep +private import semmle.javascript.dataflow.internal.FlowSteps +private import semmle.javascript.internal.NameResolution cached module CallGraph { + pragma[nomagic] + private predicate shouldBackTrack(DataFlow::SourceNode node) { + exists(DataFlow::Node rhs | rhs = node.getAPropertyWrite().getRhs().getALocalSource() | + track(_) = rhs + ) + } + + pragma[nomagic] + private predicate methodHostToReceiverStep(DataFlow::SourceNode node1, DataFlow::SourceNode node2) { + exists(DataFlow::PropWrite write | + node1 = write.getBase().getALocalSource() and + node2 = write.getRhs().getALocalSource().(DataFlow::FunctionNode).getReceiver() + ) + } + + pragma[inline] + private predicate step(DataFlow::SourceNode node1, DataFlow::SourceNode node2) { + AccessPath::step(node1.getALocalUse(), node2) + or + propertyFlowStep(node1.getALocalUse(), node2) + or + storeReadStep(node1, node2) + or + methodHostToReceiverStep(node1, node2) + } + + pragma[nomagic] + private DataFlow::SourceNode backtrackStoreTarget() { + shouldBackTrack(result) + or + step(result, backtrackStoreTarget()) + } + + pragma[nomagic] + private predicate shouldTrack(DataFlow::SourceNode node) { + node instanceof DataFlow::FunctionNode + or + node instanceof DataFlow::ClassNode + or + ( + node instanceof DataFlow::ObjectLiteralNode or + node instanceof DataFlow::InvokeNode or + node instanceof DataFlow::ArrayLiteralNode + ) and + backtrackStoreTarget() = node + } + + pragma[nomagic] + private predicate deepStore(DataFlow::SourceNode node1, string prop, DataFlow::SourceNode node2) { + track(node2).getAPropertyWrite(prop).getRhs().getALocalSource() = node1 + } + + pragma[nomagic] + private predicate deepRead(DataFlow::SourceNode node1, string prop, DataFlow::SourceNode node2) { + track(node1).getAPropertyRead(prop) = node2 + } + + pragma[nomagic] + private predicate storeReadStep(DataFlow::SourceNode node1, DataFlow::SourceNode node2) { + exists(DataFlow::SourceNode base, string name | + deepStore(node1, name, base) and + deepRead(base, name, node2) + ) + } + + pragma[nomagic] + private DataFlow::SourceNode track(DataFlow::SourceNode source) { + shouldTrack(source) and + result = source + or + step(track(source), result) + } + /** Gets the function referenced by `node`, as determined by the type inference. */ cached Function getAFunctionValue(AnalyzedNode node) { - result = node.getAValue().(AbstractCallable).getFunction() + exists(DataFlow::FunctionNode func | + result = func.getFunction() and + track(func).getALocalUse() = node + ) + or + exists(DataFlow::ClassNode cls | + result = cls.getConstructor().getFunction() and + track(cls).getALocalUse() = node + ) } /** Holds if the type inferred for `node` is indefinite due to global flow. */ diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/FlowSteps.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/FlowSteps.qll index 1711faa4adeb..79181ee91386 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/FlowSteps.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/FlowSteps.qll @@ -8,16 +8,7 @@ import javascript deprecated import semmle.javascript.dataflow.Configuration import semmle.javascript.dataflow.internal.CallGraphs private import semmle.javascript.internal.CachedStages - -/** - * Holds if flow should be tracked through properties of `obj`. - * - * Flow is tracked through `module` and `module.exports` objects. - */ -predicate shouldTrackProperties(AbstractValue obj) { - obj instanceof AbstractExportsObject or - obj instanceof AbstractModuleObject -} +private import semmle.javascript.internal.NameResolution /** * Holds if `source` corresponds to an expression returned by `f`, and @@ -337,28 +328,22 @@ private module CachedSteps { ) } - /** - * Holds if there is an assignment to property `prop` of an object represented by `obj` - * with right hand side `rhs` somewhere, and properties of `obj` should be tracked. - */ - pragma[noinline] - private predicate trackedPropertyWrite(AbstractValue obj, string prop, DataFlow::Node rhs) { - exists(AnalyzedPropertyWrite pw | - pw.writes(obj, prop, rhs) and - shouldTrackProperties(obj) and - // avoid introducing spurious global flow - not pw.baseIsIncomplete("global") - ) - } - /** * Holds if there is a flow step from `pred` to `succ` through an object property. */ cached predicate propertyFlowStep(DataFlow::Node pred, DataFlow::Node succ) { - exists(AbstractValue obj, string prop | - trackedPropertyWrite(obj, prop, pred) and - succ.(AnalyzedPropertyRead).reads(obj, prop) + // TODO: Ensure name resolution has good enough support for NodeJS and AMD + exists(NameResolution::Node node1, NameResolution::Node node2 | + NameResolution::ValueFlow::resolvedReadStep(node1, node2) and + pred = node1.toDataFlowNodeIn() and + succ = node2.toDataFlowNodeOut() + ) + or + exists(Import imprt, Module mod | + imprt.getImportedModule() = mod and + pred = NameResolution::getModuleBulkExport(mod).toDataFlowNodeIn() and + succ = imprt.getImportedModuleNodeStrict() ) } diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll deleted file mode 100644 index 3134d76d8f16..000000000000 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll +++ /dev/null @@ -1,433 +0,0 @@ -/** - * INTERNAL: Do not use directly; use `semmle.javascript.dataflow.TypeInference` instead. - * - * Provides classes implementing type inference across imports. - */ - -private import javascript -private import AbstractValuesImpl -private import semmle.javascript.dataflow.InferredTypes -private import AbstractPropertiesImpl - -/** - * Flow analysis for ECMAScript 2015 imports as variable definitions. - */ -private class AnalyzedImportSpecifier extends AnalyzedVarDef, @import_specifier { - ImportDeclaration id; - - AnalyzedImportSpecifier() { - this = id.getASpecifier() and - exists(id.resolveImportedPath()) and - not this.(ImportSpecifier).isTypeOnly() - } - - override DataFlow::AnalyzedNode getRhs() { result.(AnalyzedImport).getImportSpecifier() = this } - - override predicate isIncomplete(DataFlow::Incompleteness cause) { - // mark as incomplete if the import could rely on the lookup path - mayDependOnLookupPath(id.getImportedPathString()) and - cause = "import" - or - // mark as incomplete if we cannot fully analyze this import - exists(Module m | m = id.resolveImportedPath() | - mayDynamicallyComputeExports(m) - or - incompleteExport(m, this.(ImportSpecifier).getImportedName()) - ) and - cause = "import" - } -} - -/** - * Holds if resolving `path` may depend on the lookup path, that is, - * it does not start with `.` or `/`. - */ -bindingset[path] -private predicate mayDependOnLookupPath(string path) { - exists(string firstChar | firstChar = path.charAt(0) | firstChar != "." and firstChar != "/") -} - -/** - * Holds if `m` may dynamically compute its exports. - */ -private predicate mayDynamicallyComputeExports(Module m) { - // if `m` accesses its `module` or `exports` variable, we conservatively assume the worst; - // in particular, this makes all imports from CommonJS modules indefinite - exists(Variable v, VarAccess va | v.getName() = "module" or v.getName() = "exports" | - va = v.getAnAccess() and - ( - v = m.getScope().getAVariable() - or - // be conservative in case our heuristics for detecting Node.js modules fail - v instanceof GlobalVariable and va.getTopLevel() = m - ) - ) - or - // AMD modules can export arbitrary objects, so an import is essentially a property read - // and hence must be considered indefinite - m instanceof AmdModule - or - // `m` re-exports all exports of some other module that dynamically computes its exports - exists(BulkReExportDeclaration rexp | rexp = m.(ES2015Module).getAnExport() | - mayDynamicallyComputeExports(rexp.getReExportedModule()) - ) -} - -/** - * Holds if `x` is imported from `m`, possibly through a chain of re-exports. - */ -private predicate relevantExport(ES2015Module m, string x) { - exists(ImportDeclaration id | - id.getImportedModule() = m and - x = id.getASpecifier().getImportedName() - ) - or - exists(ReExportDeclaration rexp | - rexp.getReExportedModule() = m and - reExportsAs(rexp, x, _) - ) -} - -/** - * Holds if `rexp` re-exports `x` as `y`. - */ -private predicate reExportsAs(ReExportDeclaration rexp, string x, string y) { - relevantExport(rexp.getEnclosingModule(), y) and - ( - exists(ExportSpecifier spec | spec = rexp.(SelectiveReExportDeclaration).getASpecifier() | - x = spec.getLocalName() and - y = spec.getExportedName() - ) - or - rexp instanceof BulkReExportDeclaration and - x = y - ) -} - -/** - * Holds if `m` re-exports `y`, but we cannot fully analyze this export. - */ -private predicate incompleteExport(ES2015Module m, string y) { - exists(ReExportDeclaration rexp | rexp = m.getAnExport() | - exists(string x | reExportsAs(rexp, x, y) | - // path resolution could rely on lookup path - mayDependOnLookupPath(rexp.getImportedPath().getStringValue()) - or - // unresolvable path - not exists(rexp.getReExportedModule()) - or - exists(Module n | n = rexp.getReExportedModule() | - // re-export from CommonJS/AMD - mayDynamicallyComputeExports(n) - or - // recursively incomplete - incompleteExport(n, x) - ) - ) - or - // namespace re-export - exists(ExportNamespaceSpecifier ns | - ns.getExportDeclaration() = rexp and - ns.getExportedName() = y - ) - ) -} - -/** - * Flow analysis for import specifiers, interpreted as implicit reads of - * properties of the `module.exports` object of the imported module. - */ -private class AnalyzedImport extends AnalyzedPropertyRead, DataFlow::ValueNode { - Module imported; - override ImportSpecifier astNode; - - AnalyzedImport() { - exists(ImportDeclaration id | - astNode = id.getASpecifier() and - not astNode.isTypeOnly() and - imported = id.getImportedModule() - ) - } - - /** Gets the import specifier being analyzed. */ - ImportSpecifier getImportSpecifier() { result = astNode } - - override predicate reads(AbstractValue base, string propName) { - exists(AbstractProperty exports | - exports = MkAbstractProperty(TAbstractModuleObject(imported), "exports") - | - base = exports.getALocalValue() and - propName = astNode.getImportedName() - ) - or - // when importing CommonJS/AMD modules from ES2015, `module.exports` appears - // as the default export - ( - not imported instanceof ES2015Module - or - // CommonJS/AMD module generated by TypeScript compiler - imported.getAStmt() instanceof ExportAssignDeclaration - ) and - astNode.getImportedName() = "default" and - base = TAbstractModuleObject(imported) and - propName = "exports" - } -} - -/** - * Flow analysis for namespace imports. - */ -private class AnalyzedNamespaceImport extends AnalyzedImport { - override ImportNamespaceSpecifier astNode; - - override predicate reads(AbstractValue base, string propName) { - base = TAbstractModuleObject(imported) and - propName = "exports" - } -} - -/** - * Flow analysis for namespace imports. - */ -private class AnalyzedDestructuredImport extends AnalyzedPropertyRead { - Module imported; - - AnalyzedDestructuredImport() { - exists(ImportDeclaration id | - this = DataFlow::destructuredModuleImportNode(id) and - imported = id.getImportedModule() - ) - } - - override predicate reads(AbstractValue base, string propName) { - base = TAbstractModuleObject(imported) and - propName = "exports" - } -} - -/** - * A call to `require`, interpreted as an implicit read of - * the `module.exports` property of the imported module. - */ -class AnalyzedRequireCall extends AnalyzedPropertyRead, DataFlow::ValueNode { - Module required; - - AnalyzedRequireCall() { required = astNode.(Require).getImportedModule() } - - override predicate reads(AbstractValue base, string propName) { - base = TAbstractModuleObject(required) and - propName = "exports" - } -} - -/** - * A special TypeScript `require` call in an import-assignment, - * interpreted as an implicit of the `module.exports` property of the imported module. - */ -class AnalyzedExternalModuleReference extends AnalyzedPropertyRead, DataFlow::ValueNode { - Module required; - - AnalyzedExternalModuleReference() { - required = astNode.(ExternalModuleReference).getImportedModule() - } - - override predicate reads(AbstractValue base, string propName) { - base = TAbstractModuleObject(required) and - propName = "exports" - } -} - -/** - * Flow analysis for AMD exports. - */ -private class AnalyzedAmdExport extends AnalyzedPropertyWrite, DataFlow::ValueNode { - AmdModule amd; - - AnalyzedAmdExport() { astNode = amd.getDefine().getModuleExpr() } - - override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) { - baseVal = TAbstractModuleObject(amd) and - propName = "exports" and - source = this - } -} - -/** - * Flow analysis for AMD imports, interpreted as an implicit read of - * the `module.exports` property of the imported module. - */ -private class AnalyzedAmdImport extends AnalyzedPropertyRead, DataFlow::Node { - Module required; - - AnalyzedAmdImport() { - exists(AmdModule amd, Expr dep | - exists(Parameter p | - amd.getDefine().dependencyParameter(dep, p) and - this = DataFlow::parameterNode(p) - ) - or - exists(CallExpr requireCall | - requireCall = amd.getDefine().getARequireCall() and - dep = requireCall.getAnArgument() and - this = requireCall.flow() - ) - | - required = dep.(Import).getImportedModule() - ) - } - - override predicate reads(AbstractValue base, string propName) { - base = TAbstractModuleObject(required) and - propName = "exports" - } -} - -/** - * Flow analysis for parameters corresponding to AMD imports. - */ -private class AnalyzedAmdParameter extends AnalyzedVarDef, @var_decl { - AnalyzedAmdImport imp; - - AnalyzedAmdParameter() { imp = DataFlow::parameterNode(this) } - - override AbstractValue getAnRhsValue() { result = imp.getALocalValue() } -} - -/** - * Flow analysis for exports that export a single value. - */ -private class AnalyzedValueExport extends AnalyzedPropertyWrite, DataFlow::ValueNode { - ExportDeclaration export; - string name; - - AnalyzedValueExport() { this = export.getSourceNode(name) } - - override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) { - baseVal = TAbstractExportsObject(export.getEnclosingModule()) and - propName = name and - source = export.getSourceNode(name).analyze() - } -} - -/** - * Flow analysis for exports that export a binding. - */ -private class AnalyzedVariableExport extends AnalyzedPropertyWrite, DataFlow::ValueNode { - ExportDeclaration export; - string name; - AnalyzedVarDef varDef; - - AnalyzedVariableExport() { - export.exportsAs(varDef.getAVariable(), name) and - astNode = varDef.getTarget() - } - - override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) { - baseVal = TAbstractExportsObject(export.getEnclosingModule()) and - propName = name and - ( - source = varDef.getSource().analyze() - or - varDef.getTarget() instanceof DestructuringPattern and - source = export.getSourceNode(propName) - ) - } - - override predicate writesValue(AbstractValue baseVal, string propName, AbstractValue val) { - baseVal = TAbstractExportsObject(export.getEnclosingModule()) and - propName = name and - val = varDef.getAnAssignedValue() - } -} - -/** - * Flow analysis for default exports. - */ -private class AnalyzedDefaultExportDeclaration extends AnalyzedValueExport { - override ExportDefaultDeclaration export; - - override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) { - super.writes(baseVal, propName, source) - or - // some (presumably historic) transpilers treat `export default foo` as `module.exports = foo`, - // so allow that semantics, too, but only if there isn't a named export in the same module. - exists(Module m | - super.writes(TAbstractExportsObject(m), "default", source) and - baseVal = TAbstractModuleObject(m) and - propName = "exports" and - not m.(ES2015Module).getAnExport() instanceof ExportNamedDeclaration - ) - } -} - -/** - * Flow analysis for TypeScript export assignments. - */ -private class AnalyzedExportAssign extends AnalyzedPropertyWrite, DataFlow::ValueNode { - ExportAssignDeclaration exportAssign; - - AnalyzedExportAssign() { astNode = exportAssign.getExpression() } - - override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) { - baseVal = TAbstractModuleObject(exportAssign.getTopLevel()) and - propName = "exports" and - source = this - } -} - -/** - * Flow analysis for assignments to the `exports` variable in a Closure module. - */ -private class AnalyzedClosureExportAssign extends AnalyzedPropertyWrite, DataFlow::ValueNode { - override AssignExpr astNode; - - AnalyzedClosureExportAssign() { - astNode.getLhs() = any(Closure::ClosureModule mod).getExportsVariable().getAReference() - } - - override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) { - baseVal = TAbstractModuleObject(astNode.getTopLevel()) and - propName = "exports" and - source = astNode.getRhs().flow() - } -} - -/** - * Read of a global access path exported by a Closure library. - * - * This adds a direct flow edge to the assigned value. - */ -private class AnalyzedClosureGlobalAccessPath extends AnalyzedNode, AnalyzedPropertyRead { - string accessPath; - - AnalyzedClosureGlobalAccessPath() { - accessPath = Closure::getClosureNamespaceFromSourceNode(this) - } - - override predicate reads(AbstractValue base, string propName) { - exists(Closure::ClosureModule mod | - mod.getClosureNamespace() = accessPath and - base = TAbstractModuleObject(mod) and - propName = "exports" - ) - } -} - -/** - * A namespace export declaration analyzed as a property write. - */ -private class AnalyzedExportNamespaceSpecifier extends AnalyzedPropertyWrite, DataFlow::ValueNode { - override ExportNamespaceSpecifier astNode; - ReExportDeclaration decl; - - AnalyzedExportNamespaceSpecifier() { - decl = astNode.getExportDeclaration() and - not decl.isTypeOnly() - } - - override predicate writesValue(AbstractValue baseVal, string propName, AbstractValue value) { - baseVal = TAbstractExportsObject(this.getTopLevel()) and - propName = astNode.getExportedName() and - value = TAbstractExportsObject(decl.getReExportedModule()) - } -} diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll index 80de3cb64fdd..02d4ca08d0a4 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll @@ -45,19 +45,6 @@ private class AnalyzedThisInBoundFunction extends AnalyzedThisExpr { } } -/** - * Flow analysis for `this` expressions in node modules. - * - * These expressions are assumed to refer to the `module.exports` object. - */ -private class AnalyzedThisAsModuleExports extends DataFlow::AnalyzedNode, DataFlow::ThisNode { - NodeModule m; - - AnalyzedThisAsModuleExports() { m = this.getBindingContainer() } - - override AbstractValue getALocalValue() { result = TAbstractExportsObject(m) } -} - /** * Flow analysis for `this` expressions inside a function that is instantiated. * diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/VariableTypeInference.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/VariableTypeInference.qll index bbbd967fda80..f2a67ffd8fd7 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/VariableTypeInference.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/VariableTypeInference.qll @@ -159,28 +159,6 @@ private class AnalyzedRestParameter extends AnalyzedValueNode { override AbstractValue getALocalValue() { result = TAbstractOtherObject() } } -/** - * Flow analysis for `module` and `exports` parameters of AMD modules. - */ -private class AnalyzedAmdParameter extends AnalyzedVarDef { - AbstractValue implicitInitVal; - - AnalyzedAmdParameter() { - exists(AmdModule m, AmdModuleDefinition mdef | mdef = m.getDefine() | - this = mdef.getModuleParameter() and - implicitInitVal = TAbstractModuleObject(m) - or - this = mdef.getExportsParameter() and - implicitInitVal = TAbstractExportsObject(m) - ) - } - - override AbstractValue getAnAssignedValue() { - result = super.getAnAssignedValue() or - result = implicitInitVal - } -} - /** * An SSA definitions that has been analyzed. */ @@ -355,10 +333,6 @@ private predicate nodeBuiltins(Variable var, AbstractValue av) { exists(Module m, string name | var = m.getScope().getVariable(name) | name = "require" and av = TIndefiniteAbstractValue("heap") or - name = "module" and av = TAbstractModuleObject(m) - or - name = "exports" and av = TAbstractExportsObject(m) - or name = "arguments" and av = TAbstractOtherObject() or (name = "__filename" or name = "__dirname") and diff --git a/javascript/ql/lib/semmle/javascript/internal/NameResolution.qll b/javascript/ql/lib/semmle/javascript/internal/NameResolution.qll index b25c98fd693b..4cd2b78ac0a7 100644 --- a/javascript/ql/lib/semmle/javascript/internal/NameResolution.qll +++ b/javascript/ql/lib/semmle/javascript/internal/NameResolution.qll @@ -34,6 +34,27 @@ module NameResolution { or result = this.(JSDocTypeExpr).getLocation() } + + DataFlow::Node toDataFlowNodeIn() { + result = DataFlow::valueNode(this) + or + result = DataFlow::valueNode(this.(Variable).getAnAssignedExpr()) + or + result = DataFlow::valueNode(any(ClassDefinition def | this = def.getVariable())) + or + result = DataFlow::valueNode(any(FunctionDeclStmt def | this = def.getVariable())) + } + + DataFlow::Node toDataFlowNodeOut() { + result = DataFlow::valueNode(this) + or + result = DataFlow::valueNode(this.(Variable).getAnAccess()) + or + exists(ImportSpecifier spec | + this = spec.getLocal() and + result = DataFlow::valueNode(spec) + ) + } } private signature predicate nodeSig(Node node); @@ -73,7 +94,7 @@ module NameResolution { * * May also include some type-specific steps in cases where this is harmless when tracking values. */ - private predicate commonStep(Node node1, Node node2) { + private predicate commonStep1(Node node1, Node node2) { // Import paths are part of the graph and has an incoming edge from the imported module, if found. // This ensures we can also use the PathExpr as a source when working with external (unresolved) modules. exists(Import imprt | @@ -81,6 +102,19 @@ module NameResolution { node2 = imprt.getImportedPathExpr() ) or + // Same as above, but for re-export declarations + exists(ReExportDeclaration exprt | + node1 = exprt.getReExportedModule() and + node2 = exprt.getImportedPath() + ) + or + // For imports like `require()` that are just expressions evaluating to the imported module. + exists(Import imprt | + imprt.getImportedModuleNode() = DataFlow::valueNode(imprt) and + node1 = imprt.getImportedPathExpr() and + node2 = imprt + ) + or exists(ImportNamespaceSpecifier spec | node1 = spec.getImportDeclaration().getImportedPathExpr() and node2 = spec.getLocal() @@ -173,15 +207,22 @@ module NameResolution { node2 = req ) or - exists(Closure::ClosureModule mod | - node1 = mod.getExportsVariable() and - node2 = mod - ) - or exists(ImmediatelyInvokedFunctionExpr fun, int i | node1 = fun.getArgument(i) and node2 = fun.getParameter(i) ) + or + defaultImportInteropStep(node1, node2) + } + + pragma[inline] + private predicate commonStep(Node node1, Node node2) { + commonStep1(node1, node2) + or + exists(ModuleLike mod | + node1 = exportsObjectRhs(mod) and + node2 = mod + ) } /** @@ -209,7 +250,8 @@ module NameResolution { exists(PropAccess access | node1 = access.getBase() and name = access.getPropertyName() and - node2 = access + node2 = access and + access instanceof RValue ) or exists(ObjectPattern pattern | @@ -250,6 +292,89 @@ module NameResolution { ) } + /** + * A step that treats imports of form `import foo from "somewhere"` as a namespace import, + * to support some non-standard compilers. + */ + private predicate defaultImportInteropStep(Node node1, Node node2) { + exists(ImportDefaultSpecifier spec | + node1 = spec.getImportDeclaration().getImportedPathExpr() and + node2 = spec.getLocal() + ) + } + + pragma[nomagic] + private GlobalVarAccess globalAccess(Module mod, string name) { + result.getName() = name and + result.getTopLevel() = mod and + name = ["exports", "module"] // manually restrict size of predicate + } + + /** Gets a reference to the CommonJS `module` object within the given module. */ + private Node moduleObjectRef(Module mod) { + result = mod.getScope().getVariable("module").getAnAccess() + or + result = globalAccess(mod, "module") + or + commonStep1(moduleObjectRef(mod), result) + } + + /** Gets the right-hand side of an assignment to `module.exports` within the given module. */ + private Node exportsObjectRhs(Module mod) { + exists(AssignExpr assign | + assign.getLhs().(PropAccess).accesses(moduleObjectRef(mod), "exports") and + result = assign.getRhs() + ) + or + result = mod.(Closure::ClosureModule).getExportsVariable().getAnAssignedExpr() + or + exists(ExportDefaultDeclaration exprt | + mod instanceof Closure::ClosureModule and + exprt.getContainer() = mod and + result = exprt.getOperand() + ) + or + result = mod.(AmdModule).getDefine().getFactoryFunction().getAReturnedExpr() + } + + /** Gets a node that is bulk-exported from the given module. */ + Node getModuleBulkExport(ModuleLike mod) { result = exportsObjectRhs(mod) } + + /** Gets a node that flows to `module.exports` within the given module. */ + private Node exportsObjectRhsPred(Module mod) { commonStep1*(result, exportsObjectRhs(mod)) } + + /** Gets a node that is an alias for `module.exports` within the given module. */ + private Node exportsObjectAlias(Module mod) { + result = mod.getScope().getVariable("exports").getAnAccess() + or + result = globalAccess(mod, "exports") + or + result.(ThisExpr).getBindingContainer() = mod and + mod instanceof NodeModule + or + readStep(moduleObjectRef(mod), "exports", result) + or + result = mod.(AmdModule).getDefine().getExportsParameter() + or + result = exportsObjectRhsPred(mod) + or + commonStep(exportsObjectAlias(mod), result) + } + + /** Holds if `node` is stored into `module.exports.` within the given module. */ + private predicate storeToExports(Node node, Module mod, string name) { + exists(AssignExpr assign | + node = assign.getRhs() and + assign.getLhs().(PropAccess).accesses(exportsObjectAlias(mod), name) + ) + or + exists(Property prop | + node = prop.getInit() and + name = prop.getName() and + prop.getObjectExpr() = exportsObjectAlias(mod) + ) + } + private signature module TypeResolutionInputSig { /** * Holds if flow is permitted through the given variable. @@ -257,19 +382,10 @@ module NameResolution { predicate isRelevantVariable(LexicalName var); } - /** - * A local variable with exactly one definition, not counting implicit initialization. - */ - private class EffectivelyConstantVariable extends LocalVariableLike { - EffectivelyConstantVariable() { - count(SsaExplicitDefinition ssa | ssa.getSourceVariable() = this) <= 1 // count may be zero if ambient - } - } - /** Configuration for propagating values and namespaces */ private module ValueConfig implements TypeResolutionInputSig { predicate isRelevantVariable(LexicalName var) { - var instanceof EffectivelyConstantVariable + var instanceof LocalVariableLike or // We merge the namespace and value declaration spaces as it seems there is // no need to distinguish them in practice. @@ -328,6 +444,12 @@ module NameResolution { S::isRelevantVariable(result) ) or + exists(ExportDefaultDeclaration exprt | + mod = exprt.getContainer() and + name = "default" and + result = exprt.getOperand() + ) + or exists(ExportNamespaceSpecifier spec | result = spec and mod = spec.getContainer() and @@ -349,28 +471,13 @@ module NameResolution { result = enum.getMemberByName(name).getIdentifier() ) or - storeToVariable(result, name, mod.(Closure::ClosureModule).getExportsVariable()) - or exists(DynamicImportExpr imprt | mod = imprt and name = "$$promise-content" and result = imprt.getImportedPathExpr() ) - } - - /** - * Holds if `value` is stored in `target.prop`. Only needs to recognise assignments - * that are also recognised by JSDoc tooling such as the Closure compiler. - */ - private predicate storeToVariable(Expr value, string prop, LocalVariableLike target) { - exists(AssignExpr assign | - // target.name = value - assign.getLhs().(PropAccess).accesses(target.getAnAccess(), prop) and - value = assign.getRhs() - ) or - // target = { name: value } - value = target.getAnAssignedExpr().(ObjectExpr).getPropertyByName(prop).getInit() + storeToExports(result, mod, name) } /** Steps that only apply for this configuration. */ @@ -386,6 +493,10 @@ module NameResolution { node2.(JSDocLocalTypeAccess).getALexicalName() = var ) or + resolvedReadStep(node1, node2) + } + + predicate resolvedReadStep(Node node1, Node node2) { exists(Node base, string name, ModuleLike mod | readStep(base, name, node2) and base = trackModule(mod) and @@ -430,7 +541,20 @@ module NameResolution { /** * Gets a node to which the given module flows. */ - predicate trackModule = ValueFlow::TrackNode::track/1; + Node trackModule(ModuleLike mod) { + result = mod + or + exists(Node prev | + prev = trackModule(mod) and + ValueFlow::step(prev, result) and + not defaultImportInteropStep(prev, result) + ) + or + defaultImportInteropStep(trackModule(mod), result) and + not mod.(ES2015Module).hasBothNamedAndDefaultExports() + or + result = exportsObjectAlias(mod) + } predicate trackClassValue = ValueFlow::TrackNode::track/1; diff --git a/javascript/ql/src/NodeJS/DubiousImport.qhelp b/javascript/ql/src/NodeJS/DubiousImport.qhelp deleted file mode 100644 index db3a39b2b3b7..000000000000 --- a/javascript/ql/src/NodeJS/DubiousImport.qhelp +++ /dev/null @@ -1,47 +0,0 @@ - - - -

-Since JavaScript is a dynamically typed language, module imports in node.js are not statically checked -for correctness: calls to require simply return an object containing all the exports of -the imported module, and accessing a member that was not, in fact, exported, yields -undefined. This is most likely unintentional and usually indicates a bug. -

- -
- - -

-Examine the import in question and determine the correct name of the symbol to import. -

- -
- - -

-In the following example, module point.js exports the function Point by -assigning it to module.exports. The client module client.js tries to -import it by reading from the Point property, but since this property does not exist -the result will be undefined, and the new invocation will fail. -

- - - -

-Instead of reading the Point property, client.js should directly use -the result of the require call: -

- - - -
- - - -
  • Node.js Manual: Modules.
  • - - -
    -
    diff --git a/javascript/ql/src/NodeJS/DubiousImport.ql b/javascript/ql/src/NodeJS/DubiousImport.ql deleted file mode 100644 index 97ed336e22bc..000000000000 --- a/javascript/ql/src/NodeJS/DubiousImport.ql +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @name Dubious import - * @description Importing a symbol from a module that does not export it most likely indicates a bug. - * @kind problem - * @problem.severity warning - * @id js/node/import-without-export - * @tags reliability - * maintainability - * frameworks/node.js - * @precision low - */ - -import javascript - -/** Holds if `m` is likely to have exports that are not picked up by the analysis. */ -predicate hasUntrackedExports(NodeModule m) { - // look for assignments of the form `module.exports[p] = ...`, where we cannot - // determine the name of the exported property being assigned - exists(DataFlow::PropWrite pwn | - pwn.getBase().analyze().getAValue() = m.getAModuleExportsValue() and - not exists(pwn.getPropertyName()) - ) - or - // look for assignments of the form `module.exports = exp` where `exp` is indefinite - exists(AbstractModuleObject am, AnalyzedPropertyWrite apw, DataFlow::AnalyzedNode exp | - am.getModule() = m and - apw.writes(am, "exports", exp) and - exp.getAValue().isIndefinite(_) - ) - or - // look for function calls of the form `f(module.exports)` - exists(InvokeExpr invk | invk.getAnArgument().analyze().getAValue() = m.getAModuleExportsValue()) -} - -/** - * Holds if there is an assignment anywhere defining `prop` on the result of - * a `require` import of module `m`. - */ -predicate propDefinedOnRequire(NodeModule m, string prop) { - exists(DataFlow::ModuleImportNode imp | - imp.asExpr().(Require).getImportedModule() = m and - exists(imp.getAPropertyWrite(prop)) - ) -} - -/** - * Holds if the base expression of `pacc` could refer to the result of - * a `require` import of module `m`. - */ -predicate propAccessOn(PropAccess pacc, NodeModule m) { - exists(DataFlow::ModuleImportNode imp | - imp.asExpr().(Require).getImportedModule() = m and - imp.flowsToExpr(pacc.getBase()) - ) -} - -from NodeModule m, PropAccess pacc, string prop -where - propAccessOn(pacc, m) and - count(NodeModule mm | propAccessOn(pacc, mm)) = 1 and - prop = pacc.getPropertyName() and - // m doesn't export 'prop' - not prop = m.getAnExportedSymbol() and - // 'prop' isn't otherwise defined on m - not propDefinedOnRequire(m, prop) and - // m doesn't use complicated exports - not hasUntrackedExports(m) -select pacc, "Module $@ does not export symbol " + prop + ".", m, m.getName() diff --git a/javascript/ql/test/library-tests/Closure/tests/importFromEs6.js b/javascript/ql/test/library-tests/Closure/tests/importFromEs6.js index 5e380984817e..775f3cd264b0 100644 --- a/javascript/ql/test/library-tests/Closure/tests/importFromEs6.js +++ b/javascript/ql/test/library-tests/Closure/tests/importFromEs6.js @@ -1,10 +1,10 @@ // ES6 imports can import files by name, as long as they are modules import * as googModule from './googModule'; -import * as googModuleDefault from './googModuleDefault'; +import googModuleDefault from './googModuleDefault'; import * as es6Module from './es6Module'; -import * as es6ModuleDefault from './es6ModuleDefault'; +import es6ModuleDefault from './es6ModuleDefault'; es6Module.fun(); es6ModuleDefault(); diff --git a/javascript/ql/test/library-tests/NodeJS/tests.expected b/javascript/ql/test/library-tests/NodeJS/tests.expected index 964b784cf93f..f66d996465ce 100644 --- a/javascript/ql/test/library-tests/NodeJS/tests.expected +++ b/javascript/ql/test/library-tests/NodeJS/tests.expected @@ -1,11 +1,3 @@ -module_getAnExportedSymbol -| b.js:1:1:8:0 | | sneaky | -| d.js:1:1:7:15 | | baz | -| reexport/a.js:1:1:3:1 | | foo | -| reexport/b.js:1:1:6:1 | | bar | -| reexport/b.js:1:1:6:1 | | foo | -| sub/c.js:1:1:4:0 | | foo | -| sub/f.js:1:1:4:17 | | bar | module_getAnImport | a.js:1:1:14:0 | | a.js:1:9:1:22 | require('./b') | | a.js:1:1:14:0 | | a.js:2:7:2:19 | require('fs') | diff --git a/javascript/ql/test/library-tests/NodeJS/tests.ql b/javascript/ql/test/library-tests/NodeJS/tests.ql index f5e0f58dc467..27f5bd7da7fc 100644 --- a/javascript/ql/test/library-tests/NodeJS/tests.ql +++ b/javascript/ql/test/library-tests/NodeJS/tests.ql @@ -1,9 +1,5 @@ import javascript -query predicate module_getAnExportedSymbol(NodeModule m, string symbol) { - symbol = m.getAnExportedSymbol() -} - query predicate module_getAnImport(NodeModule m, Import imp) { imp = m.getAnImport() } query predicate module_getAnImportedModule(NodeModule m, Module mod) {