Files
codeql/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll
2025-11-13 09:46:18 +01:00

1938 lines
64 KiB
Plaintext

/**
* Provides classes for working with a data flow graph-based
* program representation.
*
* We currently consider three kinds of data flow:
*
* 1. Flow within an expression, for example from the operands of a `&&`
* expression to the expression itself.
* 2. Flow through local variables, that is, from definitions to uses.
* Captured variables are treated flow-insensitively, that is, all
* definitions are considered to flow to all uses, while for non-captured
* variables only definitions that can actually reach a use are considered.
* 3. Flow into and out of immediately invoked function expressions, that is,
* flow from arguments to parameters, and from returned expressions to the
* function expression itself.
*
* Flow through global variables, object properties or function calls is not
* modeled (except for immediately invoked functions as explained above).
*/
overlay[local]
module;
import javascript
private import internal.CallGraphs
private import internal.FlowSteps as FlowSteps
private import internal.DataFlowNode
private import internal.AnalyzedParameters
private import internal.PreCallGraphStep
private import semmle.javascript.internal.CachedStages
private import semmle.javascript.dataflow.internal.DataFlowPrivate as Private
private import semmle.javascript.dataflow.internal.VariableOrThis
private import semmle.javascript.internal.NameResolution
private import semmle.javascript.internal.UnderlyingTypes
private import semmle.javascript.internal.TypeResolution
module DataFlow {
/**
* A node in the data flow graph.
*/
class Node extends TNode {
/**
* Gets a data flow node from which data may flow to this node in one local step.
*/
Node getAPredecessor() { localFlowStep(result, this) }
/**
* Gets a data flow node to which data may flow from this node in one local step.
*/
Node getASuccessor() { localFlowStep(this, result) }
/**
* Gets a source node from which data may flow to this node in zero or more local steps.
*/
SourceNode getALocalSource() { result.flowsTo(this) }
/**
* Holds if the flow information for this node is incomplete.
*
* This predicate holds if there may be a source flow node from which data flows into
* this node, but that node is not a result of `getALocalSource()` due to analysis
* incompleteness. The parameter `cause` is bound to a string describing the source of
* incompleteness.
*
* For example, since this analysis is intra-procedural, data flow from actual arguments
* to formal parameters is not modeled. Hence, if `p` is an access to a parameter,
* `p.getALocalSource()` does _not_ return the corresponding argument, and
* `p.isIncomplete("call")` holds.
*/
overlay[global]
predicate isIncomplete(Incompleteness cause) { isIncomplete(this, cause) }
/** Gets type inference results for this data flow node. */
overlay[global]
AnalyzedNode analyze() { result = this }
/** Gets the expression corresponding to this data flow node, if any. */
Expr asExpr() { this = TValueNode(result) }
/**
* Gets the expression enclosing this data flow node.
* In most cases the result is the same as `asExpr()`, however this method
* additionally includes the `InvokeExpr` corresponding to reflective calls.
*/
Expr getEnclosingExpr() {
result = this.asExpr() or
this = DataFlow::reflectiveCallNode(result)
}
/** Gets the AST node corresponding to this data flow node, if any. */
AstNode getAstNode() { none() }
/** Gets the basic block to which this node belongs. */
BasicBlock getBasicBlock() { none() }
/** Gets the container in which this node occurs. */
StmtContainer getContainer() { result = this.getBasicBlock().getContainer() }
/** Gets the toplevel in which this node occurs. */
TopLevel getTopLevel() { result = this.getContainer().getTopLevel() }
/**
* Holds if this data flow node accesses the global variable `g`, either directly
* or through the `window` object.
*/
predicate accessesGlobal(string g) { globalVarRef(g).flowsTo(this) }
/** Holds if this node may evaluate to the string `s`, possibly through local data flow. */
pragma[nomagic]
predicate mayHaveStringValue(string s) {
this.getAPredecessor().mayHaveStringValue(s)
or
s = this.getStringValue()
}
/** Gets the string value of this node, if it is a string literal or constant string concatenation. */
string getStringValue() { result = this.asExpr().getStringValue() }
/** Holds if this node may evaluate to the Boolean value `b`. */
predicate mayHaveBooleanValue(boolean b) {
this.getAPredecessor().mayHaveBooleanValue(b)
or
b = true and this.asExpr().(BooleanLiteral).getValue() = "true"
or
b = false and this.asExpr().(BooleanLiteral).getValue() = "false"
}
/** Gets the integer value of this node, if it is an integer constant. */
int getIntValue() { result = this.asExpr().getIntValue() }
/** Gets a function value that may reach this node. */
overlay[global]
final FunctionNode getAFunctionValue() {
CallGraph::getAFunctionReference(result, 0).flowsTo(this)
}
/** Gets a function value that may reach this node with the given `imprecision` level. */
overlay[global]
final FunctionNode getAFunctionValue(int imprecision) {
CallGraph::getAFunctionReference(result, imprecision).flowsTo(this)
}
/**
* Gets a function value that may reach this node,
* possibly derived from a partial function invocation.
*/
overlay[global]
final FunctionNode getABoundFunctionValue(int boundArgs) {
result = this.getAFunctionValue() and boundArgs = 0
or
CallGraph::getABoundFunctionReference(result, boundArgs, _).flowsTo(this)
}
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
final predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the location of this node. */
cached
Location getLocation() { none() }
/** Gets the file this data flow node comes from. */
File getFile() { none() } // overridden in subclasses
/** Gets the start line of this data flow node. */
int getStartLine() { this.hasLocationInfo(_, result, _, _, _) }
/** Gets the start column of this data flow node. */
int getStartColumn() { this.hasLocationInfo(_, _, result, _, _) }
/** Gets the end line of this data flow node. */
int getEndLine() { this.hasLocationInfo(_, _, _, result, _) }
/** Gets the end column of this data flow node. */
int getEndColumn() { this.hasLocationInfo(_, _, _, _, result) }
/** Gets a textual representation of this element. */
cached
string toString() { none() }
/**
* Gets the immediate predecessor of this node, if any.
*
* A node with an immediate predecessor can usually only have the value that flows
* into its from its immediate predecessor.
*/
cached
DataFlow::Node getImmediatePredecessor() {
immediateFlowStep(result, this)
or
FlowSteps::identityFunctionStep(result, this)
}
overlay[global]
private NameResolution::Node getNameResolutionNode() {
this = valueNode(result)
or
exists(PropertyPattern pattern |
result = pattern.getValuePattern() and
this = TPropNode(pattern)
)
}
/**
* Holds if this node is annotated with the given named type,
* or is declared as a subtype thereof, or is a union or intersection containing such a type.
*/
overlay[global]
cached
predicate hasUnderlyingType(string globalName) {
Stages::TypeTracking::ref() and
exists(NameResolution::Node type |
TypeResolution::valueHasType(this.getNameResolutionNode(), type) and
UnderlyingTypes::nodeHasUnderlyingType(type, globalName)
)
}
/**
* Holds if this node is annotated with the given named type,
* or is declared as a subtype thereof, or is a union or intersection containing such a type.
*/
overlay[global]
cached
predicate hasUnderlyingType(string moduleName, string typeName) {
Stages::TypeTracking::ref() and
moduleName != "global" and
exists(NameResolution::Node type |
TypeResolution::valueHasType(this.getNameResolutionNode(), type) and
UnderlyingTypes::nodeHasUnderlyingType(type, moduleName, typeName)
)
}
/**
* Gets the post-update node corresponding to this node, if any.
*/
final PostUpdateNode getPostUpdateNode() { result.getPreUpdateNode() = this }
}
/**
* A node in the data flow graph which corresponds to an expression,
* destructuring pattern, or declaration of a function, class, namespace, or enum.
*
* Examples:
* ```js
* x + y
* Math.abs(x)
* class C {}
* function f(x, y) {}
* ```
*/
class ValueNode extends Node, TValueNode {
AST::ValueNode astNode;
ValueNode() { this = TValueNode(astNode) }
/** Gets the expression or declaration this node corresponds to. */
override AST::ValueNode getAstNode() { result = astNode }
override BasicBlock getBasicBlock() { astNode = result.getANode() }
override Location getLocation() {
Stages::DataFlowStage::ref() and
result = astNode.getLocation()
}
override File getFile() { result = astNode.getFile() }
override string toString() { Stages::DataFlowStage::ref() and result = astNode.toString() }
}
/**
* A node in the data flow graph which corresponds to an SSA variable definition.
*/
class SsaDefinitionNode extends Node, TSsaDefNode {
SsaDefinition ssa;
SsaDefinitionNode() { this = TSsaDefNode(ssa) }
/** Gets the SSA variable defined at this data flow node. */
SsaVariable getSsaVariable() { result = ssa.getVariable() }
override BasicBlock getBasicBlock() { result = ssa.getBasicBlock() }
override Location getLocation() { result = ssa.getLocation() }
override string toString() { result = ssa.getSourceVariable().getName() }
override File getFile() { result = ssa.getBasicBlock().getFile() }
override AstNode getAstNode() { none() }
}
/**
* A node in the data flow graph which corresponds to a `@property`.
*/
private class PropNode extends Node, TPropNode {
@property prop;
PropNode() { this = TPropNode(prop) }
override BasicBlock getBasicBlock() { result = prop.(ControlFlowNode).getBasicBlock() }
override Location getLocation() { result = prop.(Locatable).getLocation() }
override string toString() { result = prop.(AstNode).toString() }
override File getFile() { result = prop.(AstNode).getFile() }
override AstNode getAstNode() { result = prop }
}
/**
* A node in the data flow graph which corresponds to the rest pattern of a
* destructuring pattern.
*/
private class RestPatternNode extends Node, TRestPatternNode {
DestructuringPattern pattern;
Expr rest;
RestPatternNode() { this = TRestPatternNode(pattern, rest) }
override BasicBlock getBasicBlock() { result = rest.getBasicBlock() }
override Location getLocation() { result = rest.getLocation() }
override string toString() { result = "..." + rest.toString() }
override File getFile() { result = pattern.getFile() }
override AstNode getAstNode() { result = rest }
}
/**
* A node in the data flow graph which corresponds to an element pattern of an
* array pattern.
*/
private class ElementPatternNode extends Node, TElementPatternNode {
ArrayPattern pattern;
Expr elt;
ElementPatternNode() { this = TElementPatternNode(pattern, elt) }
override BasicBlock getBasicBlock() { result = elt.getBasicBlock() }
override Location getLocation() { result = elt.getLocation() }
override string toString() { result = elt.toString() }
override File getFile() { result = pattern.getFile() }
override AstNode getAstNode() { result = elt }
}
/**
* A node in the data flow graph which represents the (implicit) write of an element
* in an array expression to the underlying array.
*
* That is, for an array expression `["first", ,"third"]`, we have two array element nodes,
* one representing the write of expression `"first"` into the 0th element of the array,
* and one representing the write of `"third"` into its second element.
*/
private class ElementNode extends Node, TElementNode {
ArrayExpr arr;
Expr elt;
ElementNode() { this = TElementNode(arr, elt) }
override BasicBlock getBasicBlock() { result = elt.getBasicBlock() }
override Location getLocation() { result = elt.getLocation() }
override string toString() { result = elt.toString() }
override File getFile() { result = arr.getFile() }
override AstNode getAstNode() { result = elt }
}
/**
* A node in the data flow graph which corresponds to the reflective call performed
* by a `.call` or `.apply` invocation.
*/
private class ReflectiveCallNode extends Node, TReflectiveCallNode {
MethodCallExpr call;
ReflectiveCallNode() { this = TReflectiveCallNode(call, _) }
override BasicBlock getBasicBlock() { result = call.getBasicBlock() }
override Location getLocation() { result = call.getLocation() }
override string toString() { result = "reflective call" }
override File getFile() { result = call.getFile() }
}
/**
* A node referring to the module imported at a named or default ES2015 import declaration.
*/
private class DestructuredModuleImportNode extends Node, TDestructuredModuleImportNode {
ImportDeclaration imprt;
DestructuredModuleImportNode() { this = TDestructuredModuleImportNode(imprt) }
override BasicBlock getBasicBlock() { result = imprt.getBasicBlock() }
override Location getLocation() { result = imprt.getLocation() }
override string toString() { result = imprt.toString() }
override File getFile() { result = imprt.getFile() }
}
/**
* A data flow node that reads or writes an object property or class member.
*
* The default subclasses do not model global variable references or variable
* references inside `with` statements as property references.
*
* See `PropWrite` and `PropRead` for concrete syntax examples.
*/
abstract class PropRef extends Node {
/**
* Gets the data flow node corresponding to the base object
* whose property is read from or written to.
*/
cached
abstract Node getBase();
/**
* Gets the expression specifying the name of the property being
* read or written, if any.
*
* This is usually either an identifier or a literal.
*/
abstract Expr getPropertyNameExpr();
/**
* Holds if this property reference may access a property named `propName`.
*/
predicate mayHavePropertyName(string propName) {
propName = this.getPropertyName() or
this.getPropertyNameExpr().flow().mayHaveStringValue(propName)
}
/**
* Gets the name of the property being read or written,
* if it can be statically determined.
*
* By default, this predicate is undefined for dynamic property references
* such as `e[computePropertyName()]` and for spread/rest properties.
*/
abstract string getPropertyName();
/**
* Holds if this data flow node accesses property `p` on base node `base`.
*/
pragma[noinline]
predicate accesses(Node base, string p) { this.getBase() = base and this.getPropertyName() = p }
/**
* Holds if this data flow node reads or writes a private field in a class.
*/
predicate isPrivateField() {
this.getPropertyName().charAt(0) = "#" and this.getPropertyNameExpr() instanceof Label
}
/**
* Gets an accessor (`get` or `set` method) that may be invoked by this property reference.
*/
overlay[global]
final DataFlow::FunctionNode getAnAccessorCallee() {
result = CallGraph::getAnAccessorCallee(this)
}
}
/**
* A data flow node that writes to an object property.
*
* For example, all of the following are property writes:
* ```js
* obj.f = value; // assignment to a property
* obj[e] = value; // assignment to a computed property
* { f: value } // property initializer
* { g() {} } // object literal method
* { get g() {}, set g(v) {} } // accessor methods (have no rhs value)
* class C {
* constructor(public x: number); // parameter field (TypeScript only)
* static m() {} // static method
* g() {} // instance method
* }
* Object.defineProperty(obj, 'f', { value: 5} ) // call to defineProperty
* <View width={value}/> // JSX attribute
* ```
*/
abstract class PropWrite extends PropRef {
/**
* Gets the data flow node corresponding to the value being written,
* if it can be statically determined.
*
* This predicate is undefined for spread properties, accessor
* properties, and most uses of `Object.defineProperty`.
*/
pragma[nomagic]
abstract Node getRhs();
/**
* Holds if this data flow node writes the value of `rhs` to property
* `prop` of the object that `base` evaluates to.
*/
pragma[noinline]
predicate writes(DataFlow::Node base, string prop, DataFlow::Node rhs) {
base = this.getBase() and
prop = this.getPropertyName() and
rhs = this.getRhs()
}
/**
* Gets the node where the property write happens in the control flow graph.
*/
abstract ControlFlowNode getWriteNode();
/**
* If this installs an accessor on an object, as opposed to a regular property,
* gets the body of the accessor. `isSetter` is true if installing a setter, and
* false is installing a getter.
*/
DataFlow::FunctionNode getInstalledAccessor(boolean isSetter) { none() }
}
/**
* A property access in lvalue position, viewed as a property definition node.
*/
private class PropLValueAsPropWrite extends PropWrite, ValueNode {
override PropAccess astNode;
PropLValueAsPropWrite() { astNode instanceof LValue }
override Node getBase() {
result = valueNode(astNode.getBase()) and
Stages::DataFlowStage::ref()
}
override Expr getPropertyNameExpr() { result = astNode.getPropertyNameExpr() }
override string getPropertyName() { result = astNode.getPropertyName() }
override Node getRhs() { result = valueNode(astNode.(LValue).getRhs()) }
override ControlFlowNode getWriteNode() { result = astNode.(LValue).getDefNode() }
}
/**
* A property of an object literal, viewed as a data flow node that writes
* to the corresponding property.
*/
private class PropInitAsPropWrite extends PropWrite, PropNode {
override Property prop;
override Node getBase() { result = valueNode(prop.getObjectExpr()) }
override Expr getPropertyNameExpr() { result = prop.getNameExpr() }
override string getPropertyName() { result = prop.getName() }
override Node getRhs() { result = valueNode(prop.(ValueProperty).getInit()) }
override DataFlow::FunctionNode getInstalledAccessor(boolean isSetter) {
(
prop instanceof PropertySetter and
isSetter = true
or
prop instanceof PropertyGetter and
isSetter = false
) and
result = valueNode(prop.getInit())
}
override ControlFlowNode getWriteNode() { result = prop }
}
/**
* A call to `Object.defineProperty`, viewed as a data flow node that
* writes to the corresponding property.
*/
private class ObjectDefinePropertyAsPropWrite extends PropWrite, ValueNode {
override MethodCallExpr astNode;
ObjectDefinePropertyAsPropWrite() {
astNode.getReceiver().(GlobalVarAccess).getName() = "Object" and
astNode.getMethodName() = "defineProperty"
}
override Node getBase() { result = astNode.getArgument(0).flow() }
override Expr getPropertyNameExpr() { result = astNode.getArgument(1) }
override string getPropertyName() { result = astNode.getArgument(1).getStringValue() }
override Node getRhs() {
exists(DataFlow::SourceNode descriptor |
descriptor = astNode.getArgument(2).flow().getALocalSource()
|
result =
descriptor
.getAPropertyWrite("get")
.getRhs()
.getALocalSource()
.(DataFlow::FunctionNode)
.getAReturn()
or
result = descriptor.getAPropertyWrite("value").getRhs()
)
}
override ControlFlowNode getWriteNode() { result = astNode }
}
/**
* A static member definition, viewed as a data flow node that adds
* a property to the class.
*/
private class StaticClassMemberAsPropWrite extends PropWrite, PropNode {
override MemberDefinition prop;
StaticClassMemberAsPropWrite() { prop.isStatic() }
override Node getBase() { result = valueNode(prop.getDeclaringClass()) }
override Expr getPropertyNameExpr() { result = prop.getNameExpr() }
override string getPropertyName() { result = prop.getName() }
override Node getRhs() {
not prop instanceof AccessorMethodDefinition and
result = valueNode(prop.getInit())
}
override DataFlow::FunctionNode getInstalledAccessor(boolean isSetter) {
(
prop instanceof SetterMethodDefinition and
isSetter = true
or
prop instanceof GetterMethodDefinition and
isSetter = false
) and
result = valueNode(prop.getInit())
}
override ControlFlowNode getWriteNode() { result = prop }
}
/**
* An instance method definition, viewed as a data flow node that adds
* a property to an unseen value.
*/
private class InstanceMethodAsPropWrite extends PropWrite, PropNode {
override MethodDefinition prop;
InstanceMethodAsPropWrite() { not prop.isStatic() }
override Node getBase() { none() } // The prototype has no DataFlow node
override Expr getPropertyNameExpr() { result = prop.getNameExpr() }
override string getPropertyName() { result = prop.getName() }
override Node getRhs() {
not prop instanceof AccessorMethodDefinition and
result = valueNode(prop.getInit())
}
override DataFlow::FunctionNode getInstalledAccessor(boolean isSetter) {
(
prop instanceof SetterMethodDefinition and
isSetter = true
or
prop instanceof GetterMethodDefinition and
isSetter = false
) and
result = valueNode(prop.getInit())
}
override ControlFlowNode getWriteNode() { result = prop }
}
/**
* A JSX attribute definition, viewed as a data flow node that writes properties to
* the JSX element it is in.
*/
private class JsxAttributeAsPropWrite extends PropWrite, PropNode {
override JsxAttribute prop;
override Node getBase() { result = valueNode(prop.getElement()) }
override Expr getPropertyNameExpr() { result = prop.getNameExpr() }
override string getPropertyName() { result = prop.getName() }
override Node getRhs() { result = valueNode(prop.getValue()) }
override ControlFlowNode getWriteNode() { result = prop }
}
/**
* A field induced by an initializing constructor parameter, seen as a property write (TypeScript only).
*/
private class ParameterFieldAsPropWrite extends PropWrite, PropNode {
override ParameterField prop;
override Node getBase() { result = TImplicitThisUse(prop, false) }
override Expr getPropertyNameExpr() {
none() // The parameter value is not the name of the field
}
override string getPropertyName() { result = prop.getName() }
override Node getRhs() { result = TValueNode(prop.getParameter()) }
override ControlFlowNode getWriteNode() { result = prop.getParameter() }
override StmtContainer getContainer() { parameter_fields(prop, result, _) }
}
/**
* An instance field with an initializer, seen as a property write.
*/
private class InstanceFieldAsPropWrite extends PropWrite, PropNode {
override FieldDefinition prop;
InstanceFieldAsPropWrite() {
not prop.isStatic() and
not prop instanceof ParameterField and
exists(prop.getInit())
}
override Node getBase() { result = TImplicitThisUse(prop, false) }
override Expr getPropertyNameExpr() { result = prop.getNameExpr() }
override string getPropertyName() { result = prop.getName() }
override Node getRhs() { result = valueNode(prop.getInit()) }
override ControlFlowNode getWriteNode() { result = prop }
}
/**
* A data flow node that reads an object property.
*
* For example, all the following are property reads:
* ```js
* obj.f // property access
* obj[e] // computed property access
* let { f } = obj; // destructuring object pattern
* let [x, y] = array; // destructuring array pattern
* function f({ f }) {} // destructuring pattern (in parameter)
* for (let elm of array) {} // element access in for..of loop
* import { join } from 'path'; // named import specifier
* ```
*/
abstract class PropRead extends PropRef, SourceNode { }
/**
* A property read, considered as a source node.
*
* Note that we cannot simplify the characteristic predicate to `this instanceof PropRead`,
* since `PropRead` is itself a subclass of `SourceNode`.
*/
private class PropReadAsSourceNode extends SourceNode::Range {
PropReadAsSourceNode() {
this = TPropNode(any(PropertyPattern p)) or
this instanceof RestPatternNode or
this instanceof ElementPatternNode or
this = lvalueNode(any(ForOfStmt stmt).getLValue())
}
}
/**
* A property access in rvalue position.
*/
private class PropRValueAsPropRead extends PropRead, ValueNode {
override PropAccess astNode;
PropRValueAsPropRead() { astNode instanceof RValue }
override Node getBase() { result = valueNode(astNode.getBase()) }
override Expr getPropertyNameExpr() { result = astNode.getPropertyNameExpr() }
override string getPropertyName() { result = astNode.getPropertyName() }
}
/**
* A property pattern viewed as a property read; for instance, in
* `var { p: q } = o`, `p` is a read of property `p` of `o`.
*/
private class PropPatternAsPropRead extends PropRead, PropNode {
override PropertyPattern prop;
/** Gets the value pattern of this property pattern. */
Expr getValuePattern() { result = prop.getValuePattern() }
override Node getBase() { result = TValueNode(prop.getObjectPattern()) }
override Expr getPropertyNameExpr() { result = prop.getNameExpr() }
override string getPropertyName() { result = prop.getName() }
}
/**
* A rest pattern viewed as a property read; for instance, in
* `var { ...ps } = o`, `ps` is a read of all properties of `o`, and similar
* for `[ ...elts ] = arr`.
*/
private class RestPatternAsPropRead extends PropRead, RestPatternNode {
override Node getBase() { result = TValueNode(pattern) }
override Expr getPropertyNameExpr() { none() }
override string getPropertyName() { none() }
}
/**
* An array element pattern viewed as a property read; for instance, in
* `var [ x, y ] = arr`, `x` is a read of property 0 of `arr` and similar
* for `y`.
*/
private class ElementPatternAsPropRead extends PropRead, ElementPatternNode {
override Node getBase() { result = TValueNode(pattern) }
override Expr getPropertyNameExpr() { none() }
override string getPropertyName() {
exists(int i |
elt = pattern.getElement(i) and
result = i.toString()
)
}
}
/**
* A named import specifier seen as a property read on the imported module.
*/
private class ImportSpecifierAsPropRead extends PropRead, ValueNode {
override ImportSpecifier astNode;
ImportDeclaration imprt;
ImportSpecifierAsPropRead() {
astNode = imprt.getASpecifier() and
not astNode.isTypeOnly() and
exists(astNode.getImportedName())
}
override Node getBase() { result = TDestructuredModuleImportNode(imprt) }
override Expr getPropertyNameExpr() { result = astNode.getImported() }
override string getPropertyName() { result = astNode.getImportedName() }
}
/**
* The left-hand side of a `for..of` statement, seen as a property read
* on the object being iterated over.
*/
private class ForOfLvalueAsPropRead extends PropRead {
ForOfStmt stmt;
ForOfLvalueAsPropRead() { this = lvalueNode(stmt.getLValue()) }
override Node getBase() { result = stmt.getIterationDomain().flow() }
override Expr getPropertyNameExpr() { none() }
override string getPropertyName() { none() }
}
/**
* A data flow node representing an HTML attribute.
*/
class HtmlAttributeNode extends DataFlow::Node, THtmlAttributeNode {
HTML::Attribute attr;
HtmlAttributeNode() { this = THtmlAttributeNode(attr) }
override string toString() { result = attr.toString() }
override Location getLocation() { result = attr.getLocation() }
/** Gets the attribute corresponding to this data flow node. */
HTML::Attribute getAttribute() { result = attr }
override File getFile() { result = attr.getFile() }
}
/**
* A data flow node representing an XML attribute.
*/
class XmlAttributeNode extends DataFlow::Node, TXmlAttributeNode {
XmlAttribute attr;
XmlAttributeNode() { this = TXmlAttributeNode(attr) }
override string toString() { result = attr.toString() }
override Location getLocation() { result = attr.getLocation() }
/** Gets the attribute corresponding to this data flow node. */
XmlAttribute getAttribute() { result = attr }
override File getFile() { result = attr.getLocation().getFile() }
}
/**
* A data flow node representing the exceptions thrown by a function.
*/
class ExceptionalFunctionReturnNode extends DataFlow::Node, TExceptionalFunctionReturnNode {
Function function;
ExceptionalFunctionReturnNode() { this = TExceptionalFunctionReturnNode(function) }
override string toString() { result = "exceptional return of " + function.describe() }
override Location getLocation() { result = function.getLocation() }
override BasicBlock getBasicBlock() { result = function.getExit().getBasicBlock() }
override StmtContainer getContainer() {
// Override this to ensure a container exists even for unreachable returns,
// since an unreachable exit CFG node will not have a basic block
result = function
}
/**
* Gets the function corresponding to this exceptional return node.
*/
Function getFunction() { result = function }
override File getFile() { result = function.getFile() }
}
/**
* A data flow node representing the values returned by a function.
*/
class FunctionReturnNode extends DataFlow::Node, TFunctionReturnNode {
Function function;
FunctionReturnNode() { this = TFunctionReturnNode(function) }
override string toString() { result = "return of " + function.describe() }
override Location getLocation() { result = function.getLocation() }
override BasicBlock getBasicBlock() { result = function.getExit().getBasicBlock() }
override StmtContainer getContainer() {
// Override this to ensure a container exists even for unreachable returns,
// since an unreachable exit CFG node will not have a basic block
result = function
}
/**
* Gets the function corresponding to this return node.
*/
Function getFunction() { result = function }
override File getFile() { result = function.getFile() }
}
/**
* A data flow node representing the arguments object given to a function.
*/
class ReflectiveParametersNode extends DataFlow::Node, TReflectiveParametersNode {
Function function;
ReflectiveParametersNode() { this = TReflectiveParametersNode(function) }
override string toString() { result = "'arguments' object of " + function.describe() }
override Location getLocation() { result = function.getLocation() }
override BasicBlock getBasicBlock() { result = function.getEntry().getBasicBlock() }
/**
* Gets the function whose `arguments` object is represented by this node.
*/
Function getFunction() { result = function }
override File getFile() { result = function.getFile() }
}
/**
* A data flow node representing the exceptions thrown by the callee of an invocation.
*/
class ExceptionalInvocationReturnNode extends DataFlow::Node, TExceptionalInvocationReturnNode {
InvokeExpr invoke;
ExceptionalInvocationReturnNode() { this = TExceptionalInvocationReturnNode(invoke) }
override string toString() { result = "exceptional return of " + invoke.toString() }
override Location getLocation() { result = invoke.getLocation() }
override BasicBlock getBasicBlock() { result = invoke.getBasicBlock() }
/**
* Gets the invocation corresponding to this exceptional return node.
*/
DataFlow::InvokeNode getInvocation() { result = invoke.flow() }
override File getFile() { result = invoke.getFile() }
}
/**
* A pseudo-node representing the root of a global access path.
*/
private class GlobalAccessPathRoot extends TGlobalAccessPathRoot, DataFlow::Node {
override string toString() { result = "global access path" }
}
/**
* A node representing the value passed as `this` argument in a `new` call.
*/
class NewCallThisArgumentNode extends TNewCallThisArgument, DataFlow::Node {
private NewExpr expr;
NewCallThisArgumentNode() { this = TNewCallThisArgument(expr) }
override string toString() { result = "implicit 'this' argument of " + expr }
override StmtContainer getContainer() { result = expr.getContainer() }
override Location getLocation() { result = expr.getLocation() }
}
/**
* A node representing an implicit use of `this` or its post-update node.
*/
private class ImplicitThisUseNode extends TImplicitThisUse, DataFlow::Node {
private ImplicitThisUse use;
private boolean isPost;
ImplicitThisUseNode() { this = TImplicitThisUse(use, isPost) }
override string toString() {
if isPost = false
then result = "implicit 'this'"
else result = "[post-update] implicit 'this'"
}
override StmtContainer getContainer() { result = use.getUseContainer() }
override Location getLocation() { result = use.getLocation() }
}
/**
* INTERNAL. DO NOT USE.
*
* Gets a pseudo-node representing the root of a global access path.
*/
DataFlow::Node globalAccessPathRootPseudoNode() { result instanceof TGlobalAccessPathRoot }
/**
* Gets a data flow node representing the underlying call performed by the given
* call to `Function.prototype.call` or `Function.prototype.apply`.
*
* For example, for an expression `fn.call(x, y)`, this gets a call node with `fn` as the
* callee, `x` as the receiver, and `y` as the first argument.
*/
DataFlow::InvokeNode reflectiveCallNode(InvokeExpr expr) { result = TReflectiveCallNode(expr, _) }
/**
* Provides classes representing various kinds of calls.
*
* Subclass the classes in this module to introduce new kinds of calls. If you want to
* refine the behavior of the analysis on existing kinds of calls, subclass `InvokeNode`
* instead.
*/
module Impl {
/**
* INTERNAL. DO NOT USE.
*
* An alias for `Node.getImmediatePredecessor` that can be used at an earlier stage
* that does not depend on `DataFlow::Node`.
*/
predicate earlyStageImmediateFlowStep = immediateFlowStep/2;
/**
* A data flow node representing a function invocation, either explicitly or reflectively,
* and either with or without `new`.
*/
abstract class InvokeNodeDef extends DataFlow::Node {
/** Gets the syntactic invoke expression underlying this function invocation. */
abstract InvokeExpr getInvokeExpr();
/** Gets the name of the function or method being invoked, if it can be determined. */
abstract string getCalleeName();
/** Gets the data flow node specifying the function to be called. */
abstract DataFlow::Node getCalleeNode();
/** Gets the data flow node corresponding to the `i`th argument of this invocation. */
abstract DataFlow::Node getArgument(int i);
/** Gets the data flow node corresponding to an argument of this invocation. */
abstract DataFlow::Node getAnArgument();
/**
* Gets a data flow node corresponding to an array of values being passed as
* individual arguments to this invocation.
*/
abstract DataFlow::Node getASpreadArgument();
/** Gets the number of arguments of this invocation, if it can be determined. */
abstract int getNumArgument();
}
/**
* A data flow node representing a function call without `new`, either explicitly or
* reflectively.
*/
abstract class CallNodeDef extends InvokeNodeDef {
/** Gets the data flow node corresponding to the receiver of this call, if any. */
DataFlow::Node getReceiver() { none() }
}
/**
* A data flow node representing a method call.
*/
abstract class MethodCallNodeDef extends CallNodeDef {
/** Gets the name of the method being invoked, if it can be determined. */
abstract string getMethodName();
}
/**
* A data flow node representing a function invocation with `new`.
*/
abstract class NewNodeDef extends InvokeNodeDef { }
/**
* A data flow node representing an explicit (that is, non-reflective) function invocation.
*/
class ExplicitInvokeNode extends InvokeNodeDef, DataFlow::ValueNode {
override InvokeExpr astNode;
override InvokeExpr getInvokeExpr() { result = astNode }
override string getCalleeName() { result = astNode.getCalleeName() }
override DataFlow::Node getCalleeNode() { result = DataFlow::valueNode(astNode.getCallee()) }
/**
* Whether i is an index that occurs after a spread argument.
*/
pragma[nomagic]
private predicate isIndexAfterSpread(int i) {
astNode.isSpreadArgument(i)
or
exists(astNode.getArgument(i)) and
this.isIndexAfterSpread(i - 1)
}
override DataFlow::Node getArgument(int i) {
not this.isIndexAfterSpread(i) and
result = DataFlow::valueNode(astNode.getArgument(i))
}
override DataFlow::Node getAnArgument() {
exists(Expr arg | arg = astNode.getAnArgument() |
not arg instanceof SpreadElement and
result = DataFlow::valueNode(arg)
)
}
override DataFlow::Node getASpreadArgument() {
exists(SpreadElement arg | arg = astNode.getAnArgument() |
result = DataFlow::valueNode(arg.getOperand())
)
}
override int getNumArgument() {
not astNode.isSpreadArgument(_) and result = astNode.getNumArgument()
}
}
/**
* A data flow node representing an explicit (that is, non-reflective) function call.
*/
class ExplicitCallNode extends CallNodeDef, ExplicitInvokeNode {
override CallExpr astNode;
}
/**
* A data flow node representing an explicit (that is, non-reflective) method call.
*/
private class ExplicitMethodCallNode extends MethodCallNodeDef, ExplicitCallNode {
override MethodCallExpr astNode;
override DataFlow::Node getReceiver() { result = DataFlow::valueNode(astNode.getReceiver()) }
override string getMethodName() { result = astNode.getMethodName() }
}
/**
* A data flow node representing a `new` expression.
*/
private class ExplicitNewNode extends NewNodeDef, ExplicitInvokeNode {
override NewExpr astNode;
}
/**
* A data flow node representing a reflective function call.
*/
private class ReflectiveCallNodeDef extends CallNodeDef {
ExplicitMethodCallNode originalCall;
string kind;
ReflectiveCallNodeDef() { this = TReflectiveCallNode(originalCall.asExpr(), kind) }
override InvokeExpr getInvokeExpr() { result = originalCall.getInvokeExpr() }
override string getCalleeName() {
result = originalCall.getReceiver().asExpr().(PropAccess).getPropertyName()
}
override DataFlow::Node getCalleeNode() { result = originalCall.getReceiver() }
override DataFlow::Node getReceiver() { result = originalCall.getArgument(0) }
override DataFlow::Node getArgument(int i) {
i >= 0 and kind = "call" and result = originalCall.getArgument(i + 1)
}
override DataFlow::Node getAnArgument() {
kind = "call" and result = originalCall.getAnArgument() and result != this.getReceiver()
}
override DataFlow::Node getASpreadArgument() {
kind = "apply" and
result = originalCall.getArgument(1)
or
kind = "call" and
result = originalCall.getASpreadArgument()
}
override int getNumArgument() {
result >= 0 and kind = "call" and result = originalCall.getNumArgument() - 1
}
}
/**
* A data flow node representing a call with a tagged template literal.
*/
private class TaggedTemplateLiteralCallNode extends CallNodeDef, ValueNode {
override TaggedTemplateExpr astNode;
override InvokeExpr getInvokeExpr() { none() } // There is no InvokeExpr for this.
override string getCalleeName() {
result = astNode.getTag().getUnderlyingValue().(Identifier).getName()
}
override DataFlow::Node getCalleeNode() { result = DataFlow::valueNode(astNode.getTag()) }
override DataFlow::Node getArgument(int i) {
// the first argument sent to the function is the array of string parts, which we don't model.
// rank is 1-indexed, which is perfect here.
result =
DataFlow::valueNode(rank[i](Expr e, int index |
e = astNode.getTemplate().getElement(index) and not e instanceof TemplateElement
|
e order by index
))
}
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
override DataFlow::Node getASpreadArgument() { none() }
// we don't model the string constants as arguments, but we still count them.
override int getNumArgument() { result = count(this.getArgument(_)) + 1 }
override DataFlow::Node getReceiver() { none() }
}
}
/**
* An array element viewed as a property write; for instance, in
* `var arr = ["first", , "third"]`, `"first"` is a write of property 0 of `arr`
* and `"third"` is a write of property 2 of `arr`.
*
* Note: We currently do not expose the array index as the property name,
* instead treating it as a write of an unknown property.
*/
private class ElementNodeAsPropWrite extends PropWrite, ElementNode {
override Expr getPropertyNameExpr() { none() }
override string getPropertyName() { none() }
override Node getRhs() { result = valueNode(elt) }
override Node getBase() { result = valueNode(arr) }
override ControlFlowNode getWriteNode() { result = arr }
}
/**
* A data flow node representing `this` in a function or top-level.
*/
private class ThisNodeInternal extends Node, TThisNode {
override string toString() { result = "this" }
override BasicBlock getBasicBlock() {
exists(StmtContainer container | this = TThisNode(container) | result = container.getEntry())
}
override Location getLocation() {
// Use the function entry as the location
exists(StmtContainer container | this = TThisNode(container) |
result = container.getEntry().getLocation()
)
}
override File getFile() {
exists(StmtContainer container | this = TThisNode(container) | result = container.getFile())
}
}
/**
* A data flow node representing a captured variable.
*/
private class CapturedVariableNode extends Node, TCapturedVariableNode {
LocalVariable variable;
CapturedVariableNode() { this = TCapturedVariableNode(variable) }
override BasicBlock getBasicBlock() { result = variable.getDeclaringContainer().getStartBB() }
override Location getLocation() { result = variable.getLocation() }
override string toString() { result = variable.getName() }
}
/** A data flow node representing the value plugged into a template tag. */
class TemplatePlaceholderTagNode extends Node, TTemplatePlaceholderTag {
/** Gets the template tag represented by this data flow node. */
Templating::TemplatePlaceholderTag getTag() { this = TTemplatePlaceholderTag(result) }
override BasicBlock getBasicBlock() { none() }
override Location getLocation() { result = this.getTag().getLocation() }
override string toString() { result = this.getTag().toString() }
override StmtContainer getContainer() { result = this.getTag().getInnerTopLevel() }
}
/**
* A node representing the hidden parameter of a function by which a function can refer to itself.
*/
class FunctionSelfReferenceNode extends DataFlow::Node, TFunctionSelfReferenceNode {
private Function function;
FunctionSelfReferenceNode() { this = TFunctionSelfReferenceNode(function) }
/** Gets the function. */
Function getFunction() { result = function }
override StmtContainer getContainer() { result = function }
override BasicBlock getBasicBlock() { result = function.getEntryBB() }
override string toString() { result = "[function self-reference] " + function.toString() }
override Location getLocation() { result = function.getLocation() }
}
/**
* A post-update node whose pre-node corresponds to an expression. See `DataFlow::PostUpdateNode` for more details.
*/
class ExprPostUpdateNode extends DataFlow::Node, TExprPostUpdateNode, Private::PostUpdateNode {
private AST::ValueNode expr;
ExprPostUpdateNode() { this = TExprPostUpdateNode(expr) }
/** Gets the expression for which this is the post-update node. */
AST::ValueNode getExpr() { result = expr }
override StmtContainer getContainer() { result = expr.getContainer() }
override Location getLocation() { result = expr.getLocation() }
override string toString() { result = "[post update] " + expr.toString() }
}
/**
* A post-update node.
*
* This is a data-flow node that represents the new state of an object after its contents have been mutated.
* Most notably such nodes exist for arguments to a call and for the base of a property reference.
*/
class PostUpdateNode extends DataFlow::Node {
PostUpdateNode() { Private::postUpdatePair(_, this) }
/**
* Gets the corresponding pre-update node, which is usually the argument to a call or the base of a property reference.
*/
final DataFlow::Node getPreUpdateNode() { Private::postUpdatePair(result, this) }
}
/**
* INTERNAL. DO NOT USE.
*
* Gets a data flow node representing the given captured variable.
*/
Node capturedVariableNode(LocalVariable variable) { result = TCapturedVariableNode(variable) }
/**
* Gets the data flow node corresponding to `nd`.
*
* This predicate is only defined for expressions, properties, and for statements that declare
* a function, a class, or a TypeScript namespace or enum.
*/
pragma[nomagic]
ValueNode valueNode(AstNode nd) { result.getAstNode() = nd }
/**
* Gets the data flow node corresponding to `e`.
*/
overlay[caller?]
pragma[inline]
ExprNode exprNode(Expr e) { result = valueNode(e) }
/** Gets the data flow node corresponding to `ssa`. */
SsaDefinitionNode ssaDefinitionNode(SsaDefinition ssa) { result = TSsaDefNode(ssa) }
/** Gets the node corresponding to the initialization of parameter `p`. */
ParameterNode parameterNode(Parameter p) { result.getParameter() = p }
/**
* INTERNAL: Use `parameterNode(Parameter)` instead.
*/
predicate parameterNode(EarlyStageNode nd, Parameter p) { nd = TValueNode(p) }
/**
* INTERNAL: Use `thisNode(StmtContainer container)` instead.
*/
predicate thisNode(EarlyStageNode node, StmtContainer container) { node = TThisNode(container) }
/**
* Gets the node representing the receiver of the given function, or `this` in the given top-level.
*
* Has no result if `container` is an arrow function.
*/
DataFlow::ThisNode thisNode(StmtContainer container) { result = TThisNode(container) }
/**
* INTERNAL. DO NOT USE.
*
* Gets the data flow node holding the reference to the module being destructured at
* the given import declaration.
*/
DataFlow::Node destructuredModuleImportNode(ImportDeclaration imprt) {
result = TDestructuredModuleImportNode(imprt)
}
/**
* INTERNAL: Use `ExceptionalInvocationReturnNode` instead.
*/
predicate exceptionalInvocationReturnNode(DataFlow::Node nd, InvokeExpr invocation) {
nd = TExceptionalInvocationReturnNode(invocation)
}
/**
* INTERNAL: Use `ExceptionalFunctionReturnNode` instead.
*/
predicate exceptionalFunctionReturnNode(DataFlow::Node nd, Function function) {
nd = TExceptionalFunctionReturnNode(function)
}
/**
* INTERNAL: Use `FunctionReturnNode` instead.
*/
predicate functionReturnNode(DataFlow::Node nd, Function function) {
nd = TFunctionReturnNode(function)
}
/**
* INTERNAL: Do not use outside standard library.
*
* Gets a data flow node unique to the given field declaration.
*
* Note that this node defaults to being disconnected from the data flow
* graph, as the individual property reads and writes affecting the field are
* analyzed independently of the field declaration.
*
* Certain framework models may need this node to model the behavior of
* class and field decorators.
*/
DataFlow::Node fieldDeclarationNode(FieldDeclaration field) { result = TPropNode(field) }
/**
* Gets the data flow node corresponding the given l-value expression, if
* such a node exists.
*
* This differs from `DataFlow::valueNode()`, which represents the value
* _before_ the l-value is assigned to, whereas `DataFlow::lvalueNode()`
* represents the value _after_ the assignment.
*/
Node lvalueNode(BindingPattern lvalue) { result = lvalueNodeInternal(lvalue) }
/**
* INTERNAL: Do not use outside standard library.
*
* Same as `lvalueNode()` except the return type is `EarlyStageNode`, which allows it to be used
* before all data flow nodes have been materialised.
*/
EarlyStageNode lvalueNodeInternal(BindingPattern lvalue) {
exists(SsaExplicitDefinition ssa |
ssa.defines(lvalue.(LValue).getDefNode(), lvalue.(VarRef).getVariable()) and
result = TSsaDefNode(ssa)
)
or
result = TValueNode(lvalue.(DestructuringPattern))
}
/**
* A classification of flows that are not modeled, or only modeled incompletely, by
* `DataFlowNode`:
*
* - `"await"`: missing flow through promises;
* - `"call"`: missing inter-procedural data flow;
* - `"eval"`: missing reflective data flow;
* - `"global"`: missing ata flow through global variables;
* - `"heap"`: missing data flow through properties;
* - `"import"`: missing data flow through module imports;
* - `"namespace"`: missing data flow through exported namespace members;
* - `"yield"`: missing data flow through generators.
*/
class Incompleteness extends string {
Incompleteness() {
this = ["await", "call", "eval", "global", "heap", "import", "namespace", "yield"]
}
}
/**
* Holds if `call` may call `callee`, and this call should be
* tracked by local data flow.
*/
private predicate localCall(InvokeExpr call, Function callee) {
call = callee.(ImmediatelyInvokedFunctionExpr).getInvocation()
}
/**
* Holds if some call passes `arg` as the value of `parm`, and this
* call should be tracked by local data flow.
*/
private predicate localArgumentPassing(Expr arg, Parameter parm) {
any(ImmediatelyInvokedFunctionExpr iife).argumentPassing(parm, arg)
}
/**
* Holds if there is a step from `pred -> succ` due to an assignment
* to an expression in l-value position.
*/
private predicate lvalueFlowStep(EarlyStageNode pred, EarlyStageNode succ) {
exists(VarDef def |
pred = TValueNode(defSourceNode(def)) and
succ = lvalueNodeInternal(def.getTarget())
)
or
exists(SimpleParameter param |
pred = TValueNode(param) and // The value node represents the incoming argument
succ = lvalueNodeInternal(param) // The SSA node represents the parameters's local variable
)
or
exists(Expr arg, Parameter param |
localArgumentPassing(arg, param) and
pred = TValueNode(arg) and
succ = TValueNode(param)
)
or
exists(PropertyPattern pattern |
pred = TPropNode(pattern) and
succ = lvalueNodeInternal(pattern.getValuePattern())
)
or
exists(Expr element |
pred = TElementPatternNode(_, element) and
succ = lvalueNodeInternal(element)
)
or
exists(Expr rest |
pred = TRestPatternNode(_, rest) and
succ = lvalueNodeInternal(rest)
)
}
/**
* Holds if there is a step from `pred -> succ` from the default
* value of a destructuring pattern or parameter.
*/
private predicate lvalueDefaultFlowStep(EarlyStageNode pred, EarlyStageNode succ) {
exists(PropertyPattern pattern |
pred = TValueNode(pattern.getDefault()) and
succ = lvalueNodeInternal(pattern.getValuePattern())
)
or
exists(ArrayPattern array, int i |
pred = TValueNode(array.getDefault(i)) and
succ = lvalueNodeInternal(array.getElement(i))
)
or
exists(Parameter param |
pred = TValueNode(param.getDefault()) and
succ = TValueNode(param)
)
}
/**
* Flow steps shared between `immediateFlowStep` and `localFlowStep`.
*
* Inlining is forced because the two relations are indexed differently.
*/
pragma[inline]
private predicate immediateFlowStepShared(EarlyStageNode pred, EarlyStageNode succ) {
exists(SsaVariable v |
pred = TSsaDefNode(v.getDefinition()) and
succ = TValueNode(v.getAUse())
)
or
exists(Expr predExpr, Expr succExpr |
pred = TValueNode(predExpr) and succ = TValueNode(succExpr)
|
predExpr = succExpr.(ParExpr).getExpression()
or
predExpr = succExpr.(SeqExpr).getLastOperand()
or
predExpr = succExpr.(AssignExpr).getRhs()
or
predExpr = succExpr.(TypeAssertion).getExpression()
or
predExpr = succExpr.(SatisfiesExpr).getExpression()
or
predExpr = succExpr.(NonNullAssertion).getExpression()
or
predExpr = succExpr.(ExpressionWithTypeArguments).getExpression()
or
(
succExpr instanceof AssignLogOrExpr or
succExpr instanceof AssignLogAndExpr or
succExpr instanceof AssignNullishCoalescingExpr
) and
(
predExpr = succExpr.(CompoundAssignExpr).getLhs() or
predExpr = succExpr.(CompoundAssignExpr).getRhs()
)
)
or
// flow from 'this' parameter into 'this' expressions
exists(ThisExpr thiz |
pred = TThisNode(thiz.getBindingContainer()) and
succ = TValueNode(thiz)
)
or
// `f.call(...)` and `f.apply(...)` evaluate to the result of the reflective call they perform
exists(MethodCallExpr call |
pred = TReflectiveCallNode(call, _) and
succ = TValueNode(call)
)
or
// Pass 'this' into implicit uses of 'this'
exists(ImplicitThisUse use |
pred = TThisNode(use.getBindingContainer()) and
succ = TImplicitThisUse(use, false)
)
}
pragma[nomagic]
private predicate immediateFlowStep(EarlyStageNode pred, EarlyStageNode succ) {
lvalueFlowStep(pred, succ) and
not lvalueDefaultFlowStep(_, succ)
or
immediateFlowStepShared(pred, succ)
or
// Refinement of variable -> original definition of variable
exists(SsaRefinementNode refinement |
succ = TSsaDefNode(refinement) and
pred = TSsaDefNode(refinement.getAnInput())
)
or
exists(SsaPhiNode phi |
succ = TSsaDefNode(phi) and
pred = TSsaDefNode(phi.getRephinedVariable())
)
or
// IIFE call -> return value of IIFE
exists(Function fun, Expr expr |
succ = TValueNode(expr) and
localCall(expr, fun) and
pred = TValueNode(unique(Expr ret | ret = fun.getAReturnedExpr())) and
not fun.getExit().isJoin() // can only reach exit by the return statement
)
}
/**
* Holds if data can flow from `pred` to `succ` in one local step.
*/
cached
predicate localFlowStep(EarlyStageNode pred, EarlyStageNode succ) {
Stages::EarlyDataFlowStage::ref() and
// flow from RHS into LHS
lvalueFlowStep(pred, succ)
or
lvalueDefaultFlowStep(pred, succ)
or
immediateFlowStepShared(pred, succ)
or
// From an assignment or implicit initialization of a captured variable to its flow-insensitive node.
exists(SsaDefinition predDef |
pred = TSsaDefNode(predDef) and
succ = TCapturedVariableNode(predDef.getSourceVariable())
|
predDef instanceof SsaExplicitDefinition or
predDef instanceof SsaImplicitInit
)
or
// From a captured variable node to its flow-sensitive capture nodes
exists(SsaVariableCapture ssaCapture |
pred = TCapturedVariableNode(ssaCapture.getSourceVariable()) and
succ = TSsaDefNode(ssaCapture)
)
or
// Flow through implicit SSA nodes
exists(SsaImplicitDefinition ssa | succ = TSsaDefNode(ssa) |
// from the inputs of phi and pi nodes into the node itself
pred = TSsaDefNode(ssa.(SsaPseudoDefinition).getAnInput().getDefinition())
)
or
exists(Expr predExpr, Expr succExpr |
pred = TValueNode(predExpr) and succ = TValueNode(succExpr)
|
predExpr = succExpr.(LogicalOrExpr).getAnOperand()
or
predExpr = succExpr.(NullishCoalescingExpr).getAnOperand()
or
predExpr = succExpr.(LogicalAndExpr).getRightOperand()
or
predExpr = succExpr.(ConditionalExpr).getABranch()
or
exists(Function f |
predExpr = f.getAReturnedExpr() and
localCall(succExpr, f)
)
)
or
// from returned expr to the FunctionReturnNode.
exists(Function f | not f.isAsyncOrGenerator() |
succ = TFunctionReturnNode(f) and pred = TValueNode(f.getAReturnedExpr())
)
or
// from a reflective params node to a reference to the arguments object.
exists(Function f |
pred = TReflectiveParametersNode(f) and
succ = TValueNode(f.getArgumentsVariable().getAnAccess())
)
}
overlay[global]
private class ReflectiveParamsStep extends LegacyPreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::ReflectiveParametersNode params, DataFlow::FunctionNode f, int i |
f.getFunction() = params.getFunction() and
obj = params and
prop = i + "" and
element = f.getParameter(i)
)
}
}
/** A taint step from the reflective parameters node to any parameter. */
overlay[global]
private class ReflectiveParamsTaintStep extends TaintTracking::LegacyTaintStep {
override predicate step(DataFlow::Node obj, DataFlow::Node element) {
exists(DataFlow::ReflectiveParametersNode params, DataFlow::FunctionNode f |
f.getFunction() = params.getFunction() and
obj = params and
element = f.getAParameter()
)
}
}
/**
* Holds if there is a step from `pred` to `succ` through a field accessed through `this` in a class.
*/
overlay[global]
predicate localFieldStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(ClassNode cls, string prop |
pred = AccessPath::getAnAssignmentTo(cls.getADirectSuperClass*().getAReceiverNode(), prop) or
pred = cls.getInstanceMethod(prop)
|
succ = AccessPath::getAReferenceTo(cls.getAReceiverNode(), prop)
)
}
predicate argumentPassingStep = FlowSteps::argumentPassing/4;
/**
* Gets the data flow node representing the source of definition `def`, taking
* flow through IIFE calls into account.
*/
private AST::ValueNode defSourceNode(VarDef def) {
result = def.getSource() or
result = def.getDestructuringSource()
}
/**
* Holds if the flow information for the node `nd`.
*
* This predicate holds if there may be a source flow node from which data flows into
* this node, but that node is not a result of `getALocalSource()` due to analysis incompleteness.
* The parameter `cause` is bound to a string describing the source of incompleteness.
*
* For example, since this analysis is intra-procedural, data flow from actual arguments
* to formal parameters is not modeled. Hence, if `p` is an access to a parameter,
* `p.getALocalSource()` does _not_ return the corresponding argument, and
* `p.isIncomplete("call")` holds.
*/
overlay[global]
predicate isIncomplete(Node nd, Incompleteness cause) {
exists(SsaVariable ssa | nd = TSsaDefNode(ssa.getDefinition()) |
defIsIncomplete(ssa.(SsaExplicitDefinition).getDef(), cause)
or
exists(Variable v | v = ssa.getSourceVariable() |
v.isNamespaceExport() and cause = "namespace"
or
any(DirectEval e).mayAffect(v) and cause = "eval"
)
)
or
exists(GlobalVarAccess va |
nd = valueNode(va.(VarUse)) and
if Closure::isClosureNamespace(va.getName()) then cause = "heap" else cause = "global"
)
or
exists(Expr e | e = nd.asExpr() and cause = "call" |
e instanceof InvokeExpr and
not localCall(e, _)
or
e instanceof ThisExpr
or
e instanceof SuperExpr
or
e instanceof NewTargetExpr
or
e instanceof ImportMetaExpr
or
e instanceof FunctionBindExpr
or
e instanceof TaggedTemplateExpr
or
e instanceof Parameter and
not localArgumentPassing(_, e) and
not isAnalyzedParameter(e) and
not e.(Parameter).isRestParameter()
)
or
nd.(AnalyzedNode).hasAdditionalIncompleteness(cause)
or
nd.asExpr() instanceof ExternalModuleReference and
cause = "import"
or
exists(Expr e | e = nd.asExpr() and cause = "heap" |
e instanceof PropAccess or
e instanceof E4X::XmlAnyName or
e instanceof E4X::XmlAttributeSelector or
e instanceof E4X::XmlDotDotExpression or
e instanceof E4X::XmlFilterExpression or
e instanceof E4X::XmlQualifiedIdentifier or
e instanceof Angular2::PipeRefExpr
)
or
exists(Expr e | e = nd.asExpr() |
(e instanceof YieldExpr or e instanceof FunctionSentExpr) and
cause = "yield"
or
(e instanceof AwaitExpr or e instanceof DynamicImportExpr) and
cause = "await"
or
e instanceof GeneratedCodeExpr and
cause = "eval" // we use 'eval' here to represent code generation more broadly
)
or
nd instanceof TExceptionalInvocationReturnNode and cause = "call"
or
nd instanceof TExceptionalFunctionReturnNode and cause = "call"
or
exists(PropertyPattern p | nd = TPropNode(p)) and cause = "heap"
or
nd instanceof TElementPatternNode and cause = "heap"
}
/**
* Holds if definition `def` cannot be completely analyzed due to `cause`.
*/
private predicate defIsIncomplete(VarDef def, Incompleteness cause) {
def instanceof ImportSpecifier and
cause = "import"
or
exists(EnhancedForLoop efl | def = efl.getIteratorExpr()) and
cause = "heap"
or
exists(ComprehensionBlock cb | def = cb.getIterator()) and
cause = "yield"
}
import Nodes
import Sources
import TypeInference
deprecated import Configuration
import TypeTracking
import AdditionalFlowSteps
import PromisifyFlow
import internal.FunctionWrapperSteps
import internal.sharedlib.DataFlow
import internal.BarrierGuards
import FlowSummary
}