Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 12 additions & 32 deletions javascript/ql/lib/semmle/javascript/AMD.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()
}
Expand Down
17 changes: 17 additions & 0 deletions javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
4 changes: 2 additions & 2 deletions javascript/ql/lib/semmle/javascript/Modules.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
93 changes: 25 additions & 68 deletions javascript/ql/lib/semmle/javascript/NodeJS.qll
Original file line number Diff line number Diff line change
Expand Up @@ -37,48 +37,44 @@ 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")
}

/**
* Gets an expression that is an alias for `module.exports`.
* 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)
Expand All @@ -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())
)
Expand All @@ -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() |
Expand Down Expand Up @@ -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 `<prefix>/node_modules`, where
* `<prefix>` is a (not necessarily proper) prefix of `f` and does not end in `/node_modules`,
Expand Down
3 changes: 1 addition & 2 deletions javascript/ql/lib/semmle/javascript/PackageExports.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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?
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) }

Expand All @@ -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) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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() }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@
private import javascript
private import semmle.javascript.dataflow.internal.StepSummary
private import semmle.javascript.dataflow.internal.PreCallGraphStep
private import semmle.javascript.internal.NameResolution

cached
module CallGraph {
/** Gets the function referenced by `node`, as determined by the type inference. */
cached
Function getAFunctionValue(AnalyzedNode node) {
result = node.getAValue().(AbstractCallable).getFunction()
or
node = NameResolution::trackFunctionValue(result).toDataFlowNodeOut()
or
exists(DataFlow::Node pred |
AccessPath::step(pred, node) and
result = getAFunctionValue(pred)
)
}

/** Holds if the type inferred for `node` is indefinite due to global flow. */
Expand Down
Loading
Loading