mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
1759 lines
55 KiB
Plaintext
1759 lines
55 KiB
Plaintext
/**
|
|
* Provides classes representing particular kinds of data flow nodes, such
|
|
* as nodes corresponding to function definitions or nodes corresponding to
|
|
* parameters.
|
|
*/
|
|
|
|
private import javascript
|
|
private import semmle.javascript.dependencies.Dependencies
|
|
private import internal.CallGraphs
|
|
private import semmle.javascript.internal.CachedStages
|
|
private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
|
|
|
/**
|
|
* A data flow node corresponding to an expression.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* x + y
|
|
* Math.abs(x)
|
|
* ```
|
|
*/
|
|
class ExprNode extends DataFlow::ValueNode {
|
|
override Expr astNode;
|
|
|
|
pragma[nomagic]
|
|
ExprNode() { any() }
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to a parameter.
|
|
*
|
|
* For example, `x` is a parameter of `function f(x) {}`.
|
|
*
|
|
* When a parameter is a destructuring pattern, such as `{x, y}`, there is one
|
|
* parameter node representing the entire parameter, and separate `PropRead` nodes
|
|
* for the individual property patterns (`x` and `y`).
|
|
*/
|
|
class ParameterNode extends DataFlow::SourceNode {
|
|
Parameter p;
|
|
|
|
ParameterNode() { DataFlow::parameterNode(this, p) }
|
|
|
|
/** Gets the parameter to which this data flow node corresponds. */
|
|
Parameter getParameter() { result = p }
|
|
|
|
/** Gets the name of this parameter. */
|
|
string getName() { result = p.getName() }
|
|
|
|
/** Holds if this parameter is a rest parameter. */
|
|
predicate isRestParameter() { p.isRestParameter() }
|
|
|
|
/** Gets the data flow node for an expression that is applied to this decorator. */
|
|
DataFlow::Node getADecorator() {
|
|
result = this.getParameter().getADecorator().getExpression().flow()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to a function invocation (with or without `new`).
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* Math.abs(x)
|
|
* new Array(16)
|
|
* ```
|
|
*/
|
|
class InvokeNode extends DataFlow::SourceNode instanceof DataFlow::Impl::InvokeNodeDef {
|
|
/** Gets the syntactic invoke expression underlying this function invocation. */
|
|
InvokeExpr getInvokeExpr() { result = super.getInvokeExpr() }
|
|
|
|
/** Gets the name of the function or method being invoked, if it can be determined. */
|
|
string getCalleeName() { result = super.getCalleeName() }
|
|
|
|
/** Gets the data flow node specifying the function to be called. */
|
|
DataFlow::Node getCalleeNode() { result = super.getCalleeNode() }
|
|
|
|
/**
|
|
* Gets the data flow node corresponding to the `i`th argument of this invocation.
|
|
*
|
|
* For direct calls, this is the `i`th argument to the call itself: for instance,
|
|
* for a call `f(x, y)`, the 0th argument node is `x` and the first argument node is `y`.
|
|
*
|
|
* For reflective calls using `call`, the 0th argument to the call denotes the
|
|
* receiver, so argument positions are shifted by one: for instance, for a call
|
|
* `f.call(x, y, z)`, the 0th argument node is `y` and the first argument node is `z`,
|
|
* while `x` is not an argument node at all.
|
|
*
|
|
* For reflective calls using `apply` we cannot, in general, tell which argument
|
|
* occurs at which position, so this predicate is not defined for such calls.
|
|
*
|
|
* Note that this predicate is not defined for arguments following a spread
|
|
* argument: for instance, for a call `f(x, ...y, z)`, the 0th argument node is `x`,
|
|
* but the position of `z` cannot be determined, hence there are no first and second
|
|
* argument nodes.
|
|
*/
|
|
cached
|
|
DataFlow::Node getArgument(int i) {
|
|
result = super.getArgument(i) and Stages::DataFlowStage::ref()
|
|
}
|
|
|
|
/** Gets the data flow node corresponding to an argument of this invocation. */
|
|
cached
|
|
DataFlow::Node getAnArgument() { result = super.getAnArgument() and Stages::DataFlowStage::ref() }
|
|
|
|
/** Gets the data flow node corresponding to the last argument of this invocation. */
|
|
cached
|
|
DataFlow::Node getLastArgument() {
|
|
result = this.getArgument(this.getNumArgument() - 1) and Stages::DataFlowStage::ref()
|
|
}
|
|
|
|
/**
|
|
* Gets a data flow node corresponding to an array of values being passed as
|
|
* individual arguments to this invocation.
|
|
*
|
|
* Examples:
|
|
* ```
|
|
* x.push(...args); // 'args' is a spread argument
|
|
* x.push(x, ...args, y, ...more); // 'args' and 'more' are a spread arguments
|
|
* Array.prototype.push.apply(x, args); // 'args' is a spread argument
|
|
* ```
|
|
* .
|
|
*/
|
|
DataFlow::Node getASpreadArgument() { result = super.getASpreadArgument() }
|
|
|
|
/** Gets the number of arguments of this invocation, if it can be determined. */
|
|
int getNumArgument() { result = super.getNumArgument() }
|
|
|
|
Function getEnclosingFunction() { result = this.getBasicBlock().getContainer() }
|
|
|
|
/**
|
|
* Gets a function passed as the `i`th argument of this invocation.
|
|
*
|
|
* This predicate only performs local data flow tracking.
|
|
* Consider using `getABoundCallbackParameter` to handle interprocedural flow of callback functions.
|
|
*/
|
|
FunctionNode getCallback(int i) { result.flowsTo(this.getArgument(i)) }
|
|
|
|
/**
|
|
* Gets a parameter of a callback passed into this call.
|
|
*
|
|
* `callback` indicates which argument the callback passed into, and `param`
|
|
* is the index of the parameter in the callback function.
|
|
*
|
|
* For example, for the call below, `getABoundCallbackParameter(1, 0)` refers
|
|
* to the parameter `e` (the first parameter of the second callback argument):
|
|
* ```js
|
|
* addEventHandler("click", e => { ... })
|
|
* ```
|
|
*
|
|
* This predicate takes interprocedural data flow into account, as well as
|
|
* partial function applications such as `.bind`.
|
|
*
|
|
* For example, for the call below `getABoundCallbackParameter(1, 0)` returns the parameter `e`,
|
|
* (the first parameter of the second callback argument), since the first parameter of `foo`
|
|
* has been bound by the `bind` call:
|
|
* ```js
|
|
* function foo(x, e) { }
|
|
* addEventHandler("click", foo.bind(this, "value of x"))
|
|
* ```
|
|
*/
|
|
ParameterNode getABoundCallbackParameter(int callback, int param) {
|
|
exists(int boundArgs |
|
|
result =
|
|
this.getArgument(callback).getABoundFunctionValue(boundArgs).getParameter(param + boundArgs)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if the `i`th argument of this invocation is an object literal whose property
|
|
* `name` is set to `result`.
|
|
*/
|
|
pragma[nomagic]
|
|
DataFlow::ValueNode getOptionArgument(int i, string name) {
|
|
this.getOptionsArgument(i).hasPropertyWrite(name, result)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private ObjectLiteralNode getOptionsArgument(int i) { result.flowsTo(this.getArgument(i)) }
|
|
|
|
/** Gets an abstract value representing possible callees of this call site. */
|
|
final AbstractValue getACalleeValue() {
|
|
exists(DataFlow::Node callee, DataFlow::AnalyzedNode analyzed |
|
|
pragma[only_bind_into](callee) = this.getCalleeNode() and
|
|
pragma[only_bind_into](analyzed) = callee.analyze() and
|
|
pragma[only_bind_into](result) = analyzed.getAValue()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a potential callee of this call site.
|
|
*
|
|
* To alter the call graph as seen by the interprocedural data flow libraries, override
|
|
* the `getACallee(int imprecision)` predicate instead.
|
|
*/
|
|
final Function getACallee() { result = this.getACallee(0) }
|
|
|
|
/**
|
|
* Gets a callee of this call site where `imprecision` is a heuristic measure of how
|
|
* likely it is that `callee` is only suggested as a potential callee due to
|
|
* imprecise analysis of global variables and is not, in fact, a viable callee at all.
|
|
*
|
|
* Callees with imprecision zero, in particular, have either been derived without
|
|
* considering global variables, or are calls to a global variable within the same file,
|
|
* or a global variable that has unique definition within the project.
|
|
*
|
|
* This predicate can be overridden to alter the call graph used by the interprocedural
|
|
* data flow libraries.
|
|
*/
|
|
Function getACallee(int imprecision) {
|
|
result = CallGraph::getACallee(this, imprecision).getFunction()
|
|
}
|
|
|
|
/**
|
|
* Holds if the approximation of possible callees for this call site is
|
|
* affected by the given analysis incompleteness `cause`.
|
|
*/
|
|
predicate isIndefinite(DataFlow::Incompleteness cause) {
|
|
this.getACalleeValue().isIndefinite(cause)
|
|
}
|
|
|
|
/**
|
|
* Holds if our approximation of possible callees for this call site is
|
|
* likely to be imprecise.
|
|
*
|
|
* We currently track one specific source of imprecision: call
|
|
* resolution relies on flow through global variables, and the flow
|
|
* analysis finds possible callees that are not functions.
|
|
* This usually means that a global variable is used in multiple
|
|
* independent contexts, so tracking flow through it leads to
|
|
* imprecision.
|
|
*/
|
|
predicate isImprecise() {
|
|
this.isIndefinite("global") and
|
|
exists(DefiniteAbstractValue v | v = this.getACalleeValue() | not v instanceof AbstractCallable)
|
|
}
|
|
|
|
/**
|
|
* Holds if our approximation of possible callees for this call site is
|
|
* likely to be incomplete.
|
|
*/
|
|
predicate isIncomplete() {
|
|
// the flow analysis identifies a source of incompleteness other than
|
|
// global flow (which usually leads to imprecision rather than incompleteness)
|
|
any(DataFlow::Incompleteness cause | this.isIndefinite(cause)) != "global"
|
|
}
|
|
|
|
/**
|
|
* Holds if our approximation of possible callees for this call site is
|
|
* likely to be imprecise or incomplete.
|
|
*/
|
|
predicate isUncertain() { this.isImprecise() or this.isIncomplete() }
|
|
|
|
/**
|
|
* Gets the data flow node representing an exception thrown from this invocation.
|
|
*/
|
|
DataFlow::ExceptionalInvocationReturnNode getExceptionalReturn() {
|
|
DataFlow::exceptionalInvocationReturnNode(result, this.asExpr())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to a function call without `new`.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* Math.abs(x)
|
|
* ```
|
|
*/
|
|
class CallNode extends InvokeNode instanceof DataFlow::Impl::CallNodeDef {
|
|
/**
|
|
* Gets the data flow node corresponding to the receiver expression of this method call.
|
|
*
|
|
* For example, the receiver of `x.m()` is `x`.
|
|
*/
|
|
DataFlow::Node getReceiver() { result = super.getReceiver() }
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to a method call, that is,
|
|
* a call of form `x.m(...)`.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* obj.foo()
|
|
* Math.abs(x)
|
|
* ```
|
|
*/
|
|
class MethodCallNode extends CallNode instanceof DataFlow::Impl::MethodCallNodeDef {
|
|
/** Gets the name of the invoked method, if it can be determined. */
|
|
string getMethodName() { result = super.getMethodName() }
|
|
|
|
/**
|
|
* Holds if this data flow node calls method `methodName` on receiver node `receiver`.
|
|
*/
|
|
predicate calls(DataFlow::Node receiver, string methodName) {
|
|
receiver = this.getReceiver() and
|
|
methodName = this.getMethodName()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to a `new` expression.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* new Array(16)
|
|
* ```
|
|
*/
|
|
class NewNode extends InvokeNode instanceof DataFlow::Impl::NewNodeDef { }
|
|
|
|
/**
|
|
* A data flow node corresponding to the `this` parameter in a function or `this` at the top-level.
|
|
*
|
|
* Each function only has one `this` node, even if it references `this` multiple times.
|
|
*/
|
|
class ThisNode extends DataFlow::Node, DataFlow::SourceNode {
|
|
ThisNode() { DataFlow::thisNode(this, _) }
|
|
|
|
/**
|
|
* Gets the function whose `this` binding this expression refers to,
|
|
* which is the nearest enclosing non-arrow function.
|
|
*/
|
|
FunctionNode getBinder() {
|
|
exists(Function binder |
|
|
DataFlow::thisNode(this, binder) and
|
|
result = DataFlow::valueNode(binder)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the function or top-level whose `this` binding this expression refers to,
|
|
* which is the nearest enclosing non-arrow function or top-level.
|
|
*/
|
|
StmtContainer getBindingContainer() { DataFlow::thisNode(this, result) }
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to a global variable access through a simple identifier.
|
|
*
|
|
* For example, this includes `document` but not `window.document`.
|
|
* To recognize global variable access more generally, instead use `DataFlow::globalVarRef`
|
|
* or the member predicate `accessesGlobal`.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* document
|
|
* Math
|
|
* NaN
|
|
* undefined
|
|
* ```
|
|
*/
|
|
class GlobalVarRefNode extends DataFlow::ValueNode, DataFlow::SourceNode {
|
|
override GlobalVarAccess astNode;
|
|
|
|
/** Gets the name of the global variable. */
|
|
string getName() { result = astNode.getName() }
|
|
}
|
|
|
|
/**
|
|
* Gets a data flow node corresponding to an access to the global object, including
|
|
* `this` expressions outside functions, references to global variables `window`
|
|
* and `global`, and uses of the `global` npm package.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* window
|
|
* window.window
|
|
* global
|
|
* globalThis
|
|
* this
|
|
* self
|
|
* require('global/window')
|
|
* ```
|
|
*/
|
|
DataFlow::SourceNode globalObjectRef() {
|
|
// top-level `this`
|
|
exists(StmtContainer sc |
|
|
sc = result.(ThisNode).getBindingContainer() and
|
|
not sc instanceof Function and
|
|
not sc instanceof Templating::TemplateTopLevel
|
|
)
|
|
or
|
|
// DOM
|
|
result = globalVariable("window")
|
|
or
|
|
// Node.js
|
|
result = globalVariable("global")
|
|
or
|
|
// DOM and service workers
|
|
result = globalVariable("self")
|
|
or
|
|
// ECMAScript 2020
|
|
result = globalVariable("globalThis")
|
|
or
|
|
// `require("global")`
|
|
result = moduleImport("global")
|
|
or
|
|
// Closure library - based on AST to avoid recursion with Closure library model
|
|
result = globalVariable("goog").getAPropertyRead("global")
|
|
}
|
|
|
|
/**
|
|
* Gets a reference to a global variable `name`.
|
|
* For example, if `name` is "foo":
|
|
* ```js
|
|
* foo
|
|
* require('global/foo')
|
|
* ```
|
|
*/
|
|
private DataFlow::SourceNode globalVariable(string name) {
|
|
result.(GlobalVarRefNode).getName() = name
|
|
or
|
|
// `require("global/document")` or `require("global/window")`
|
|
(name = "document" or name = "window") and
|
|
result = moduleImport("global/" + name)
|
|
}
|
|
|
|
/**
|
|
* Gets a data flow node corresponding to an access to global variable `name`,
|
|
* either directly, through `window` or `global`, or through the `global` npm package.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* document
|
|
* Math
|
|
* window.document
|
|
* window.Math
|
|
* require('global/document')
|
|
* ```
|
|
*/
|
|
pragma[nomagic]
|
|
DataFlow::SourceNode globalVarRef(string name) {
|
|
result = globalVariable(name)
|
|
or
|
|
result = globalObjectRef().getAPropertyReference(name)
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to a function definition.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```
|
|
* function greet() { // function declaration
|
|
* console.log("Hi");
|
|
* }
|
|
*
|
|
* var greet =
|
|
* function() { // function expression
|
|
* console.log("Hi");
|
|
* };
|
|
*
|
|
* var greet2 =
|
|
* () => console.log("Hi") // arrow function expression
|
|
*
|
|
* var o = {
|
|
* m() { // function expression in a method definition in an object literal
|
|
* return 0;
|
|
* },
|
|
* get x() { // function expression in a getter method definition in an object literal
|
|
* return 1
|
|
* }
|
|
* };
|
|
*
|
|
* class C {
|
|
* m() { // function expression in a method definition in a class
|
|
* return 0;
|
|
* }
|
|
* }
|
|
* ```
|
|
*/
|
|
class FunctionNode extends DataFlow::ValueNode, DataFlow::SourceNode {
|
|
override Function astNode;
|
|
|
|
/** Gets the `i`th parameter of this function. */
|
|
ParameterNode getParameter(int i) { result = DataFlow::parameterNode(astNode.getParameter(i)) }
|
|
|
|
/** Gets a parameter of this function. */
|
|
ParameterNode getAParameter() { result = this.getParameter(_) }
|
|
|
|
/** Gets the parameter named `name` of this function, if any. */
|
|
DataFlow::ParameterNode getParameterByName(string name) {
|
|
result = this.getAParameter() and
|
|
result.getName() = name
|
|
}
|
|
|
|
/** Gets the number of parameters declared on this function. */
|
|
int getNumParameter() { result = count(astNode.getAParameter()) }
|
|
|
|
/** Gets the last parameter of this function. */
|
|
ParameterNode getLastParameter() { result = this.getParameter(this.getNumParameter() - 1) }
|
|
|
|
/** Holds if the last parameter of this function is a rest parameter. */
|
|
predicate hasRestParameter() { astNode.hasRestParameter() }
|
|
|
|
/** Gets the unqualified name of this function, if it has one or one can be determined from the context. */
|
|
string getName() { result = astNode.getName() }
|
|
|
|
/** Gets a data flow node corresponding to a return value of this function. */
|
|
DataFlow::Node getAReturn() { result = astNode.getAReturnedExpr().flow() }
|
|
|
|
/**
|
|
* Gets the function this node corresponds to.
|
|
*/
|
|
Function getFunction() { result = astNode }
|
|
|
|
/**
|
|
* Gets the function whose `this` binding a `this` expression in this function refers to,
|
|
* which is the nearest enclosing non-arrow function.
|
|
*/
|
|
FunctionNode getThisBinder() { result.getFunction() = this.getFunction().getThisBinder() }
|
|
|
|
/**
|
|
* Gets the dataflow node holding the value of the receiver passed to the given function.
|
|
*
|
|
* Has no result for arrow functions, as they ignore the receiver argument.
|
|
*
|
|
* To get the data flow node for `this` in an arrow function, consider using `getThisBinder().getReceiver()`.
|
|
*/
|
|
ThisNode getReceiver() { result.getBinder() = this }
|
|
|
|
/**
|
|
* Gets the data flow node representing an exception thrown from this function.
|
|
*/
|
|
DataFlow::ExceptionalFunctionReturnNode getExceptionalReturn() {
|
|
DataFlow::exceptionalFunctionReturnNode(result, astNode)
|
|
}
|
|
|
|
/**
|
|
* Gets the data flow node representing the value returned from this function.
|
|
*
|
|
* Note that this differs from `getAReturn()`, in that every function has exactly
|
|
* one canonical return node, but may have multiple (or zero) returned expressions.
|
|
* The result of `getAReturn()` is always a predecessor of `getReturnNode()`
|
|
* in the data-flow graph.
|
|
*/
|
|
DataFlow::FunctionReturnNode getReturnNode() { DataFlow::functionReturnNode(result, astNode) }
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to an object literal expression.
|
|
*
|
|
* Example:
|
|
* ```js
|
|
* var p = { // object literal containing five property definitions
|
|
* x: 1,
|
|
* y: 1,
|
|
* diag: function() { return this.x - this.y; },
|
|
* get area() { return this.x * this.y; },
|
|
* set area(a) { this.x = Math.sqrt(a); this.y = Math.sqrt(a); }
|
|
* };
|
|
* ```
|
|
*/
|
|
class ObjectLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
|
|
override ObjectExpr astNode;
|
|
|
|
/** Gets the value of a spread property of this object literal, such as `x` in `{...x}` */
|
|
DataFlow::Node getASpreadProperty() {
|
|
result = astNode.getAProperty().(SpreadProperty).getInit().(SpreadElement).getOperand().flow()
|
|
}
|
|
|
|
/** Gets the property getter of the given name, installed on this object literal. */
|
|
DataFlow::FunctionNode getPropertyGetter(string name) {
|
|
result = astNode.getPropertyByName(name).(PropertyGetter).getInit().flow()
|
|
}
|
|
|
|
/** Gets the property setter of the given name, installed on this object literal. */
|
|
DataFlow::FunctionNode getPropertySetter(string name) {
|
|
result = astNode.getPropertyByName(name).(PropertySetter).getInit().flow()
|
|
}
|
|
|
|
/** Gets the value of a computed property name of this object literal, such as `x` in `{[x]: 1}` */
|
|
DataFlow::Node getAComputedPropertyName() {
|
|
exists(Property prop | prop = astNode.getAProperty() |
|
|
prop.isComputed() and
|
|
result = prop.getNameExpr().flow()
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to an array literal expression.
|
|
*
|
|
* Example:
|
|
* ```
|
|
* [ 1, , [ 3, 4 ] ]
|
|
* ```
|
|
*/
|
|
class ArrayLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
|
|
override ArrayExpr astNode;
|
|
|
|
/** Gets the `i`th element of this array literal. */
|
|
DataFlow::ValueNode getElement(int i) { result = DataFlow::valueNode(astNode.getElement(i)) }
|
|
|
|
/** Gets an element of this array literal. */
|
|
DataFlow::ValueNode getAnElement() { result = DataFlow::valueNode(astNode.getAnElement()) }
|
|
|
|
/** Gets the initial size of this array. */
|
|
int getSize() { result = astNode.getSize() }
|
|
}
|
|
|
|
/**
|
|
* A data-flow node corresponding to a regular-expression literal.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* /https?/i
|
|
* ```
|
|
*/
|
|
class RegExpLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
|
|
override RegExpLiteral astNode;
|
|
|
|
/** Holds if this regular expression has a `g` flag. */
|
|
predicate isGlobal() { astNode.isGlobal() }
|
|
|
|
/** Gets the root term of this regular expression. */
|
|
RegExpTerm getRoot() { result = astNode.getRoot() }
|
|
|
|
/** Gets the flags of this regular expression literal. */
|
|
string getFlags() { result = astNode.getFlags() }
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to a `new Array()` or `Array()` invocation.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* Array('apple', 'orange') // two initial elements
|
|
* new Array('apple', 'orange')
|
|
* Array(16) // size 16 but no initial elements
|
|
* new Array(16)
|
|
* ```
|
|
*/
|
|
class ArrayConstructorInvokeNode extends DataFlow::InvokeNode {
|
|
ArrayConstructorInvokeNode() { this.getCalleeNode() = DataFlow::globalVarRef("Array") }
|
|
|
|
/** Gets the `i`th initial element of this array, if one is provided. */
|
|
DataFlow::ValueNode getElement(int i) {
|
|
this.getNumArgument() > 1 and // A single-argument invocation specifies the array length, not an element.
|
|
result = this.getArgument(i)
|
|
}
|
|
|
|
/** Gets an initial element of this array, if one is provided. */
|
|
DataFlow::ValueNode getAnElement() {
|
|
this.getNumArgument() > 1 and
|
|
result = this.getAnArgument()
|
|
}
|
|
|
|
/** Gets the initial size of the created array, if it can be determined. */
|
|
int getSize() {
|
|
if this.getNumArgument() = 1
|
|
then result = this.getArgument(0).getIntValue()
|
|
else result = count(this.getAnElement())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to the creation or a new array, either through an array literal,
|
|
* an invocation of the `Array` constructor, or the `Array.from` method.
|
|
*
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* ['apple', 'orange'];
|
|
* Array('apple', 'orange')
|
|
* new Array('apple', 'orange')
|
|
* Array(16)
|
|
* new Array(16)
|
|
* ```
|
|
*/
|
|
class ArrayCreationNode extends DataFlow::ValueNode, DataFlow::SourceNode {
|
|
ArrayCreationNode() {
|
|
this instanceof ArrayLiteralNode or
|
|
this instanceof ArrayConstructorInvokeNode
|
|
}
|
|
|
|
/** Gets the `i`th initial element of this array, if one is provided. */
|
|
DataFlow::ValueNode getElement(int i) {
|
|
result = this.(ArrayLiteralNode).getElement(i) or
|
|
result = this.(ArrayConstructorInvokeNode).getElement(i)
|
|
}
|
|
|
|
/** Gets an initial element of this array, if one if provided. */
|
|
DataFlow::ValueNode getAnElement() { result = this.getElement(_) }
|
|
|
|
/** Gets the initial size of the created array, if it can be determined. */
|
|
int getSize() {
|
|
result = this.(ArrayLiteralNode).getSize() or
|
|
result = this.(ArrayConstructorInvokeNode).getSize()
|
|
}
|
|
|
|
/**
|
|
* Gets a data flow node corresponding to an array of values being passed as
|
|
* individual arguments to this array creation.
|
|
*/
|
|
DataFlow::Node getASpreadArgument() {
|
|
exists(SpreadElement arg | arg = this.getAnElement().getEnclosingExpr() |
|
|
result = DataFlow::valueNode(arg.getOperand())
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A data flow node representing an import of a module, either through
|
|
* an `import` declaration, a call to `require`, or an AMD dependency parameter.
|
|
*
|
|
* For compatibility with old transpilers, we treat both `import * as x from '...'` and
|
|
* `import x from '...'` as module import nodes.
|
|
*
|
|
* Additional import nodes can be added by subclassing `ModuleImportNode::Range`.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* require('fs');
|
|
* import * as fs from 'fs';
|
|
* import fs from 'fs';
|
|
* import { readDir } from 'fs'; // Note: 'readDir' is a PropRead with a ModuleImportNode as base
|
|
* define(["fs"], function(fs) { ... }); // AMD module
|
|
* ```
|
|
*/
|
|
class ModuleImportNode extends DataFlow::SourceNode instanceof ModuleImportNode::Range {
|
|
/** Gets the path of the imported module. */
|
|
string getPath() { result = super.getPath() }
|
|
}
|
|
|
|
module ModuleImportNode {
|
|
/**
|
|
* A data flow node that refers to an imported module.
|
|
*/
|
|
abstract class Range extends DataFlow::SourceNode {
|
|
/** Gets the path of the imported module. */
|
|
abstract string getPath();
|
|
}
|
|
|
|
private class DefaultRange extends Range {
|
|
string path;
|
|
|
|
DefaultRange() {
|
|
exists(Import i |
|
|
this = i.getImportedModuleNode() and
|
|
i.getImportedPath().getValue() = path
|
|
)
|
|
or
|
|
// AMD require
|
|
exists(AmdModuleDefinition amd, CallExpr req |
|
|
req = amd.getARequireCall() and
|
|
this = DataFlow::valueNode(req) and
|
|
path = req.getArgument(0).getStringValue()
|
|
)
|
|
}
|
|
|
|
/** Gets the path of the imported module. */
|
|
override string getPath() { result = path }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a (default) import of the module with the given path, such as `require("fs")`
|
|
* or `import * as fs from "fs"`.
|
|
*
|
|
* This predicate can be extended by subclassing `ModuleImportNode::Range`.
|
|
*/
|
|
cached
|
|
ModuleImportNode moduleImport(string path) {
|
|
// NB. internal modules may be imported with a "node:" prefix
|
|
Stages::Imports::ref() and result.getPath() = ["node:" + path, path]
|
|
}
|
|
|
|
/**
|
|
* Gets a (default) import of the given dependency `dep`, such as
|
|
* `require("lodash")` in a context where a package.json file includes
|
|
* `"lodash"` as a dependency.
|
|
*/
|
|
ModuleImportNode dependencyModuleImport(Dependency dep) {
|
|
result = dep.getAUse("import").(Import).getImportedModuleNode()
|
|
}
|
|
|
|
/**
|
|
* Gets a data flow node that either imports `m` from the module with
|
|
* the given `path`, or accesses `m` as a member on a default or
|
|
* namespace import from `path`.
|
|
*/
|
|
DataFlow::SourceNode moduleMember(string path, string m) {
|
|
result = moduleImport(path).getAPropertyRead(m)
|
|
}
|
|
|
|
/**
|
|
* The string `method`, `getter`, or `setter`, representing the kind of a function member
|
|
* in a class.
|
|
*
|
|
* Can can be used with `ClassNode.getInstanceMember` to obtain members of a specific kind.
|
|
*/
|
|
class MemberKind extends string {
|
|
MemberKind() { this = "method" or this = "getter" or this = "setter" }
|
|
|
|
/** Holds if this is the `method` kind. */
|
|
predicate isMethod() { this = MemberKind::method() }
|
|
|
|
/** Holds if this is the `getter` kind. */
|
|
predicate isGetter() { this = MemberKind::getter() }
|
|
|
|
/** Holds if this is the `setter` kind. */
|
|
predicate isSetter() { this = MemberKind::setter() }
|
|
|
|
/** Holds if this is the `getter` or `setter` kind. */
|
|
predicate isAccessor() { this = MemberKind::accessor() }
|
|
}
|
|
|
|
private import internal.StepSummary
|
|
|
|
module MemberKind {
|
|
/** Gets the kind of a method, such as `m() {}` */
|
|
MemberKind method() { result = "method" }
|
|
|
|
/** Gets the kind of a getter accessor, such as `get f() {}`. */
|
|
MemberKind getter() { result = "getter" }
|
|
|
|
/** Gets the kind of a setter accessor, such as `set f() {}`. */
|
|
MemberKind setter() { result = "setter" }
|
|
|
|
/** Gets the `getter` and `setter` kinds. */
|
|
MemberKind accessor() { result = getter() or result = setter() }
|
|
|
|
/**
|
|
* Gets the member kind of a given syntactic member declaration in a class.
|
|
*/
|
|
MemberKind of(MemberDeclaration decl) {
|
|
decl instanceof GetterMethodDeclaration and result = getter()
|
|
or
|
|
decl instanceof SetterMethodDeclaration and result = setter()
|
|
or
|
|
decl instanceof MethodDeclaration and
|
|
not decl instanceof AccessorMethodDeclaration and
|
|
not decl instanceof ConstructorDeclaration and
|
|
result = method()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to a class definition or a function definition
|
|
* acting as a class.
|
|
*
|
|
* The following patterns are recognized as classes and methods:
|
|
* ```
|
|
* class C {
|
|
* method()
|
|
* }
|
|
*
|
|
* function F() {}
|
|
*
|
|
* F.prototype.method = function() {}
|
|
*
|
|
* F.prototype = {
|
|
* method: function() {}
|
|
* }
|
|
*
|
|
* extend(F.prototype, {
|
|
* method: function() {}
|
|
* });
|
|
* ```
|
|
*
|
|
* Additional patterns can be recognized as class nodes, by extending `DataFlow::ClassNode::Range`.
|
|
*/
|
|
class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range {
|
|
/**
|
|
* Gets the unqualified name of the class, if it has one or one can be determined from the context.
|
|
*/
|
|
string getName() { result = super.getName() }
|
|
|
|
/**
|
|
* Gets a description of the class.
|
|
*/
|
|
string describe() { result = super.describe() }
|
|
|
|
/**
|
|
* Gets the constructor function of this class.
|
|
*/
|
|
FunctionNode getConstructor() { result = super.getConstructor() }
|
|
|
|
/**
|
|
* Gets an instance method declared in this class, with the given name, if any.
|
|
*
|
|
* Does not include methods from superclasses.
|
|
*/
|
|
FunctionNode getInstanceMethod(string name) {
|
|
result = super.getInstanceMember(name, MemberKind::method())
|
|
}
|
|
|
|
/**
|
|
* Gets an instance method declared in this class.
|
|
*
|
|
* The constructor is not considered an instance method.
|
|
*
|
|
* Does not include methods from superclasses.
|
|
*/
|
|
FunctionNode getAnInstanceMethod() { result = super.getAnInstanceMember(MemberKind::method()) }
|
|
|
|
/**
|
|
* Gets the instance method, getter, or setter with the given name and kind.
|
|
*
|
|
* Does not include members from superclasses.
|
|
*/
|
|
FunctionNode getInstanceMember(string name, MemberKind kind) {
|
|
result = super.getInstanceMember(name, kind)
|
|
}
|
|
|
|
/**
|
|
* Gets an instance method, getter, or setter with the given kind.
|
|
*
|
|
* Does not include members from superclasses.
|
|
*/
|
|
FunctionNode getAnInstanceMember(MemberKind kind) { result = super.getAnInstanceMember(kind) }
|
|
|
|
/**
|
|
* Gets an instance method, getter, or setter declared in this class.
|
|
*
|
|
* Does not include members from superclasses.
|
|
*/
|
|
FunctionNode getAnInstanceMember() { result = super.getAnInstanceMember(_) }
|
|
|
|
/**
|
|
* Gets the static method, getter, or setter declared in this class with the given name and kind.
|
|
*/
|
|
FunctionNode getStaticMember(string name, MemberKind kind) {
|
|
result = super.getStaticMember(name, kind)
|
|
}
|
|
|
|
/**
|
|
* Gets the static method declared in this class with the given name.
|
|
*/
|
|
FunctionNode getStaticMethod(string name) {
|
|
result = this.getStaticMember(name, MemberKind::method())
|
|
}
|
|
|
|
/**
|
|
* Gets a static method, getter, or setter declared in this class with the given kind.
|
|
*/
|
|
FunctionNode getAStaticMember(MemberKind kind) { result = super.getAStaticMember(kind) }
|
|
|
|
/**
|
|
* Gets a static method declared in this class.
|
|
*
|
|
* The constructor is not considered a static method.
|
|
*/
|
|
FunctionNode getAStaticMethod() { result = this.getAStaticMember(MemberKind::method()) }
|
|
|
|
/**
|
|
* Gets a dataflow node that refers to the superclass of this class.
|
|
*/
|
|
DataFlow::Node getASuperClassNode() { result = super.getASuperClassNode() }
|
|
|
|
/**
|
|
* Gets a direct super class of this class.
|
|
*
|
|
* This predicate can be overridden to customize the class hierarchy.
|
|
*/
|
|
ClassNode getADirectSuperClass() {
|
|
result.getAClassReference().flowsTo(this.getASuperClassNode())
|
|
}
|
|
|
|
/**
|
|
* Gets a direct subclass of this class.
|
|
*/
|
|
final ClassNode getADirectSubClass() { this = result.getADirectSuperClass() }
|
|
|
|
/**
|
|
* Gets the receiver of an instance member or constructor of this class.
|
|
*/
|
|
DataFlow::SourceNode getAReceiverNode() {
|
|
result = this.getConstructor().getReceiver()
|
|
or
|
|
result = this.getAnInstanceMember().getReceiver()
|
|
}
|
|
|
|
/**
|
|
* Gets the abstract value representing the class itself.
|
|
*/
|
|
AbstractValue getAbstractClassValue() { result = this.(AnalyzedNode).getAValue() }
|
|
|
|
/**
|
|
* Gets the abstract value representing an instance of this class.
|
|
*/
|
|
AbstractValue getAbstractInstanceValue() { result = AbstractInstance::of(this.getAstNode()) }
|
|
|
|
/**
|
|
* Gets a dataflow node that refers to this class object.
|
|
*
|
|
* This predicate can be overridden to customize the tracking of class objects.
|
|
*/
|
|
DataFlow::SourceNode getAClassReference(DataFlow::TypeTracker t) {
|
|
t.start() and
|
|
result.(AnalyzedNode).getAValue() = this.getAbstractClassValue() and
|
|
(
|
|
not CallGraph::isIndefiniteGlobal(result)
|
|
or
|
|
result.getAstNode().getFile() = this.getAstNode().getFile()
|
|
)
|
|
or
|
|
t.start() and
|
|
PreCallGraphStep::classObjectSource(this, result)
|
|
or
|
|
result = this.getAClassReferenceRec(t)
|
|
}
|
|
|
|
pragma[noopt]
|
|
private DataFlow::SourceNode getAClassReferenceRec(DataFlow::TypeTracker t) {
|
|
exists(DataFlow::TypeTracker t2, StepSummary summary, DataFlow::SourceNode prev |
|
|
prev = this.getAClassReference(t2) and
|
|
StepSummary::step(prev, result, summary) and
|
|
t = t2.append(summary)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a dataflow node that refers to this class object.
|
|
*/
|
|
cached
|
|
final DataFlow::SourceNode getAClassReference() {
|
|
result = this.getAClassReference(DataFlow::TypeTracker::end())
|
|
}
|
|
|
|
/**
|
|
* Gets a dataflow node that refers to an instance of this class.
|
|
*
|
|
* This predicate can be overridden to customize the tracking of class instances.
|
|
*/
|
|
DataFlow::SourceNode getAnInstanceReference(DataFlow::TypeTracker t) {
|
|
result = this.getAClassReference(t.continue()).getAnInstantiation()
|
|
or
|
|
t.start() and
|
|
result.(AnalyzedNode).getAValue() = this.getAbstractInstanceValue() and
|
|
not result = any(DataFlow::ClassNode cls).getAReceiverNode()
|
|
or
|
|
t.start() and
|
|
result = this.getAReceiverNode()
|
|
or
|
|
// Use a parameter type as starting point of type tracking.
|
|
// Use `t.call()` to emulate the value being passed in through an unseen
|
|
// call site, but not out of the call again.
|
|
t.call() and
|
|
exists(Parameter param |
|
|
this = param.getTypeAnnotation().getClass() and
|
|
result = DataFlow::parameterNode(param)
|
|
)
|
|
or
|
|
result = this.getAnInstanceReferenceAux(t) and
|
|
// Avoid tracking into the receiver of other classes.
|
|
// Note that this also blocks flows into a property of the receiver,
|
|
// but the `localFieldStep` rule will often compensate for this.
|
|
not result = any(DataFlow::ClassNode cls).getAReceiverNode()
|
|
or
|
|
t.start() and
|
|
PreCallGraphStep::classInstanceSource(this, result)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private DataFlow::SourceNode getAnInstanceReferenceAux(DataFlow::TypeTracker t) {
|
|
exists(DataFlow::TypeTracker t2 | result = this.getAnInstanceReference(t2).track(t2, t))
|
|
}
|
|
|
|
/**
|
|
* Gets a dataflow node that refers to an instance of this class.
|
|
*/
|
|
cached
|
|
final DataFlow::SourceNode getAnInstanceReference() {
|
|
result = this.getAnInstanceReference(DataFlow::TypeTracker::end())
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private DataFlow::PropRead getAnOwnInstanceMemberAccess(string name, DataFlow::TypeTracker t) {
|
|
result = this.getAnInstanceReference(t.continue()).getAPropertyRead(name)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private DataFlow::PropRead getAnInstanceMemberAccessOnSubClass(
|
|
string name, DataFlow::TypeTracker t
|
|
) {
|
|
exists(DataFlow::ClassNode subclass |
|
|
subclass = this.getADirectSubClass() and
|
|
not exists(subclass.getInstanceMember(name, _))
|
|
|
|
|
result = subclass.getAnOwnInstanceMemberAccess(name, t)
|
|
or
|
|
result = subclass.getAnInstanceMemberAccessOnSubClass(name, t)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private DataFlow::PropRead getAnInstanceMemberAccessOnSuperClass(string name) {
|
|
result = this.getADirectSuperClass().getAReceiverNode().getAPropertyRead(name)
|
|
or
|
|
result = this.getADirectSuperClass().getAnInstanceMemberAccessOnSuperClass(name)
|
|
}
|
|
|
|
/**
|
|
* Gets a property read that accesses the property `name` on an instance of this class.
|
|
*
|
|
* This includes accesses on subclasses (if the member is not overridden) and accesses in a base class
|
|
* (only if accessed on `this`).
|
|
*/
|
|
pragma[nomagic]
|
|
DataFlow::PropRead getAnInstanceMemberAccess(string name, DataFlow::TypeTracker t) {
|
|
result = this.getAnOwnInstanceMemberAccess(name, t)
|
|
or
|
|
result = this.getAnInstanceMemberAccessOnSubClass(name, t)
|
|
or
|
|
t.start() and
|
|
result = this.getAnInstanceMemberAccessOnSuperClass(name)
|
|
}
|
|
|
|
/**
|
|
* Gets a property read that accesses the property `name` on an instance of this class.
|
|
*
|
|
* Concretely, this holds when the base is an instance of this class or a subclass thereof.
|
|
*/
|
|
DataFlow::PropRead getAnInstanceMemberAccess(string name) {
|
|
result = this.getAnInstanceMemberAccess(name, DataFlow::TypeTracker::end())
|
|
}
|
|
|
|
/**
|
|
* Gets an access to a static member of this class.
|
|
*/
|
|
DataFlow::PropRead getAStaticMemberAccess(string name) {
|
|
result = this.getAClassReference().getAPropertyRead(name)
|
|
}
|
|
|
|
/**
|
|
* Holds if this class is exposed in the global scope through the given qualified name.
|
|
*/
|
|
pragma[noinline]
|
|
predicate hasQualifiedName(string name) {
|
|
this.getAClassReference().flowsTo(AccessPath::getAnAssignmentTo(name))
|
|
}
|
|
|
|
/**
|
|
* Gets the type annotation for the field `fieldName`, if any.
|
|
*/
|
|
TypeAnnotation getFieldTypeAnnotation(string fieldName) {
|
|
result = super.getFieldTypeAnnotation(fieldName)
|
|
}
|
|
|
|
/**
|
|
* Gets a decorator applied to this class.
|
|
*/
|
|
DataFlow::Node getADecorator() { result = super.getADecorator() }
|
|
}
|
|
|
|
module ClassNode {
|
|
/**
|
|
* A dataflow node that should be considered a class node.
|
|
*
|
|
* Subclass this to introduce new kinds of class nodes. If you want to refine
|
|
* the definition of existing class nodes, subclass `DataFlow::ClassNode` instead.
|
|
*/
|
|
cached
|
|
abstract class Range extends DataFlow::SourceNode {
|
|
/**
|
|
* Gets the name of the class, if it has one.
|
|
*/
|
|
cached
|
|
abstract string getName();
|
|
|
|
/**
|
|
* Gets a description of the class.
|
|
*/
|
|
cached
|
|
abstract string describe();
|
|
|
|
/**
|
|
* Gets the constructor function of this class.
|
|
*/
|
|
cached
|
|
abstract FunctionNode getConstructor();
|
|
|
|
/**
|
|
* Gets the instance member with the given name and kind.
|
|
*/
|
|
cached
|
|
abstract FunctionNode getInstanceMember(string name, MemberKind kind);
|
|
|
|
/**
|
|
* Gets an instance member with the given kind.
|
|
*/
|
|
cached
|
|
abstract FunctionNode getAnInstanceMember(MemberKind kind);
|
|
|
|
/**
|
|
* Gets the static member of this class with the given name and kind.
|
|
*/
|
|
cached
|
|
abstract FunctionNode getStaticMember(string name, MemberKind kind);
|
|
|
|
/**
|
|
* Gets a static member of this class of the given kind.
|
|
*/
|
|
cached
|
|
abstract FunctionNode getAStaticMember(MemberKind kind);
|
|
|
|
/**
|
|
* Gets a dataflow node representing a class to be used as the super-class
|
|
* of this node.
|
|
*/
|
|
cached
|
|
abstract DataFlow::Node getASuperClassNode();
|
|
|
|
/**
|
|
* Gets the type annotation for the field `fieldName`, if any.
|
|
*/
|
|
cached
|
|
TypeAnnotation getFieldTypeAnnotation(string fieldName) { none() }
|
|
|
|
/** Gets a decorator applied to this class. */
|
|
cached
|
|
DataFlow::Node getADecorator() { none() }
|
|
}
|
|
|
|
/**
|
|
* An ES6 class as a `ClassNode` instance.
|
|
*/
|
|
private class ES6Class extends Range, DataFlow::ValueNode {
|
|
override ClassDefinition astNode;
|
|
|
|
override string getName() { result = astNode.getName() }
|
|
|
|
override string describe() { result = astNode.describe() }
|
|
|
|
override FunctionNode getConstructor() { result = astNode.getConstructor().getBody().flow() }
|
|
|
|
override FunctionNode getInstanceMember(string name, MemberKind kind) {
|
|
exists(MethodDeclaration method |
|
|
method = astNode.getMethod(name) and
|
|
not method.isStatic() and
|
|
kind = MemberKind::of(method) and
|
|
result = method.getBody().flow()
|
|
)
|
|
or
|
|
kind = MemberKind::method() and
|
|
result = this.getConstructor().getReceiver().getAPropertySource(name)
|
|
}
|
|
|
|
override FunctionNode getAnInstanceMember(MemberKind kind) {
|
|
exists(MethodDeclaration method |
|
|
method = astNode.getAMethod() and
|
|
not method.isStatic() and
|
|
kind = MemberKind::of(method) and
|
|
result = method.getBody().flow()
|
|
)
|
|
or
|
|
kind = MemberKind::method() and
|
|
result = this.getConstructor().getReceiver().getAPropertySource()
|
|
}
|
|
|
|
override FunctionNode getStaticMember(string name, MemberKind kind) {
|
|
exists(MethodDeclaration method |
|
|
method = astNode.getMethod(name) and
|
|
method.isStatic() and
|
|
kind = MemberKind::of(method) and
|
|
result = method.getBody().flow()
|
|
)
|
|
or
|
|
kind.isMethod() and
|
|
result = this.getAPropertySource(name)
|
|
}
|
|
|
|
override FunctionNode getAStaticMember(MemberKind kind) {
|
|
exists(MethodDeclaration method |
|
|
method = astNode.getAMethod() and
|
|
method.isStatic() and
|
|
kind = MemberKind::of(method) and
|
|
result = method.getBody().flow()
|
|
)
|
|
or
|
|
kind.isMethod() and
|
|
result = this.getAPropertySource()
|
|
}
|
|
|
|
override DataFlow::Node getASuperClassNode() { result = astNode.getSuperClass().flow() }
|
|
|
|
override TypeAnnotation getFieldTypeAnnotation(string fieldName) {
|
|
exists(FieldDeclaration field |
|
|
field.getDeclaringClass() = astNode and
|
|
fieldName = field.getName() and
|
|
result = field.getTypeAnnotation()
|
|
)
|
|
}
|
|
|
|
override DataFlow::Node getADecorator() {
|
|
result = astNode.getADecorator().getExpression().flow()
|
|
}
|
|
}
|
|
|
|
private DataFlow::PropRef getAPrototypeReferenceInFile(string name, File f) {
|
|
result.getBase() = AccessPath::getAReferenceOrAssignmentTo(name) and
|
|
result.getPropertyName() = "prototype" and
|
|
result.getFile() = f
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private DataFlow::NewNode getAnInstantiationInFile(string name, File f) {
|
|
result = AccessPath::getAReferenceTo(name).(DataFlow::LocalSourceNode).getAnInstantiation() and
|
|
result.getFile() = f
|
|
}
|
|
|
|
/**
|
|
* Gets a reference to the function `func`, where there exists a read/write of the "prototype" property on that reference.
|
|
*/
|
|
pragma[noinline]
|
|
private DataFlow::SourceNode getAFunctionValueWithPrototype(AbstractValue func) {
|
|
exists(result.getAPropertyReference("prototype")) and
|
|
result.analyze().getAValue() = pragma[only_bind_into](func) and
|
|
func instanceof AbstractFunction // the join-order goes bad if `func` has type `AbstractFunction`.
|
|
}
|
|
|
|
/**
|
|
* A function definition, targeted by a `new`-call or with prototype manipulation, seen as a `ClassNode` instance.
|
|
*/
|
|
class FunctionStyleClass extends Range, DataFlow::ValueNode {
|
|
override Function astNode;
|
|
AbstractFunction function;
|
|
|
|
FunctionStyleClass() {
|
|
function.getFunction() = astNode and
|
|
(
|
|
exists(getAFunctionValueWithPrototype(function))
|
|
or
|
|
function = any(NewNode new).getCalleeNode().analyze().getAValue()
|
|
or
|
|
exists(string name | this = AccessPath::getAnAssignmentTo(name) |
|
|
exists(getAPrototypeReferenceInFile(name, this.getFile()))
|
|
or
|
|
exists(getAnInstantiationInFile(name, this.getFile()))
|
|
)
|
|
)
|
|
}
|
|
|
|
override string getName() { result = astNode.getName() }
|
|
|
|
override string describe() { result = astNode.describe() }
|
|
|
|
override FunctionNode getConstructor() { result = this }
|
|
|
|
private PropertyAccessor getAnAccessor(MemberKind kind) {
|
|
result.getObjectExpr() = this.getAPrototypeReference().asExpr() and
|
|
(
|
|
kind = MemberKind::getter() and
|
|
result instanceof PropertyGetter
|
|
or
|
|
kind = MemberKind::setter() and
|
|
result instanceof PropertySetter
|
|
)
|
|
}
|
|
|
|
override FunctionNode getInstanceMember(string name, MemberKind kind) {
|
|
kind = MemberKind::method() and
|
|
result = this.getAPrototypeReference().getAPropertySource(name)
|
|
or
|
|
kind = MemberKind::method() and
|
|
result = this.getConstructor().getReceiver().getAPropertySource(name)
|
|
or
|
|
exists(PropertyAccessor accessor |
|
|
accessor = this.getAnAccessor(kind) and
|
|
accessor.getName() = name and
|
|
result = accessor.getInit().flow()
|
|
)
|
|
}
|
|
|
|
override FunctionNode getAnInstanceMember(MemberKind kind) {
|
|
kind = MemberKind::method() and
|
|
result = this.getAPrototypeReference().getAPropertySource()
|
|
or
|
|
kind = MemberKind::method() and
|
|
result = this.getConstructor().getReceiver().getAPropertySource()
|
|
or
|
|
exists(PropertyAccessor accessor |
|
|
accessor = this.getAnAccessor(kind) and
|
|
result = accessor.getInit().flow()
|
|
)
|
|
}
|
|
|
|
override FunctionNode getStaticMember(string name, MemberKind kind) {
|
|
kind.isMethod() and
|
|
result = this.getAPropertySource(name)
|
|
}
|
|
|
|
override FunctionNode getAStaticMember(MemberKind kind) {
|
|
kind.isMethod() and
|
|
result = this.getAPropertySource()
|
|
}
|
|
|
|
/**
|
|
* Gets a reference to the prototype of this class.
|
|
*/
|
|
DataFlow::SourceNode getAPrototypeReference() {
|
|
exists(DataFlow::SourceNode base | base = getAFunctionValueWithPrototype(function) |
|
|
result = base.getAPropertyRead("prototype")
|
|
or
|
|
result = base.getAPropertySource("prototype")
|
|
)
|
|
or
|
|
exists(string name |
|
|
this = AccessPath::getAnAssignmentTo(name) and
|
|
result = getAPrototypeReferenceInFile(name, this.getFile())
|
|
)
|
|
or
|
|
exists(ExtendCall call |
|
|
call.getDestinationOperand() = this.getAPrototypeReference() and
|
|
result = call.getASourceOperand()
|
|
)
|
|
}
|
|
|
|
override DataFlow::Node getASuperClassNode() {
|
|
// C.prototype = Object.create(D.prototype)
|
|
exists(DataFlow::InvokeNode objectCreate, DataFlow::PropRead superProto |
|
|
this.getAPropertySource("prototype") = objectCreate and
|
|
objectCreate = DataFlow::globalVarRef("Object").getAMemberCall("create") and
|
|
superProto.flowsTo(objectCreate.getArgument(0)) and
|
|
superProto.getPropertyName() = "prototype" and
|
|
result = superProto.getBase()
|
|
)
|
|
or
|
|
// C.prototype = new D()
|
|
exists(DataFlow::NewNode newCall |
|
|
this.getAPropertySource("prototype") = newCall and
|
|
result = newCall.getCalleeNode()
|
|
)
|
|
or
|
|
// util.inherits(C, D);
|
|
exists(DataFlow::CallNode inheritsCall |
|
|
inheritsCall = DataFlow::moduleMember("util", "inherits").getACall()
|
|
|
|
|
this = inheritsCall.getArgument(0).getALocalSource() and
|
|
result = inheritsCall.getArgument(1)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A data flow node that performs a partial function application.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* fn.bind(this)
|
|
* x.method.bind(x)
|
|
* _.partial(fn, x, y, z)
|
|
* ```
|
|
*/
|
|
class PartialInvokeNode extends DataFlow::Node instanceof PartialInvokeNode::Range {
|
|
/** Gets a node holding a callback invoked by this partial invocation node. */
|
|
DataFlow::Node getACallbackNode() {
|
|
this.isPartialArgument(result, _, _)
|
|
or
|
|
exists(this.getBoundReceiver(result))
|
|
}
|
|
|
|
/**
|
|
* Holds if `argument` is passed as argument `index` to the function in `callback`.
|
|
*/
|
|
predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
|
super.isPartialArgument(callback, argument, index)
|
|
}
|
|
|
|
/**
|
|
* Gets a node referring to a bound version of `callback` with `boundArgs` arguments bound.
|
|
*/
|
|
DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
|
result = super.getBoundFunction(callback, boundArgs)
|
|
}
|
|
|
|
/**
|
|
* Gets the node holding the receiver to be passed to the bound function, if specified.
|
|
*/
|
|
DataFlow::Node getBoundReceiver() { result = super.getBoundReceiver(_) }
|
|
|
|
/**
|
|
* Gets the node holding the receiver to be passed to the bound function, if specified.
|
|
*/
|
|
DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
|
|
result = super.getBoundReceiver(callback)
|
|
}
|
|
}
|
|
|
|
module PartialInvokeNode {
|
|
/**
|
|
* A data flow node that performs a partial function application.
|
|
*/
|
|
abstract class Range extends DataFlow::Node {
|
|
/**
|
|
* Holds if `argument` is passed as argument `index` to the function in `callback`.
|
|
*/
|
|
predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
|
none()
|
|
}
|
|
|
|
/**
|
|
* Gets a node referring to a bound version of `callback` with `boundArgs` arguments bound.
|
|
*/
|
|
DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) { none() }
|
|
|
|
/**
|
|
* Gets the node holding the receiver to be passed to `callback`.
|
|
*/
|
|
DataFlow::Node getBoundReceiver(DataFlow::Node callback) { none() }
|
|
}
|
|
|
|
/**
|
|
* A partial call through the built-in `Function.prototype.bind`.
|
|
*/
|
|
private class BindPartialCall extends PartialInvokeNode::Range, DataFlow::MethodCallNode {
|
|
BindPartialCall() {
|
|
this.getMethodName() = "bind" and
|
|
// Avoid overlap with angular.bind and goog.bind
|
|
not this = AngularJS::angular().getAMethodCall() and
|
|
not this.getReceiver().accessesGlobal("goog")
|
|
}
|
|
|
|
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
|
index >= 0 and
|
|
callback = this.getReceiver() and
|
|
argument = this.getArgument(index + 1)
|
|
}
|
|
|
|
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
|
callback = this.getReceiver() and
|
|
boundArgs = this.getNumArgument() - 1 and
|
|
result = this
|
|
}
|
|
|
|
override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
|
|
callback = this.getReceiver() and
|
|
result = this.getArgument(0)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A partial call through `_.partial`.
|
|
*/
|
|
private class LodashPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode {
|
|
LodashPartialCall() { this = LodashUnderscore::member("partial").getACall() }
|
|
|
|
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
|
index >= 0 and
|
|
callback = this.getArgument(0) and
|
|
argument = this.getArgument(index + 1)
|
|
}
|
|
|
|
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
|
callback = this.getArgument(0) and
|
|
boundArgs = this.getNumArgument() - 1 and
|
|
result = this
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A partial call that behaves like a throttle call, like `require("call-limit")(fs, limit)` or `_.memoize`.
|
|
* Seen as a partial invocation that binds no arguments.
|
|
*/
|
|
private class ThrottleLikePartialCall extends PartialInvokeNode::Range, DataFlow::CallNode {
|
|
int callbackIndex;
|
|
|
|
ThrottleLikePartialCall() {
|
|
callbackIndex = 0 and
|
|
(
|
|
this = LodashUnderscore::member(["throttle", "debounce", "once", "memoize"]).getACall()
|
|
or
|
|
this = DataFlow::moduleImport(["call-limit", "debounce"]).getACall()
|
|
)
|
|
or
|
|
callbackIndex = 1 and
|
|
(
|
|
this = LodashUnderscore::member(["after", "before"]).getACall()
|
|
or
|
|
// not jQuery: https://github.com/cowboy/jquery-throttle-debounce
|
|
this = DataFlow::globalVarRef("$").getAMemberCall(["throttle", "debounce"])
|
|
)
|
|
or
|
|
callbackIndex = -1 and
|
|
this = DataFlow::moduleMember("throttle-debounce", ["debounce", "throttle"]).getACall()
|
|
}
|
|
|
|
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
|
(
|
|
callbackIndex >= 0 and
|
|
callback = this.getArgument(callbackIndex)
|
|
or
|
|
callbackIndex = -1 and
|
|
callback = this.getLastArgument()
|
|
) and
|
|
boundArgs = 0 and
|
|
result = this
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A partial call through `ramda.partial`.
|
|
*/
|
|
private class RamdaPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode {
|
|
RamdaPartialCall() { this = DataFlow::moduleMember("ramda", "partial").getACall() }
|
|
|
|
private DataFlow::ArrayCreationNode getArgumentsArray() { result.flowsTo(this.getArgument(1)) }
|
|
|
|
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
|
|
callback = this.getArgument(0) and
|
|
argument = this.getArgumentsArray().getElement(index)
|
|
}
|
|
|
|
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
|
|
callback = this.getArgument(0) and
|
|
boundArgs = this.getArgumentsArray().getSize() and
|
|
result = this
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to `for-in` or `for-own`, passing the context parameter to the target function.
|
|
*/
|
|
class ForOwnInPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode {
|
|
ForOwnInPartialCall() {
|
|
exists(string name | name = "for-in" or name = "for-own" |
|
|
this = moduleImport(name).getACall()
|
|
)
|
|
}
|
|
|
|
override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
|
|
callback = this.getArgument(1) and
|
|
result = this.getArgument(2)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An invocation of the `RegExp` constructor.
|
|
*
|
|
* Example:
|
|
* ```js
|
|
* new RegExp("#[a-z]+", "g");
|
|
* RegExp("^\w*$");
|
|
* ```
|
|
*/
|
|
class RegExpConstructorInvokeNode extends DataFlow::InvokeNode {
|
|
RegExpConstructorInvokeNode() { this = DataFlow::globalVarRef("RegExp").getAnInvocation() }
|
|
|
|
/**
|
|
* Gets the AST of the regular expression created here, provided that the
|
|
* first argument is a string literal.
|
|
*/
|
|
RegExpTerm getRoot() {
|
|
result = this.getArgument(0).asExpr().(StringLiteral).asRegExp()
|
|
or
|
|
// In case someone writes `new RegExp(/foo/)` for some reason
|
|
result = this.getArgument(0).asExpr().(RegExpLiteral).getRoot()
|
|
}
|
|
|
|
/**
|
|
* Gets the flags provided in the second argument, or an empty string if no
|
|
* flags are provided.
|
|
*
|
|
* Has no result if the flags are provided but are not constant.
|
|
*/
|
|
string getFlags() {
|
|
result = this.getArgument(1).getStringValue()
|
|
or
|
|
not exists(this.getArgument(1)) and
|
|
result = ""
|
|
}
|
|
|
|
/**
|
|
* Gets the flags provided in the second argument, or an empty string if no
|
|
* flags are provided, or the string `"?"` if the provided flags are not known.
|
|
*/
|
|
string tryGetFlags() {
|
|
result = this.getFlags()
|
|
or
|
|
not exists(this.getFlags()) and
|
|
result = RegExp::unknownFlag()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A data flow node corresponding to a regular expression literal or
|
|
* an invocation of the `RegExp` constructor.
|
|
*
|
|
* Examples:
|
|
* ```js
|
|
* new RegExp("#[a-z]+", "g");
|
|
* RegExp("^\w*$");
|
|
* /[a-z]+/i
|
|
* ```
|
|
*/
|
|
class RegExpCreationNode extends DataFlow::SourceNode {
|
|
RegExpCreationNode() {
|
|
this instanceof RegExpConstructorInvokeNode or
|
|
this instanceof RegExpLiteralNode
|
|
}
|
|
|
|
/**
|
|
* Gets the root term of the created regular expression, if it is known.
|
|
*
|
|
* Has no result for calls to `RegExp` with a non-constant argument.
|
|
*/
|
|
RegExpTerm getRoot() {
|
|
result = this.(RegExpConstructorInvokeNode).getRoot() or
|
|
result = this.(RegExpLiteralNode).getRoot()
|
|
}
|
|
|
|
/**
|
|
* Gets the provided regular expression flags, if they are known.
|
|
*/
|
|
string getFlags() {
|
|
result = this.(RegExpConstructorInvokeNode).getFlags() or
|
|
result = this.(RegExpLiteralNode).getFlags()
|
|
}
|
|
|
|
/**
|
|
* Gets the flags provided in the second argument, or an empty string if no
|
|
* flags are provided, or the string `"?"` if the provided flags are not known.
|
|
*/
|
|
string tryGetFlags() {
|
|
result = this.(RegExpConstructorInvokeNode).tryGetFlags() or
|
|
result = this.(RegExpLiteralNode).getFlags()
|
|
}
|
|
|
|
/** Holds if the constructed predicate has the `g` flag. */
|
|
predicate isGlobal() { RegExp::isGlobal(this.getFlags()) }
|
|
|
|
/** Holds if the constructed predicate has the `g` flag or unknown flags. */
|
|
predicate maybeGlobal() { RegExp::maybeGlobal(this.tryGetFlags()) }
|
|
|
|
/** Gets a data flow node referring to this regular expression. */
|
|
private DataFlow::SourceNode getAReference(DataFlow::TypeTracker t) {
|
|
t.start() and
|
|
result = this
|
|
or
|
|
exists(DataFlow::TypeTracker t2 | result = this.getAReference(t2).track(t2, t))
|
|
}
|
|
|
|
/** Gets a data flow node referring to this regular expression. */
|
|
cached
|
|
DataFlow::SourceNode getAReference() {
|
|
Stages::FlowSteps::ref() and
|
|
result = this.getAReference(DataFlow::TypeTracker::end())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A guard node for a variable in a negative condition, such as `x` in `if(!x)`.
|
|
* Can be added to a `isBarrier` in a data-flow configuration to block flow through such checks.
|
|
*/
|
|
class VarAccessBarrier extends DataFlow::Node {
|
|
VarAccessBarrier() {
|
|
exists(ConditionGuardNode guard, SsaRefinementNode refinement |
|
|
this = DataFlow::ssaDefinitionNode(refinement) and
|
|
refinement.getGuard() = guard and
|
|
guard.getTest() instanceof VarAccess and
|
|
guard.getOutcome() = false
|
|
)
|
|
}
|
|
}
|