mirror of
https://github.com/github/codeql.git
synced 2026-02-12 13:11:20 +01:00
1732 lines
58 KiB
Plaintext
1732 lines
58 KiB
Plaintext
overlay[local?]
|
|
module;
|
|
|
|
private import javascript
|
|
private import semmle.javascript.dataflow.internal.CallGraphs
|
|
private import semmle.javascript.dataflow.internal.DataFlowNode
|
|
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
|
private import semmle.javascript.dataflow.internal.AdditionalFlowInternal
|
|
private import semmle.javascript.dataflow.internal.Contents::Private
|
|
private import semmle.javascript.dataflow.internal.VariableCapture
|
|
private import semmle.javascript.dataflow.internal.VariableOrThis
|
|
private import semmle.javascript.dataflow.internal.sharedlib.DataFlowImplCommon as DataFlowImplCommon
|
|
private import semmle.javascript.dataflow.internal.sharedlib.Ssa as Ssa2
|
|
private import semmle.javascript.internal.flow_summaries.AllFlowSummaries
|
|
private import sharedlib.FlowSummaryImpl as FlowSummaryImpl
|
|
private import semmle.javascript.dataflow.internal.FlowSummaryPrivate as FlowSummaryPrivate
|
|
private import semmle.javascript.dataflow.FlowSummary as FlowSummary
|
|
private import semmle.javascript.dataflow.internal.BarrierGuards
|
|
|
|
class DataFlowSecondLevelScope = Unit;
|
|
|
|
private class Node = DataFlow::Node;
|
|
|
|
class PostUpdateNode = DataFlow::PostUpdateNode;
|
|
|
|
class SsaUseNode extends DataFlow::Node, TSsaUseNode {
|
|
private ControlFlowNode expr;
|
|
|
|
SsaUseNode() { this = TSsaUseNode(expr) }
|
|
|
|
cached
|
|
override string toString() { result = "[ssa-use] " + expr.toString() }
|
|
|
|
cached
|
|
override StmtContainer getContainer() { result = expr.getContainer() }
|
|
|
|
cached
|
|
override Location getLocation() { result = expr.getLocation() }
|
|
}
|
|
|
|
class SsaSynthReadNode extends DataFlow::Node, TSsaSynthReadNode {
|
|
private Ssa2::SsaSynthReadNode read;
|
|
|
|
SsaSynthReadNode() { this = TSsaSynthReadNode(read) }
|
|
|
|
cached
|
|
override string toString() { result = "[ssa-synth-read] " + read.getSourceVariable().getName() }
|
|
|
|
cached
|
|
override StmtContainer getContainer() {
|
|
result = read.getSourceVariable().getDeclaringContainer()
|
|
}
|
|
|
|
cached
|
|
override Location getLocation() { result = read.getLocation() }
|
|
}
|
|
|
|
class FlowSummaryNode extends DataFlow::Node, TFlowSummaryNode {
|
|
FlowSummaryImpl::Private::SummaryNode getSummaryNode() { this = TFlowSummaryNode(result) }
|
|
|
|
/** Gets the summarized callable that this node belongs to. */
|
|
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() {
|
|
result = this.getSummaryNode().getSummarizedCallable()
|
|
}
|
|
|
|
cached
|
|
override string toString() { result = this.getSummaryNode().toString() }
|
|
}
|
|
|
|
class FlowSummaryDynamicParameterArrayNode extends DataFlow::Node,
|
|
TFlowSummaryDynamicParameterArrayNode
|
|
{
|
|
private FlowSummaryImpl::Public::SummarizedCallable callable;
|
|
|
|
FlowSummaryDynamicParameterArrayNode() { this = TFlowSummaryDynamicParameterArrayNode(callable) }
|
|
|
|
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() { result = callable }
|
|
|
|
cached
|
|
override string toString() { result = "[dynamic parameter array] " + callable }
|
|
}
|
|
|
|
class FlowSummaryIntermediateAwaitStoreNode extends DataFlow::Node,
|
|
TFlowSummaryIntermediateAwaitStoreNode
|
|
{
|
|
FlowSummaryImpl::Private::SummaryNode getSummaryNode() {
|
|
this = TFlowSummaryIntermediateAwaitStoreNode(result)
|
|
}
|
|
|
|
/** Gets the summarized callable that this node belongs to. */
|
|
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() {
|
|
result = this.getSummaryNode().getSummarizedCallable()
|
|
}
|
|
|
|
override string toString() {
|
|
result = this.getSummaryNode().toString() + " [intermediate node for Awaited store]"
|
|
}
|
|
}
|
|
|
|
predicate mentionsExceptionalReturn(FlowSummaryImpl::Public::SummarizedCallable callable) {
|
|
exists(FlowSummaryImpl::Private::SummaryNode node | node.getSummarizedCallable() = callable |
|
|
FlowSummaryImpl::Private::summaryReturnNode(node, MkExceptionalReturnKind())
|
|
or
|
|
FlowSummaryImpl::Private::summaryOutNode(_, node, MkExceptionalReturnKind())
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Exceptional return node in a summarized callable whose summary does not mention `ReturnValue[exception]`.
|
|
*
|
|
* By default, every call inside such a callable will forward their exceptional return to the caller's
|
|
* exceptional return, i.e. exceptions are not caught.
|
|
*/
|
|
class FlowSummaryDefaultExceptionalReturn extends DataFlow::Node,
|
|
TFlowSummaryDefaultExceptionalReturn
|
|
{
|
|
private FlowSummaryImpl::Public::SummarizedCallable callable;
|
|
|
|
FlowSummaryDefaultExceptionalReturn() { this = TFlowSummaryDefaultExceptionalReturn(callable) }
|
|
|
|
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() { result = callable }
|
|
|
|
cached
|
|
override string toString() { result = "[default exceptional return] " + callable }
|
|
}
|
|
|
|
class CaptureNode extends DataFlow::Node, TSynthCaptureNode {
|
|
/** Gets the underlying node from the variable-capture library. */
|
|
VariableCaptureOutput::SynthesizedCaptureNode getNode() {
|
|
this = TSynthCaptureNode(result) and DataFlowImplCommon::forceCachingInSameStage()
|
|
}
|
|
|
|
cached
|
|
override StmtContainer getContainer() { result = this.getNode().getEnclosingCallable() }
|
|
|
|
cached
|
|
private string toStringInternal() { result = this.getNode().toString() + " [capture node]" }
|
|
|
|
override string toString() { result = this.toStringInternal() } // cached in parent class
|
|
|
|
cached
|
|
override Location getLocation() { result = this.getNode().getLocation() }
|
|
}
|
|
|
|
class GenericSynthesizedNode extends DataFlow::Node, TGenericSynthesizedNode {
|
|
private AstNode node;
|
|
private string tag;
|
|
private DataFlowCallable container;
|
|
|
|
GenericSynthesizedNode() { this = TGenericSynthesizedNode(node, tag, container) }
|
|
|
|
override StmtContainer getContainer() { result = container.asSourceCallable() }
|
|
|
|
override string toString() { result = "[synthetic node] " + tag }
|
|
|
|
override Location getLocation() { result = node.getLocation() }
|
|
|
|
string getTag() { result = tag }
|
|
}
|
|
|
|
/**
|
|
* An argument containing an array of all positional arguments with an obvious index, i.e. not affected by a spread argument.
|
|
*/
|
|
class StaticArgumentArrayNode extends DataFlow::Node, TStaticArgumentArrayNode {
|
|
private InvokeExpr invoke;
|
|
|
|
StaticArgumentArrayNode() { this = TStaticArgumentArrayNode(invoke) }
|
|
|
|
override StmtContainer getContainer() { result = invoke.getContainer() }
|
|
|
|
override string toString() { result = "[static argument array]" }
|
|
|
|
override Location getLocation() { result = invoke.getLocation() }
|
|
}
|
|
|
|
/**
|
|
* An argument containing an array of all positional arguments with non-obvious index, i.e. affected by a spread argument.
|
|
*
|
|
* Only exists for call sites with a spread argument.
|
|
*/
|
|
class DynamicArgumentArrayNode extends DataFlow::Node, TDynamicArgumentArrayNode {
|
|
private InvokeExpr invoke;
|
|
|
|
DynamicArgumentArrayNode() { this = TDynamicArgumentArrayNode(invoke) }
|
|
|
|
override StmtContainer getContainer() { result = invoke.getContainer() }
|
|
|
|
override string toString() { result = "[dynamic argument array]" }
|
|
|
|
override Location getLocation() { result = invoke.getLocation() }
|
|
}
|
|
|
|
/**
|
|
* Intermediate node with data that will be stored in `DyanmicArgumentArrayNode`.
|
|
*/
|
|
class DynamicArgumentStoreNode extends DataFlow::Node, TDynamicArgumentStoreNode {
|
|
private InvokeExpr invoke;
|
|
private Content content;
|
|
|
|
DynamicArgumentStoreNode() { this = TDynamicArgumentStoreNode(invoke, content) }
|
|
|
|
override StmtContainer getContainer() { result = invoke.getContainer() }
|
|
|
|
override string toString() { result = "[dynamic argument store node] content=" + content }
|
|
|
|
override Location getLocation() { result = invoke.getLocation() }
|
|
}
|
|
|
|
/**
|
|
* Intermediate node with data that will be stored in the function's rest parameter node.
|
|
*/
|
|
class RestParameterStoreNode extends DataFlow::Node, TRestParameterStoreNode {
|
|
private Function function;
|
|
private Content content;
|
|
|
|
RestParameterStoreNode() { this = TRestParameterStoreNode(function, content) }
|
|
|
|
override StmtContainer getContainer() { result = function }
|
|
|
|
override string toString() {
|
|
result =
|
|
"[rest parameter store node] '..." + function.getRestParameter().getName() + "' content=" +
|
|
content
|
|
}
|
|
|
|
override Location getLocation() { result = function.getRestParameter().getLocation() }
|
|
}
|
|
|
|
/**
|
|
* A parameter containing an array of all positional arguments with an obvious index, i.e. not affected by spread or `.apply()`.
|
|
*
|
|
* These are read and stored in the function's rest parameter and `arguments` array.
|
|
* The node only exists for functions with a rest parameter or which uses the `arguments` array.
|
|
*/
|
|
class StaticParameterArrayNode extends DataFlow::Node, TStaticParameterArrayNode {
|
|
private Function function;
|
|
|
|
StaticParameterArrayNode() { this = TStaticParameterArrayNode(function) }
|
|
|
|
override StmtContainer getContainer() { result = function }
|
|
|
|
override string toString() { result = "[static parameter array]" }
|
|
|
|
override Location getLocation() { result = function.getLocation() }
|
|
}
|
|
|
|
/**
|
|
* A parameter containing an array of all positional argument values with non-obvious index, i.e. affected by spread or `.apply()`.
|
|
*
|
|
* These are read and assigned into regular positional parameters and stored into rest parameters and the `arguments` array.
|
|
*/
|
|
class DynamicParameterArrayNode extends DataFlow::Node, TDynamicParameterArrayNode {
|
|
private Function function;
|
|
|
|
DynamicParameterArrayNode() { this = TDynamicParameterArrayNode(function) }
|
|
|
|
override StmtContainer getContainer() { result = function }
|
|
|
|
override string toString() { result = "[dynamic parameter array]" }
|
|
|
|
override Location getLocation() { result = function.getLocation() }
|
|
}
|
|
|
|
/**
|
|
* Node with taint input from the second argument of `.apply()` and with a store edge back into that same argument.
|
|
*
|
|
* This ensures that if `.apply()` is called with a tainted value (not inside a content) the taint is
|
|
* boxed in an `ArrayElement` content. This is necessary for the target function to propagate the taint.
|
|
*/
|
|
class ApplyCallTaintNode extends DataFlow::Node, TApplyCallTaintNode {
|
|
private MethodCallExpr apply;
|
|
|
|
ApplyCallTaintNode() { this = TApplyCallTaintNode(apply) }
|
|
|
|
override StmtContainer getContainer() { result = apply.getContainer() }
|
|
|
|
override string toString() { result = "[apply call taint node]" }
|
|
|
|
override Location getLocation() { result = apply.getArgument(1).getLocation() }
|
|
|
|
MethodCallExpr getMethodCallExpr() { result = apply }
|
|
|
|
DataFlow::Node getArrayNode() { result = apply.getArgument(1).flow() }
|
|
}
|
|
|
|
cached
|
|
newtype TReturnKind =
|
|
MkNormalReturnKind() or
|
|
MkExceptionalReturnKind()
|
|
|
|
class ReturnKind extends TReturnKind {
|
|
string toString() {
|
|
this = MkNormalReturnKind() and result = "return"
|
|
or
|
|
this = MkExceptionalReturnKind() and result = "exception"
|
|
}
|
|
}
|
|
|
|
private predicate returnNodeImpl(DataFlow::Node node, ReturnKind kind) {
|
|
node instanceof TFunctionReturnNode and kind = MkNormalReturnKind()
|
|
or
|
|
exists(Function fun |
|
|
node = TExceptionalFunctionReturnNode(fun) and
|
|
kind = MkExceptionalReturnKind() and
|
|
// For async/generators, the exception is caught and wrapped in the returned promise/iterator object.
|
|
// See the models for AsyncAwait and Generator.
|
|
not fun.isAsyncOrGenerator()
|
|
)
|
|
or
|
|
FlowSummaryImpl::Private::summaryReturnNode(node.(FlowSummaryNode).getSummaryNode(), kind)
|
|
or
|
|
node instanceof FlowSummaryDefaultExceptionalReturn and
|
|
kind = MkExceptionalReturnKind()
|
|
}
|
|
|
|
overlay[global]
|
|
private DataFlow::Node getAnOutNodeImpl(DataFlowCall call, ReturnKind kind) {
|
|
kind = MkNormalReturnKind() and result = call.asOrdinaryCall()
|
|
or
|
|
kind = MkExceptionalReturnKind() and result = call.asOrdinaryCall().getExceptionalReturn()
|
|
or
|
|
kind = MkNormalReturnKind() and result = call.asBoundCall(_)
|
|
or
|
|
kind = MkExceptionalReturnKind() and result = call.asBoundCall(_).getExceptionalReturn()
|
|
or
|
|
kind = MkNormalReturnKind() and result = call.asAccessorCall().(DataFlow::PropRead)
|
|
or
|
|
FlowSummaryImpl::Private::summaryOutNode(call.(SummaryCall).getReceiver(),
|
|
result.(FlowSummaryNode).getSummaryNode(), kind)
|
|
or
|
|
kind = MkExceptionalReturnKind() and
|
|
result.(FlowSummaryDefaultExceptionalReturn).getSummarizedCallable() =
|
|
call.(SummaryCall).getSummarizedCallable()
|
|
}
|
|
|
|
class ReturnNode extends DataFlow::Node {
|
|
ReturnNode() { returnNodeImpl(this, _) }
|
|
|
|
ReturnKind getKind() { returnNodeImpl(this, result) }
|
|
}
|
|
|
|
/** A node that receives an output from a call. */
|
|
overlay[global]
|
|
class OutNode extends DataFlow::Node {
|
|
OutNode() { this = getAnOutNodeImpl(_, _) }
|
|
}
|
|
|
|
overlay[global]
|
|
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { result = getAnOutNodeImpl(call, kind) }
|
|
|
|
cached
|
|
predicate postUpdatePair(Node pre, Node post) {
|
|
exists(AST::ValueNode expr |
|
|
pre = TValueNode(expr) and
|
|
post = TExprPostUpdateNode(expr)
|
|
)
|
|
or
|
|
exists(NewExpr expr |
|
|
pre = TNewCallThisArgument(expr) and
|
|
post = TValueNode(expr)
|
|
)
|
|
or
|
|
exists(ImplicitThisUse use |
|
|
pre = TImplicitThisUse(use, false) and
|
|
post = TImplicitThisUse(use, true)
|
|
)
|
|
or
|
|
FlowSummaryImpl::Private::summaryPostUpdateNode(post.(FlowSummaryNode).getSummaryNode(),
|
|
pre.(FlowSummaryNode).getSummaryNode())
|
|
or
|
|
VariableCaptureOutput::capturePostUpdateNode(getClosureNode(post), getClosureNode(pre))
|
|
}
|
|
|
|
class CastNode extends DataFlow::Node {
|
|
CastNode() { none() }
|
|
}
|
|
|
|
cached
|
|
newtype TDataFlowCallable =
|
|
MkSourceCallable(StmtContainer container) or
|
|
MkLibraryCallable(LibraryCallable callable) or
|
|
MkFileCallable(File file)
|
|
|
|
/**
|
|
* A callable entity. This is a wrapper around either a `StmtContainer`, `LibraryCallable`, or `File`.
|
|
*/
|
|
class DataFlowCallable extends TDataFlowCallable {
|
|
/** Gets a string representation of this callable. */
|
|
string toString() {
|
|
result = this.asSourceCallable().toString()
|
|
or
|
|
result = this.asLibraryCallable()
|
|
or
|
|
result = this.asFileCallable().toString()
|
|
}
|
|
|
|
/** Gets the location of this callable, if it is present in the source code. */
|
|
Location getLocation() {
|
|
result = this.asSourceCallable().getLocation() or result = this.asFileCallable().getLocation()
|
|
}
|
|
|
|
/** Gets the corresponding `StmtContainer` if this is a source callable. */
|
|
StmtContainer asSourceCallable() { this = MkSourceCallable(result) }
|
|
|
|
/** Gets the corresponding `File` if this is a file representing a callable. */
|
|
File asFileCallable() { this = MkFileCallable(result) }
|
|
|
|
/** Gets the corresponding `StmtContainer` if this is a source callable. */
|
|
pragma[nomagic]
|
|
StmtContainer asSourceCallableNotExterns() {
|
|
this = MkSourceCallable(result) and
|
|
not result.inExternsFile()
|
|
}
|
|
|
|
/** Gets the corresponding `LibraryCallable` if this is a library callable. */
|
|
LibraryCallable asLibraryCallable() { this = MkLibraryCallable(result) }
|
|
}
|
|
|
|
/** A callable defined in library code, identified by a unique string. */
|
|
abstract class LibraryCallable extends string {
|
|
bindingset[this]
|
|
LibraryCallable() { any() }
|
|
|
|
/** Gets a call to this library callable. */
|
|
overlay[global]
|
|
DataFlow::InvokeNode getACall() { none() }
|
|
|
|
/** Same as `getACall()` except this does not depend on the call graph or API graph. */
|
|
overlay[global]
|
|
DataFlow::InvokeNode getACallSimple() { none() }
|
|
}
|
|
|
|
/** Internal subclass of `LibraryCallable`, whose member predicates should not be visible on `SummarizedCallable`. */
|
|
abstract class LibraryCallableInternal extends LibraryCallable {
|
|
bindingset[this]
|
|
LibraryCallableInternal() { any() }
|
|
|
|
/**
|
|
* Gets a call to this library callable.
|
|
*
|
|
* Same as `getACall()` but is evaluated later and may depend negatively on `getACall()`.
|
|
*/
|
|
overlay[global]
|
|
DataFlow::InvokeNode getACallStage2() { none() }
|
|
}
|
|
|
|
private predicate isParameterNodeImpl(Node p, DataFlowCallable c, ParameterPosition pos) {
|
|
exists(Parameter parameter |
|
|
parameter = c.asSourceCallable().(Function).getParameter(pos.asPositional()) and
|
|
not parameter.isRestParameter() and
|
|
p = TValueNode(parameter)
|
|
)
|
|
or
|
|
pos.isThis() and p = TThisNode(c.asSourceCallable().(Function))
|
|
or
|
|
pos.isFunctionSelfReference() and p = TFunctionSelfReferenceNode(c.asSourceCallable())
|
|
or
|
|
pos.isStaticArgumentArray() and p = TStaticParameterArrayNode(c.asSourceCallable())
|
|
or
|
|
pos.isDynamicArgumentArray() and p = TDynamicParameterArrayNode(c.asSourceCallable())
|
|
or
|
|
exists(FlowSummaryNode summaryNode |
|
|
summaryNode = p and
|
|
FlowSummaryImpl::Private::summaryParameterNode(summaryNode.getSummaryNode(), pos) and
|
|
c.asLibraryCallable() = summaryNode.getSummarizedCallable()
|
|
)
|
|
or
|
|
exists(FlowSummaryImpl::Public::SummarizedCallable callable |
|
|
c.asLibraryCallable() = callable and
|
|
pos.isDynamicArgumentArray() and
|
|
p = TFlowSummaryDynamicParameterArrayNode(callable)
|
|
)
|
|
}
|
|
|
|
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
|
|
isParameterNodeImpl(p, c, pos)
|
|
}
|
|
|
|
overlay[global]
|
|
private predicate isArgumentNodeImpl(Node n, DataFlowCall call, ArgumentPosition pos) {
|
|
n = call.asOrdinaryCall().getArgument(pos.asPositional())
|
|
or
|
|
exists(InvokeExpr invoke |
|
|
call.asOrdinaryCall() = TReflectiveCallNode(invoke, "apply") and
|
|
pos.isDynamicArgumentArray() and
|
|
n = TValueNode(invoke.getArgument(1))
|
|
)
|
|
or
|
|
pos.isThis() and n = call.asOrdinaryCall().(DataFlow::CallNode).getReceiver()
|
|
or
|
|
exists(DataFlow::PartialInvokeNode invoke, DataFlow::Node callback |
|
|
call = MkPartialCall(invoke, callback) and
|
|
invoke.isPartialArgument(callback, n, pos.asPositional())
|
|
)
|
|
or
|
|
pos.isThis() and n = call.asPartialCall().getBoundReceiver()
|
|
or
|
|
exists(int boundArgs |
|
|
n = call.asBoundCall(boundArgs).getArgument(pos.asPositional() - boundArgs)
|
|
)
|
|
or
|
|
pos.isFunctionSelfReference() and n = call.asOrdinaryCall().getCalleeNode()
|
|
or
|
|
pos.isFunctionSelfReference() and n = call.asImpliedLambdaCall().flow()
|
|
or
|
|
exists(Function fun |
|
|
call.asImpliedLambdaCall() = fun and
|
|
CallGraph::impliedReceiverStep(n, TThisNode(fun)) and
|
|
sameContainerAsEnclosingContainer(n, fun) and
|
|
pos.isThis()
|
|
)
|
|
or
|
|
pos.isThis() and n = TNewCallThisArgument(call.asOrdinaryCall().asExpr())
|
|
or
|
|
pos.isThis() and
|
|
n = TImplicitThisUse(call.asOrdinaryCall().asExpr().(SuperCall).getCallee(), false)
|
|
or
|
|
// receiver of accessor call
|
|
pos.isThis() and n = call.asAccessorCall().getBase()
|
|
or
|
|
// argument to setter
|
|
pos.asPositional() = 0 and n = call.asAccessorCall().(DataFlow::PropWrite).getRhs()
|
|
or
|
|
FlowSummaryImpl::Private::summaryArgumentNode(call.(SummaryCall).getReceiver(),
|
|
n.(FlowSummaryNode).getSummaryNode(), pos)
|
|
or
|
|
exists(InvokeExpr invoke | call.asOrdinaryCall() = TValueNode(invoke) |
|
|
n = TStaticArgumentArrayNode(invoke) and
|
|
pos.isStaticArgumentArray()
|
|
or
|
|
n = TDynamicArgumentArrayNode(invoke) and
|
|
pos.isDynamicArgumentArray()
|
|
)
|
|
}
|
|
|
|
overlay[global]
|
|
predicate isArgumentNode(ArgumentNode n, DataFlowCall call, ArgumentPosition pos) {
|
|
isArgumentNodeImpl(n, call, pos)
|
|
}
|
|
|
|
DataFlowCallable nodeGetEnclosingCallable(Node node) {
|
|
result.asSourceCallable() = node.getContainer()
|
|
or
|
|
result.asLibraryCallable() = node.(FlowSummaryNode).getSummarizedCallable()
|
|
or
|
|
result.asLibraryCallable() = node.(FlowSummaryDynamicParameterArrayNode).getSummarizedCallable()
|
|
or
|
|
result.asLibraryCallable() = node.(FlowSummaryIntermediateAwaitStoreNode).getSummarizedCallable()
|
|
or
|
|
result.asLibraryCallable() = node.(FlowSummaryDefaultExceptionalReturn).getSummarizedCallable()
|
|
or
|
|
node = TGenericSynthesizedNode(_, _, result)
|
|
or
|
|
node instanceof DataFlow::HtmlAttributeNode and result.asFileCallable() = node.getFile()
|
|
or
|
|
node instanceof DataFlow::XmlAttributeNode and result.asFileCallable() = node.getFile()
|
|
}
|
|
|
|
overlay[global]
|
|
newtype TDataFlowType =
|
|
TFunctionType(Function f) or
|
|
TInstanceType(DataFlow::ClassNode cls) or
|
|
TAnyType()
|
|
|
|
overlay[global]
|
|
class DataFlowType extends TDataFlowType {
|
|
string toDebugString() {
|
|
result =
|
|
"TFunctionType(" + this.asFunction().toString() + ") at line " +
|
|
this.asFunction().getLocation().getStartLine()
|
|
or
|
|
result =
|
|
"TInstanceType(" + this.asInstanceOfClass().toString() + ") at line " +
|
|
this.asInstanceOfClass().getLocation().getStartLine()
|
|
or
|
|
this instanceof TAnyType and result = "TAnyType"
|
|
}
|
|
|
|
string toString() {
|
|
result = "" // Must be the empty string to prevent this from showing up in path explanations
|
|
}
|
|
|
|
Function asFunction() { this = TFunctionType(result) }
|
|
|
|
DataFlow::ClassNode asInstanceOfClass() { this = TInstanceType(result) }
|
|
}
|
|
|
|
/**
|
|
* Holds if `t1` is strictly stronger than `t2`.
|
|
*/
|
|
overlay[global]
|
|
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) {
|
|
// 't1' is a subclass of 't2'
|
|
t1.asInstanceOfClass() = t2.asInstanceOfClass().getADirectSubClass+()
|
|
or
|
|
// Ensure all types are stronger than 'any'
|
|
not t1 = TAnyType() and
|
|
t2 = TAnyType()
|
|
}
|
|
|
|
overlay[global]
|
|
private DataFlowType getPreciseType(Node node) {
|
|
exists(Function f |
|
|
(node = TValueNode(f) or node = TFunctionSelfReferenceNode(f)) and
|
|
result = TFunctionType(f)
|
|
)
|
|
or
|
|
result.asInstanceOfClass() =
|
|
unique(DataFlow::ClassNode cls | cls.getAnInstanceReference().getALocalUse() = node)
|
|
or
|
|
result = getPreciseType(node.getImmediatePredecessor())
|
|
or
|
|
result = getPreciseType(node.(PostUpdateNode).getPreUpdateNode())
|
|
}
|
|
|
|
overlay[global]
|
|
DataFlowType getNodeType(Node node) {
|
|
result = getPreciseType(node)
|
|
or
|
|
not exists(getPreciseType(node)) and
|
|
result = TAnyType()
|
|
}
|
|
|
|
predicate nodeIsHidden(Node node) {
|
|
// Skip phi, refinement, and capture nodes
|
|
node.(DataFlow::SsaDefinitionNode).getSsaVariable().getDefinition() instanceof
|
|
SsaImplicitDefinition
|
|
or
|
|
// Skip SSA definition of parameter as its location coincides with the parameter node
|
|
node = DataFlow::ssaDefinitionNode(Ssa::definition(any(SimpleParameter p)))
|
|
or
|
|
// Skip to the top of big left-leaning string concatenation trees.
|
|
node = any(AddExpr add).flow() and
|
|
node = any(AddExpr add).getAnOperand().flow()
|
|
or
|
|
// Skip the exceptional return on functions, as this highlights the entire function.
|
|
node = any(DataFlow::FunctionNode f).getExceptionalReturn()
|
|
or
|
|
// Skip the special return node for functions, as this highlights the entire function (and the returned expr is the previous node).
|
|
node = any(DataFlow::FunctionNode f).getReturnNode()
|
|
or
|
|
// Skip the synthetic 'this' node, as a ThisExpr will be the next node anyway
|
|
node = DataFlow::thisNode(_)
|
|
or
|
|
// Skip captured variable nodes as the successor will be a use of that variable anyway.
|
|
node = DataFlow::capturedVariableNode(_)
|
|
or
|
|
node instanceof DataFlow::FunctionSelfReferenceNode
|
|
or
|
|
node instanceof FlowSummaryNode
|
|
or
|
|
node instanceof FlowSummaryDynamicParameterArrayNode
|
|
or
|
|
node instanceof FlowSummaryIntermediateAwaitStoreNode
|
|
or
|
|
node instanceof FlowSummaryDefaultExceptionalReturn
|
|
or
|
|
node instanceof CaptureNode
|
|
or
|
|
// Hide function expressions, as capture-flow causes them to appear in unhelpful ways
|
|
// In the future we could hide PathNodes with a capture content as the head of its access path.
|
|
node.asExpr() instanceof Function
|
|
or
|
|
// Also hide post-update nodes for function expressions
|
|
node.(DataFlow::ExprPostUpdateNode).getExpr() instanceof Function
|
|
or
|
|
node instanceof GenericSynthesizedNode
|
|
or
|
|
node instanceof StaticArgumentArrayNode
|
|
or
|
|
node instanceof DynamicArgumentArrayNode
|
|
or
|
|
node instanceof DynamicArgumentStoreNode
|
|
or
|
|
node instanceof StaticParameterArrayNode
|
|
or
|
|
node instanceof DynamicParameterArrayNode
|
|
or
|
|
node instanceof RestParameterStoreNode
|
|
or
|
|
node instanceof SsaUseNode
|
|
or
|
|
node instanceof SsaSynthReadNode
|
|
}
|
|
|
|
predicate neverSkipInPathGraph(Node node) {
|
|
// Include the left-hand side of assignments
|
|
node = DataFlow::lvalueNode(_)
|
|
or
|
|
// Include the return-value expression
|
|
node.asExpr() = any(Function f).getAReturnedExpr()
|
|
or
|
|
// Include calls (which may have been modeled as steps)
|
|
node.asExpr() instanceof InvokeExpr
|
|
or
|
|
// Include references to a variable
|
|
node.asExpr() instanceof VarRef
|
|
}
|
|
|
|
overlay[global]
|
|
string ppReprType(DataFlowType t) { none() }
|
|
|
|
overlay[global]
|
|
pragma[inline]
|
|
private predicate compatibleTypesWithAny(DataFlowType t1, DataFlowType t2) {
|
|
t1 != TAnyType() and
|
|
t2 = TAnyType()
|
|
}
|
|
|
|
overlay[global]
|
|
pragma[nomagic]
|
|
private predicate compatibleTypes1(DataFlowType t1, DataFlowType t2) {
|
|
t1.asInstanceOfClass().getADirectSubClass+() = t2.asInstanceOfClass()
|
|
}
|
|
|
|
overlay[global]
|
|
pragma[inline]
|
|
predicate compatibleTypes(DataFlowType t1, DataFlowType t2) {
|
|
t1 = t2
|
|
or
|
|
compatibleTypesWithAny(t1, t2)
|
|
or
|
|
compatibleTypesWithAny(t2, t1)
|
|
or
|
|
compatibleTypes1(t1, t2)
|
|
or
|
|
compatibleTypes1(t2, t1)
|
|
}
|
|
|
|
predicate forceHighPrecision(Content c) { none() }
|
|
|
|
newtype TContentApprox =
|
|
TApproxPropertyContent() or
|
|
TApproxMapKey() or
|
|
TApproxMapValue() or
|
|
TApproxSetElement() or
|
|
TApproxIteratorElement() or
|
|
TApproxIteratorError() or
|
|
TApproxPromiseValue() or
|
|
TApproxPromiseError() or
|
|
TApproxCapturedContent()
|
|
|
|
class ContentApprox extends TContentApprox {
|
|
string toString() {
|
|
this = TApproxPropertyContent() and result = "TApproxPropertyContent"
|
|
or
|
|
this = TApproxMapKey() and result = "TApproxMapKey"
|
|
or
|
|
this = TApproxMapValue() and result = "TApproxMapValue"
|
|
or
|
|
this = TApproxSetElement() and result = "TApproxSetElement"
|
|
or
|
|
this = TApproxIteratorElement() and result = "TApproxIteratorElement"
|
|
or
|
|
this = TApproxIteratorError() and result = "TApproxIteratorError"
|
|
or
|
|
this = TApproxPromiseValue() and result = "TApproxPromiseValue"
|
|
or
|
|
this = TApproxPromiseError() and result = "TApproxPromiseError"
|
|
or
|
|
this = TApproxCapturedContent() and result = "TApproxCapturedContent"
|
|
}
|
|
}
|
|
|
|
overlay[global]
|
|
pragma[inline]
|
|
ContentApprox getContentApprox(Content c) {
|
|
c instanceof MkPropertyContent and result = TApproxPropertyContent()
|
|
or
|
|
c instanceof MkArrayElementUnknown and result = TApproxPropertyContent()
|
|
or
|
|
c instanceof MkMapKey and result = TApproxMapKey()
|
|
or
|
|
c instanceof MkMapValueWithKnownKey and result = TApproxMapValue()
|
|
or
|
|
c instanceof MkMapValueWithUnknownKey and result = TApproxMapValue()
|
|
or
|
|
c instanceof MkSetElement and result = TApproxSetElement()
|
|
or
|
|
c instanceof MkIteratorElement and result = TApproxIteratorElement()
|
|
or
|
|
c instanceof MkIteratorError and result = TApproxIteratorError()
|
|
or
|
|
c instanceof MkPromiseValue and result = TApproxPromiseValue()
|
|
or
|
|
c instanceof MkPromiseError and result = TApproxPromiseError()
|
|
or
|
|
c instanceof MkCapturedContent and result = TApproxCapturedContent()
|
|
}
|
|
|
|
overlay[global]
|
|
cached
|
|
private newtype TDataFlowCall =
|
|
MkOrdinaryCall(DataFlow::InvokeNode node) or
|
|
MkPartialCall(DataFlow::PartialInvokeNode node, DataFlow::Node callback) {
|
|
callback = node.getACallbackNode()
|
|
} or
|
|
MkBoundCall(DataFlow::InvokeNode node, int boundArgs) {
|
|
FlowSteps::callsBound(node, _, boundArgs)
|
|
} or
|
|
MkAccessorCall(DataFlow::PropRef node) {
|
|
// Some PropRefs can't result in an accessor call, such as Object.defineProperty.
|
|
// Restrict to PropRefs that can result in an accessor call.
|
|
node = TValueNode(any(PropAccess p)) or
|
|
node = TPropNode(any(PropertyPattern p))
|
|
} or
|
|
MkImpliedLambdaCall(Function f) {
|
|
VariableCaptureConfig::captures(f, _) or CallGraph::impliedReceiverStep(_, TThisNode(f))
|
|
} or
|
|
MkSummaryCall(
|
|
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver
|
|
) {
|
|
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
|
|
}
|
|
|
|
overlay[global]
|
|
class DataFlowCall extends TDataFlowCall {
|
|
DataFlowCallable getEnclosingCallable() { none() } // Overridden in subclass
|
|
|
|
string toString() { none() } // Overridden in subclass
|
|
|
|
DataFlow::InvokeNode asOrdinaryCall() { this = MkOrdinaryCall(result) }
|
|
|
|
DataFlow::PropRef asAccessorCall() { this = MkAccessorCall(result) }
|
|
|
|
DataFlow::PartialInvokeNode asPartialCall() { this = MkPartialCall(result, _) }
|
|
|
|
DataFlow::InvokeNode asBoundCall(int boundArgs) { this = MkBoundCall(result, boundArgs) }
|
|
|
|
Function asImpliedLambdaCall() { this = MkImpliedLambdaCall(result) }
|
|
|
|
predicate isSummaryCall(
|
|
FlowSummaryImpl::Public::SummarizedCallable enclosingCallable,
|
|
FlowSummaryImpl::Private::SummaryNode receiver
|
|
) {
|
|
this = MkSummaryCall(enclosingCallable, receiver)
|
|
}
|
|
|
|
Location getLocation() { none() } // Overridden in subclass
|
|
}
|
|
|
|
overlay[global]
|
|
private class OrdinaryCall extends DataFlowCall, MkOrdinaryCall {
|
|
private DataFlow::InvokeNode node;
|
|
|
|
OrdinaryCall() { this = MkOrdinaryCall(node) }
|
|
|
|
DataFlow::InvokeNode getNode() { result = node }
|
|
|
|
override DataFlowCallable getEnclosingCallable() {
|
|
result.asSourceCallable() = node.getContainer()
|
|
}
|
|
|
|
override string toString() { result = node.toString() }
|
|
|
|
override Location getLocation() { result = node.getLocation() }
|
|
}
|
|
|
|
overlay[global]
|
|
private class PartialCall extends DataFlowCall, MkPartialCall {
|
|
private DataFlow::PartialInvokeNode node;
|
|
private DataFlow::Node callback;
|
|
|
|
PartialCall() { this = MkPartialCall(node, callback) }
|
|
|
|
DataFlow::PartialInvokeNode getNode() { result = node }
|
|
|
|
DataFlow::Node getCallback() { result = callback }
|
|
|
|
override DataFlowCallable getEnclosingCallable() {
|
|
result.asSourceCallable() = node.getContainer()
|
|
}
|
|
|
|
override string toString() { result = node.toString() + " (as partial invocation)" }
|
|
|
|
override Location getLocation() { result = node.getLocation() }
|
|
}
|
|
|
|
overlay[global]
|
|
private class BoundCall extends DataFlowCall, MkBoundCall {
|
|
private DataFlow::InvokeNode node;
|
|
private int boundArgs;
|
|
|
|
BoundCall() { this = MkBoundCall(node, boundArgs) }
|
|
|
|
override DataFlowCallable getEnclosingCallable() {
|
|
result.asSourceCallable() = node.getContainer()
|
|
}
|
|
|
|
override string toString() {
|
|
result = node.toString() + " (as call with " + boundArgs + " bound arguments)"
|
|
}
|
|
|
|
override Location getLocation() { result = node.getLocation() }
|
|
}
|
|
|
|
overlay[global]
|
|
private class AccessorCall extends DataFlowCall, MkAccessorCall {
|
|
private DataFlow::PropRef ref;
|
|
|
|
AccessorCall() { this = MkAccessorCall(ref) }
|
|
|
|
override DataFlowCallable getEnclosingCallable() {
|
|
result.asSourceCallable() = ref.getContainer()
|
|
}
|
|
|
|
override string toString() { result = ref.toString() + " (as accessor call)" }
|
|
|
|
override Location getLocation() { result = ref.getLocation() }
|
|
}
|
|
|
|
overlay[global]
|
|
class SummaryCall extends DataFlowCall, MkSummaryCall {
|
|
private FlowSummaryImpl::Public::SummarizedCallable enclosingCallable;
|
|
private FlowSummaryImpl::Private::SummaryNode receiver;
|
|
|
|
SummaryCall() { this = MkSummaryCall(enclosingCallable, receiver) }
|
|
|
|
override DataFlowCallable getEnclosingCallable() {
|
|
result.asLibraryCallable() = enclosingCallable
|
|
}
|
|
|
|
override string toString() {
|
|
result = "[summary] call to " + receiver + " in " + enclosingCallable
|
|
}
|
|
|
|
/** Gets the receiver node. */
|
|
FlowSummaryImpl::Private::SummaryNode getReceiver() { result = receiver }
|
|
|
|
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() { result = enclosingCallable }
|
|
}
|
|
|
|
/**
|
|
* A call that invokes a lambda with nothing but its self-reference node.
|
|
*
|
|
* This is to help ensure captured variables can flow into the lambda in cases where
|
|
* we can't find its call sites.
|
|
*/
|
|
overlay[global]
|
|
private class ImpliedLambdaCall extends DataFlowCall, MkImpliedLambdaCall {
|
|
private Function function;
|
|
|
|
ImpliedLambdaCall() { this = MkImpliedLambdaCall(function) }
|
|
|
|
override string toString() { result = "[implied lambda call] " + function }
|
|
|
|
override Location getLocation() { result = function.getLocation() }
|
|
|
|
override DataFlowCallable getEnclosingCallable() {
|
|
result.asSourceCallable() = function.getEnclosingContainer()
|
|
}
|
|
}
|
|
|
|
private int getMaxArity() {
|
|
// TODO: account for flow summaries
|
|
result =
|
|
max(int n |
|
|
n = any(InvokeExpr e).getNumArgument() or
|
|
n = any(Function f).getNumParameter() or
|
|
n = 10
|
|
)
|
|
}
|
|
|
|
cached
|
|
newtype TParameterPosition =
|
|
MkPositionalParameter(int n) { n = [0 .. getMaxArity()] } or
|
|
MkPositionalLowerBound(int n) { n = [0 .. getMaxArity()] } or
|
|
MkThisParameter() or
|
|
MkFunctionSelfReferenceParameter() or
|
|
MkStaticArgumentArray() or
|
|
MkDynamicArgumentArray()
|
|
|
|
class ParameterPosition extends TParameterPosition {
|
|
predicate isPositionalExact() { this instanceof MkPositionalParameter }
|
|
|
|
predicate isPositionalLowerBound() { this instanceof MkPositionalLowerBound }
|
|
|
|
predicate isPositionalLike() { this.isPositionalExact() or this.isPositionalLowerBound() }
|
|
|
|
int asPositional() { this = MkPositionalParameter(result) }
|
|
|
|
int asPositionalLowerBound() { this = MkPositionalLowerBound(result) }
|
|
|
|
predicate isThis() { this = MkThisParameter() }
|
|
|
|
predicate isFunctionSelfReference() { this = MkFunctionSelfReferenceParameter() }
|
|
|
|
predicate isStaticArgumentArray() { this = MkStaticArgumentArray() }
|
|
|
|
predicate isDynamicArgumentArray() { this = MkDynamicArgumentArray() }
|
|
|
|
string toString() {
|
|
result = this.asPositional().toString()
|
|
or
|
|
result = this.asPositionalLowerBound().toString() + ".."
|
|
or
|
|
this.isThis() and result = "this"
|
|
or
|
|
this.isFunctionSelfReference() and result = "function"
|
|
or
|
|
this.isStaticArgumentArray() and result = "static-argument-array"
|
|
or
|
|
this.isDynamicArgumentArray() and result = "dynamic-argument-array"
|
|
}
|
|
}
|
|
|
|
class ArgumentPosition extends ParameterPosition { }
|
|
|
|
class DataFlowExpr = Expr;
|
|
|
|
Node exprNode(DataFlowExpr expr) { result = DataFlow::exprNode(expr) }
|
|
|
|
overlay[global]
|
|
pragma[nomagic]
|
|
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
|
ppos = apos
|
|
or
|
|
apos.asPositional() >= ppos.asPositionalLowerBound()
|
|
or
|
|
ppos.asPositional() >= apos.asPositionalLowerBound()
|
|
//
|
|
// Note: for now, there is no need to match lower bounds agaist lower bounds since we
|
|
// are only using these in cases where either the call or callee is generated by a flow summary.
|
|
}
|
|
|
|
overlay[global]
|
|
pragma[inline]
|
|
DataFlowCallable viableCallable(DataFlowCall node) {
|
|
// Note: we never include call edges externs here, as it negatively affects the field-flow branch limit,
|
|
// particularly when the call can also target a flow summary.
|
|
result.asSourceCallableNotExterns() = node.asOrdinaryCall().getACallee()
|
|
or
|
|
result.asSourceCallableNotExterns() =
|
|
node.(PartialCall).getCallback().getAFunctionValue().getFunction()
|
|
or
|
|
exists(DataFlow::InvokeNode invoke, int boundArgs |
|
|
invoke = node.asBoundCall(boundArgs) and
|
|
FlowSteps::callsBound(invoke, result.asSourceCallableNotExterns(), boundArgs)
|
|
)
|
|
or
|
|
result.asSourceCallableNotExterns() = node.asAccessorCall().getAnAccessorCallee().getFunction()
|
|
or
|
|
exists(LibraryCallable callable |
|
|
result = MkLibraryCallable(callable) and
|
|
node.asOrdinaryCall() =
|
|
[
|
|
callable.getACall(), callable.getACallSimple(),
|
|
callable.(LibraryCallableInternal).getACallStage2()
|
|
]
|
|
)
|
|
or
|
|
result.asSourceCallableNotExterns() = node.asImpliedLambdaCall()
|
|
}
|
|
|
|
overlay[global]
|
|
private DataFlowCall getACallOnThis(DataFlow::ClassNode cls) {
|
|
result.asOrdinaryCall() = cls.getAReceiverNode().getAPropertyRead().getACall()
|
|
or
|
|
result.asAccessorCall() = cls.getAReceiverNode().getAPropertyRead()
|
|
or
|
|
result.asPartialCall().getACallbackNode() = cls.getAReceiverNode().getAPropertyRead()
|
|
}
|
|
|
|
overlay[global]
|
|
private predicate downwardCall(DataFlowCall call) {
|
|
exists(DataFlow::ClassNode cls |
|
|
call = getACallOnThis(cls) and
|
|
viableCallable(call).asSourceCallable() =
|
|
cls.getADirectSubClass+().getAnInstanceMember().getFunction()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if the set of viable implementations that can be called by `call`
|
|
* might be improved by knowing the call context.
|
|
*/
|
|
overlay[global]
|
|
predicate mayBenefitFromCallContext(DataFlowCall call) { downwardCall(call) }
|
|
|
|
/** Gets the type of the receiver of `call`. */
|
|
overlay[global]
|
|
private DataFlowType getThisArgumentType(DataFlowCall call) {
|
|
exists(DataFlow::Node node |
|
|
isArgumentNodeImpl(node, call, MkThisParameter()) and
|
|
result = getNodeType(node)
|
|
)
|
|
}
|
|
|
|
/** Gets the type of the 'this' parameter of `call`. */
|
|
overlay[global]
|
|
private DataFlowType getThisParameterType(DataFlowCallable callable) {
|
|
exists(DataFlow::Node node |
|
|
isParameterNodeImpl(node, callable, MkThisParameter()) and
|
|
result = getNodeType(node)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a viable dispatch target of `call` in the context `ctx`. This is
|
|
* restricted to those `call`s for which a context might make a difference.
|
|
*/
|
|
overlay[global]
|
|
DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
|
|
mayBenefitFromCallContext(call) and
|
|
result = viableCallable(call) and
|
|
viableCallable(ctx) = call.getEnclosingCallable() and
|
|
compatibleTypes(getThisArgumentType(ctx), getThisParameterType(result))
|
|
}
|
|
|
|
bindingset[node, fun]
|
|
overlay[caller?]
|
|
pragma[inline_late]
|
|
private predicate sameContainerAsEnclosingContainer(Node node, Function fun) {
|
|
node.getContainer() = fun.getEnclosingContainer()
|
|
}
|
|
|
|
overlay[global]
|
|
abstract private class BarrierGuardAdapter extends DataFlow::Node {
|
|
// Note: avoid depending on DataFlow::FlowLabel here as it will cause these barriers to be re-evaluated
|
|
predicate blocksExpr(boolean outcome, Expr e) { none() }
|
|
}
|
|
|
|
overlay[global]
|
|
deprecated private class BarrierGuardAdapterSubclass extends BarrierGuardAdapter instanceof DataFlow::AdditionalBarrierGuardNode
|
|
{
|
|
override predicate blocksExpr(boolean outcome, Expr e) { super.blocks(outcome, e) }
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` should be a barrier in all data flow configurations due to custom subclasses
|
|
* of `AdditionalBarrierGuardNode`.
|
|
*
|
|
* The standard library contains no subclasses of that class; this is for backwards compatibility only.
|
|
*/
|
|
overlay[global]
|
|
pragma[nomagic]
|
|
private predicate legacyBarrier(DataFlow::Node node) {
|
|
node = MakeBarrierGuard<BarrierGuardAdapter>::getABarrierNode()
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` should be removed from the local data flow graph, for compatibility with legacy code.
|
|
*/
|
|
overlay[global]
|
|
pragma[nomagic]
|
|
private predicate isBlockedLegacyNode(Node node) {
|
|
// Ignore captured variable nodes for those variables that are handled by the captured-variable library.
|
|
// Note that some variables, such as top-level variables, are still modeled with these nodes (which will result in jump steps).
|
|
exists(LocalVariable variable |
|
|
node = TCapturedVariableNode(variable) and
|
|
variable = any(VariableCaptureConfig::CapturedVariable v).asLocalVariable()
|
|
)
|
|
or
|
|
legacyBarrier(node)
|
|
}
|
|
|
|
/**
|
|
* Holds if `thisNode` represents a value of `this` that is being tracked by the
|
|
* variable capture library.
|
|
*
|
|
* In this case we need to suppress the default flow steps between `thisNode` and
|
|
* the `ThisExpr` nodes; especially those that would become jump steps.
|
|
*
|
|
* Note that local uses of `this` are sometimes tracked by the local SSA library, but we should
|
|
* not block local def-use flow, since we only switch to use-use flow after a post-update.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate isThisNodeTrackedByVariableCapture(DataFlow::ThisNode thisNode) {
|
|
exists(StmtContainer container | thisNode = TThisNode(container) |
|
|
any(VariableCaptureConfig::CapturedVariable v).asThisContainer() = container
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if there should be flow from `postUpdate` to `target` because of a variable/this value
|
|
* that is captured but not tracked precisely by the variable-capture library.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate imprecisePostUpdateStep(DataFlow::PostUpdateNode postUpdate, DataFlow::Node target) {
|
|
exists(LocalVariableOrThis var, DataFlow::Node use |
|
|
// 'var' is captured but not tracked precisely
|
|
var.isCaptured() and
|
|
not var instanceof VariableCaptureConfig::CapturedVariable and
|
|
(
|
|
use = TValueNode(var.asLocalVariable().getAnAccess())
|
|
or
|
|
use = TValueNode(var.getAThisExpr())
|
|
or
|
|
use = TImplicitThisUse(var.getAThisUse(), false)
|
|
) and
|
|
postUpdate.getPreUpdateNode() = use and
|
|
target = use.getALocalSource()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if there is a value-preserving steps `node1` -> `node2` that might
|
|
* be cross function boundaries.
|
|
*/
|
|
overlay[global]
|
|
private predicate valuePreservingStep(Node node1, Node node2) {
|
|
node1.getASuccessor() = node2 and
|
|
not isBlockedLegacyNode(node1) and
|
|
not isBlockedLegacyNode(node2) and
|
|
not isThisNodeTrackedByVariableCapture(node1)
|
|
or
|
|
imprecisePostUpdateStep(node1, node2)
|
|
or
|
|
FlowSteps::propertyFlowStep(node1, node2)
|
|
or
|
|
FlowSteps::globalFlowStep(node1, node2)
|
|
or
|
|
node2 = FlowSteps::getThrowTarget(node1)
|
|
or
|
|
FlowSummaryPrivate::Steps::summaryLocalStep(node1.(FlowSummaryNode).getSummaryNode(),
|
|
node2.(FlowSummaryNode).getSummaryNode(), true, _) // TODO: preserve 'model'
|
|
}
|
|
|
|
predicate knownSourceModel(Node sink, string model) { none() }
|
|
|
|
predicate knownSinkModel(Node sink, string model) { none() }
|
|
|
|
private predicate samePhi(SsaPhiNode legacyPhi, Ssa2::PhiNode newPhi) {
|
|
exists(BasicBlock bb, LocalVariableOrThis v |
|
|
newPhi.definesAt(v, bb, _) and
|
|
legacyPhi.definesAt(bb, _, v.asLocalVariable())
|
|
)
|
|
}
|
|
|
|
cached
|
|
Node getNodeFromSsa2(Ssa2::Node node) {
|
|
result = TSsaUseNode(node.(Ssa2::ExprNode).getExpr())
|
|
or
|
|
result = TExprPostUpdateNode(node.(Ssa2::ExprPostUpdateNode).getExpr())
|
|
or
|
|
exists(ImplicitThisUse use |
|
|
node.(Ssa2::ExprPostUpdateNode).getExpr() = use and
|
|
result = TImplicitThisUse(use, true)
|
|
)
|
|
or
|
|
result = TSsaSynthReadNode(node)
|
|
or
|
|
exists(SsaPhiNode legacyPhi, Ssa2::PhiNode ssaPhi |
|
|
node.(Ssa2::SsaDefinitionNode).getDefinition() = ssaPhi and
|
|
samePhi(legacyPhi, ssaPhi) and
|
|
result = TSsaDefNode(legacyPhi)
|
|
)
|
|
}
|
|
|
|
private predicate useUseFlow(Node node1, Node node2) {
|
|
exists(Ssa2::Node ssa1, Ssa2::Node ssa2 |
|
|
Ssa2::localFlowStep(_, ssa1, ssa2, _) and
|
|
node1 = getNodeFromSsa2(ssa1) and
|
|
node2 = getNodeFromSsa2(ssa2) and
|
|
not node1.getTopLevel().isExterns()
|
|
)
|
|
or
|
|
exists(Expr use |
|
|
node1 = TSsaUseNode(use) and
|
|
node2 = TValueNode(use)
|
|
)
|
|
or
|
|
exists(ImplicitThisUse use |
|
|
node1 = TSsaUseNode(use) and
|
|
node2 = TImplicitThisUse(use, false)
|
|
)
|
|
}
|
|
|
|
overlay[global]
|
|
predicate simpleLocalFlowStep(Node node1, Node node2, string model) {
|
|
simpleLocalFlowStep(node1, node2) and model = ""
|
|
}
|
|
|
|
overlay[global]
|
|
predicate simpleLocalFlowStep(Node node1, Node node2) {
|
|
valuePreservingStep(node1, node2) and
|
|
nodeGetEnclosingCallable(pragma[only_bind_out](node1)) =
|
|
nodeGetEnclosingCallable(pragma[only_bind_out](node2))
|
|
or
|
|
useUseFlow(node1, node2)
|
|
or
|
|
exists(FlowSummaryImpl::Private::SummaryNode input, FlowSummaryImpl::Private::SummaryNode output |
|
|
FlowSummaryPrivate::Steps::summaryStoreStep(input, MkAwaited(), output) and
|
|
node1 = TFlowSummaryNode(input) and
|
|
(
|
|
node2 = TFlowSummaryNode(output) and
|
|
not node2 instanceof PostUpdateNode // When doing a store-back, do not add the local flow edge
|
|
or
|
|
node2 = TFlowSummaryIntermediateAwaitStoreNode(input)
|
|
)
|
|
or
|
|
FlowSummaryPrivate::Steps::summaryReadStep(input, MkAwaited(), output) and
|
|
node1 = TFlowSummaryNode(input) and
|
|
node2 = TFlowSummaryNode(output)
|
|
or
|
|
// Add flow through optional barriers. This step is then blocked by the barrier for queries that choose to use the barrier.
|
|
FlowSummaryPrivate::Steps::summaryReadStep(input, MkOptionalBarrier(_), output) and
|
|
node1 = TFlowSummaryNode(input) and
|
|
node2 = TFlowSummaryNode(output)
|
|
)
|
|
or
|
|
VariableCaptureOutput::localFlowStep(getClosureNode(node1), getClosureNode(node2))
|
|
or
|
|
// NOTE: For consistency with readStep/storeStep, we do not translate these steps to jump steps automatically.
|
|
DataFlow::AdditionalFlowStep::step(node1, node2)
|
|
or
|
|
exists(InvokeExpr invoke |
|
|
// When the first argument is a spread argument, flow into the argument array as a local flow step
|
|
// to ensure we preserve knowledge about array indices
|
|
node1 = TValueNode(invoke.getArgument(0).stripParens().(SpreadElement).getOperand()) and
|
|
node2 = TDynamicArgumentArrayNode(invoke)
|
|
)
|
|
or
|
|
exists(Function f |
|
|
// When the first parameter is a rest parameter, flow into the rest parameter as a local flow step
|
|
// to ensure we preserve knowledge about array indices
|
|
node1 = TStaticParameterArrayNode(f) or node1 = TDynamicParameterArrayNode(f)
|
|
|
|
|
// rest parameter at initial position
|
|
exists(Parameter rest |
|
|
rest = f.getParameter(0) and
|
|
rest.isRestParameter() and
|
|
node2 = TValueNode(rest)
|
|
)
|
|
or
|
|
// 'arguments' array
|
|
node2 = TReflectiveParametersNode(f)
|
|
)
|
|
or
|
|
// Prepare to store non-spread arguments after a spread into the dynamic arguments array
|
|
exists(InvokeExpr invoke, int n, Expr argument, Content storeContent |
|
|
invoke.getArgument(n) = argument and
|
|
not argument instanceof SpreadElement and
|
|
n > firstSpreadArgumentIndex(invoke) and
|
|
node1 = TValueNode(argument) and
|
|
node2 = TDynamicArgumentStoreNode(invoke, storeContent) and
|
|
storeContent.isUnknownArrayElement()
|
|
)
|
|
}
|
|
|
|
predicate localMustFlowStep(Node node1, Node node2) { node1 = node2.getImmediatePredecessor() }
|
|
|
|
/**
|
|
* Holds if `node1 -> node2` should be removed as a jump step.
|
|
*
|
|
* Currently this is done as a workaround for the local steps generated from IIFEs.
|
|
*/
|
|
private predicate excludedJumpStep(Node node1, Node node2) {
|
|
exists(ImmediatelyInvokedFunctionExpr iife |
|
|
iife.argumentPassing(node2.asExpr(), node1.asExpr())
|
|
or
|
|
node1 = iife.getAReturnedExpr().flow() and
|
|
node2 = iife.getInvocation().flow()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow from `node1` to `node2` through a non-local step
|
|
* that does not follow a call edge. For example, a step through a global
|
|
* variable.
|
|
*/
|
|
overlay[global]
|
|
predicate jumpStep(Node node1, Node node2) {
|
|
valuePreservingStep(node1, node2) and
|
|
node1.getContainer() != node2.getContainer() and
|
|
not excludedJumpStep(node1, node2)
|
|
or
|
|
FlowSummaryPrivate::Steps::summaryJumpStep(node1.(FlowSummaryNode).getSummaryNode(),
|
|
node2.(FlowSummaryNode).getSummaryNode())
|
|
or
|
|
DataFlow::AdditionalFlowStep::jumpStep(node1, node2)
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow from `node1` to `node2` via a read of `c`. Thus,
|
|
* `node1` references an object with a content `c.getAReadContent()` whose
|
|
* value ends up in `node2`.
|
|
*/
|
|
overlay[global]
|
|
predicate readStep(Node node1, ContentSet c, Node node2) {
|
|
exists(DataFlow::PropRead read |
|
|
node1 = read.getBase() and
|
|
node2 = read
|
|
|
|
|
exists(PropertyName name | read.getPropertyName() = name |
|
|
not exists(name.asArrayIndex()) and
|
|
c = ContentSet::property(name)
|
|
or
|
|
c = ContentSet::arrayElementKnown(name.asArrayIndex())
|
|
)
|
|
or
|
|
not exists(read.getPropertyName()) and
|
|
c = ContentSet::arrayElement()
|
|
)
|
|
or
|
|
exists(ContentSet contentSet |
|
|
FlowSummaryPrivate::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), contentSet,
|
|
node2.(FlowSummaryNode).getSummaryNode())
|
|
|
|
|
not isSpecialContentSet(contentSet) and
|
|
c = contentSet
|
|
or
|
|
contentSet = MkAwaited() and
|
|
c = ContentSet::promiseValue()
|
|
)
|
|
or
|
|
// For deep reads, generate read edges with a self-loop
|
|
exists(Node origin, ContentSet contentSet |
|
|
FlowSummaryPrivate::Steps::summaryReadStep(origin.(FlowSummaryNode).getSummaryNode(),
|
|
contentSet, node2.(FlowSummaryNode).getSummaryNode()) and
|
|
node1 = [origin, node2]
|
|
|
|
|
contentSet = MkAnyPropertyDeep() and
|
|
c = ContentSet::anyProperty()
|
|
or
|
|
contentSet = MkArrayElementDeep() and
|
|
c = ContentSet::arrayElement()
|
|
)
|
|
or
|
|
exists(LocalVariableOrThis variable |
|
|
VariableCaptureOutput::readStep(getClosureNode(node1), variable, getClosureNode(node2)) and
|
|
c.asSingleton() = MkCapturedContent(variable)
|
|
)
|
|
or
|
|
DataFlow::AdditionalFlowStep::readStep(node1, c, node2)
|
|
or
|
|
// Pass dynamic arguments into plain parameters
|
|
exists(Function function, Parameter param, int n |
|
|
param = function.getParameter(n) and
|
|
not param.isRestParameter() and
|
|
node1 = TDynamicParameterArrayNode(function) and
|
|
node2 = TValueNode(param) and
|
|
c = ContentSet::arrayElementFromInt(n)
|
|
)
|
|
or
|
|
// Prepare to store dynamic and static arguments into the rest parameter array when it isn't the first parameter
|
|
exists(Function function, Content content, int restIndex |
|
|
restIndex = function.getRestParameter().getIndex() and
|
|
restIndex > 0 and
|
|
(node1 = TStaticParameterArrayNode(function) or node1 = TDynamicParameterArrayNode(function)) and
|
|
node2 = TRestParameterStoreNode(function, content)
|
|
|
|
|
// shift known array indices
|
|
c.asSingleton().asArrayIndex() = content.asArrayIndex() + restIndex
|
|
or
|
|
content.isUnknownArrayElement() and
|
|
c = ContentSet::arrayElementUnknown()
|
|
)
|
|
or
|
|
// Prepare to store spread arguments into the dynamic arguments array, when it isn't the initial argument
|
|
exists(InvokeExpr invoke, int n, Expr argument, Content storeContent |
|
|
invoke.getArgument(n).stripParens().(SpreadElement).getOperand() = argument and
|
|
n > 0 and // n=0 is handled as a value step
|
|
node1 = TValueNode(argument) and
|
|
node2 = TDynamicArgumentStoreNode(invoke, storeContent) and
|
|
if n > firstSpreadArgumentIndex(invoke)
|
|
then
|
|
c = ContentSet::arrayElement() and // unknown start index when not the first spread operator
|
|
storeContent.isUnknownArrayElement()
|
|
else (
|
|
storeContent.asArrayIndex() = n + c.asSingleton().asArrayIndex()
|
|
or
|
|
storeContent.isUnknownArrayElement() and c.asSingleton() = storeContent
|
|
)
|
|
)
|
|
or
|
|
exists(FlowSummaryNode parameter, ParameterPosition pos |
|
|
FlowSummaryImpl::Private::summaryParameterNode(parameter.getSummaryNode(), pos) and
|
|
node1 = TFlowSummaryDynamicParameterArrayNode(parameter.getSummarizedCallable()) and
|
|
node2 = parameter and
|
|
(
|
|
c.asSingleton().asArrayIndex() = pos.asPositional()
|
|
or
|
|
c = ContentSet::arrayElementLowerBound(pos.asPositionalLowerBound())
|
|
)
|
|
)
|
|
or
|
|
// Implicitly read array elements before stringification
|
|
stringifiedNode(node1) and
|
|
node2 = node1 and
|
|
c = ContentSet::arrayElement()
|
|
}
|
|
|
|
private predicate stringifiedNode(Node node) {
|
|
exists(Expr e | node = TValueNode(e) |
|
|
e = any(AddExpr add).getAnOperand() and
|
|
not e instanceof StringLiteral
|
|
or
|
|
e = any(TemplateLiteral t).getAnElement() and
|
|
not e instanceof TemplateElement
|
|
)
|
|
or
|
|
node = DataFlow::globalVarRef("String").getAnInvocation().getArgument(0)
|
|
}
|
|
|
|
/** Gets the post-update node for which `node` is the corresponding pre-update node. */
|
|
pragma[nomagic]
|
|
private Node getPostUpdateForStore(Node base) {
|
|
exists(Expr expr |
|
|
base = TValueNode(expr) and
|
|
result = TExprPostUpdateNode(expr)
|
|
|
|
|
// When object/array literal appears as an argument to a call, we would generally need two post-update nodes:
|
|
// - one for the stores coming from the properties or array elements (which happen before the call and must flow into the call)
|
|
// - one for the argument position, to propagate the updates that happened during the call
|
|
//
|
|
// However, the first post-update is not actually needed since we are storing into a brand new object, so in the first case
|
|
// we just target the expression directly. In the second case we use the ExprPostUpdateNode.
|
|
not expr instanceof ObjectExpr and
|
|
not expr instanceof ArrayExpr
|
|
)
|
|
or
|
|
exists(ImplicitThisUse use |
|
|
base = TImplicitThisUse(use, false) and
|
|
result = TImplicitThisUse(use, true)
|
|
)
|
|
}
|
|
|
|
/** Gets node to target with a store to the given `base` object.. */
|
|
overlay[caller?]
|
|
pragma[inline]
|
|
private Node getStoreTarget(DataFlow::Node base) {
|
|
result = getPostUpdateForStore(base)
|
|
or
|
|
not exists(getPostUpdateForStore(base)) and
|
|
result = base
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private int firstSpreadArgumentIndex(InvokeExpr expr) {
|
|
result = min(int i | expr.isSpreadArgument(i))
|
|
}
|
|
|
|
/**
|
|
* Holds if data can flow from `node1` to `node2` via a store into `c`. Thus,
|
|
* `node2` references an object with a content `c.getAStoreContent()` that
|
|
* contains the value of `node1`.
|
|
*/
|
|
overlay[global]
|
|
predicate storeStep(Node node1, ContentSet c, Node node2) {
|
|
exists(DataFlow::PropWrite write |
|
|
node1 = write.getRhs() and
|
|
c.asPropertyName() = write.getPropertyName() and
|
|
// Target the post-update node if one exists (for object literals we do not generate post-update nodes)
|
|
node2 = getStoreTarget(write.getBase())
|
|
)
|
|
or
|
|
FlowSummaryPrivate::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
|
|
node2.(FlowSummaryNode).getSummaryNode()) and
|
|
not isSpecialContentSet(c)
|
|
or
|
|
// Store into Awaited
|
|
exists(FlowSummaryImpl::Private::SummaryNode input, FlowSummaryImpl::Private::SummaryNode output |
|
|
FlowSummaryPrivate::Steps::summaryStoreStep(input, MkAwaited(), output) and
|
|
node1 = TFlowSummaryIntermediateAwaitStoreNode(input) and
|
|
node2 = TFlowSummaryNode(output) and
|
|
c = ContentSet::promiseValue()
|
|
)
|
|
or
|
|
exists(LocalVariableOrThis variable |
|
|
VariableCaptureOutput::storeStep(getClosureNode(node1), variable, getClosureNode(node2)) and
|
|
c.asSingleton() = MkCapturedContent(variable)
|
|
)
|
|
or
|
|
DataFlow::AdditionalFlowStep::storeStep(node1, c, node2)
|
|
or
|
|
exists(Function f, Content storeContent |
|
|
node1 = TRestParameterStoreNode(f, storeContent) and
|
|
node2 = TValueNode(f.getRestParameter()) and
|
|
c.asSingleton() = storeContent
|
|
)
|
|
or
|
|
exists(InvokeExpr invoke, Content storeContent |
|
|
node1 = TDynamicArgumentStoreNode(invoke, storeContent) and
|
|
node2 = TDynamicArgumentArrayNode(invoke) and
|
|
c.asSingleton() = storeContent
|
|
)
|
|
or
|
|
exists(InvokeExpr invoke, int n |
|
|
node1 = TValueNode(invoke.getArgument(n)) and
|
|
node2 = TStaticArgumentArrayNode(invoke) and
|
|
c.asSingleton().asArrayIndex() = n and
|
|
not n >= firstSpreadArgumentIndex(invoke)
|
|
)
|
|
or
|
|
exists(ApplyCallTaintNode taintNode |
|
|
node1 = taintNode and
|
|
node2 = taintNode.getArrayNode() and
|
|
c = ContentSet::arrayElementUnknown()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if values stored inside content `c` are cleared at node `n`. For example,
|
|
* any value stored inside `f` is cleared at the pre-update node associated with `x`
|
|
* in `x.f = newValue`.
|
|
*/
|
|
overlay[global]
|
|
predicate clearsContent(Node n, ContentSet c) {
|
|
FlowSummaryPrivate::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
|
|
or
|
|
// Clear promise content before storing into promise value, to avoid creating nested promises
|
|
n = TFlowSummaryIntermediateAwaitStoreNode(_) and
|
|
c = MkPromiseFilter()
|
|
or
|
|
// After reading from Awaited, the output must not be stored in a promise content
|
|
FlowSummaryPrivate::Steps::summaryReadStep(_, MkAwaited(), n.(FlowSummaryNode).getSummaryNode()) and
|
|
c = MkPromiseFilter()
|
|
or
|
|
any(AdditionalFlowInternal flow).clearsContent(n, c)
|
|
or
|
|
// When a function `f` captures itself, all its access paths can be prefixed by an arbitrary number of `f.f.f...`.
|
|
// When multiple functions `f,g` capture each other, these prefixes can become interleaved, like `f.g.f.g...`.
|
|
// To avoid creating these trivial prefixes, we never allow two consecutive captured variables in the access path.
|
|
// We implement this rule by clearing any captured-content before storing into another captured-content.
|
|
VariableCaptureOutput::storeStep(getClosureNode(n), _, _) and
|
|
c = MkAnyCapturedContent()
|
|
or
|
|
// Block flow into the "window.location" property, as any assignment/mutation to this causes a page load and stops execution.
|
|
// The use of clearsContent here ensures we also block assignments like `window.location.href = ...`
|
|
exists(DataFlow::PropRef ref |
|
|
ref = DataFlow::globalObjectRef().getAPropertyReference("location") and
|
|
n = ref.getBase().getPostUpdateNode() and
|
|
c = ContentSet::property("location")
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if the value that is being tracked is expected to be stored inside content `c`
|
|
* at node `n`.
|
|
*/
|
|
overlay[global]
|
|
predicate expectsContent(Node n, ContentSet c) {
|
|
FlowSummaryPrivate::Steps::summaryExpectsContent(n.(FlowSummaryNode).getSummaryNode(), c)
|
|
or
|
|
// After storing into Awaited, the result must be stored in a promise-content.
|
|
// There is a value step from the input directly to this node, hence the need for expectsContent.
|
|
FlowSummaryPrivate::Steps::summaryStoreStep(_, MkAwaited(), n.(FlowSummaryNode).getSummaryNode()) and
|
|
c = MkPromiseFilter()
|
|
or
|
|
any(AdditionalFlowInternal flow).expectsContent(n, c)
|
|
or
|
|
c = ContentSet::arrayElement() and
|
|
n instanceof TDynamicParameterArrayNode
|
|
}
|
|
|
|
abstract class NodeRegion extends Unit {
|
|
NodeRegion() { none() }
|
|
|
|
/** Holds if this region contains `n`. */
|
|
predicate contains(Node n) { none() }
|
|
}
|
|
|
|
/**
|
|
* Holds if the node `n` is unreachable when the call context is `call`.
|
|
*/
|
|
overlay[global]
|
|
predicate isUnreachableInCall(NodeRegion n, DataFlowCall call) {
|
|
none() // TODO: could be useful, but not currently implemented for JS
|
|
}
|
|
|
|
int accessPathLimit() { result = 2 }
|
|
|
|
/**
|
|
* Holds if flow is allowed to pass from parameter `p` and back to itself as a
|
|
* side-effect, resulting in a summary from `p` to itself.
|
|
*
|
|
* One example would be to allow flow like `p.foo = p.bar;`, which is disallowed
|
|
* by default as a heuristic.
|
|
*/
|
|
predicate allowParameterReturnInSelf(ParameterNode p) {
|
|
exists(DataFlowCallable callable, ParameterPosition pos |
|
|
isParameterNodeImpl(p, callable, pos) and
|
|
FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(callable.asLibraryCallable(), pos)
|
|
)
|
|
or
|
|
exists(Function f |
|
|
VariableCaptureOutput::heuristicAllowInstanceParameterReturnInSelf(f) and
|
|
p = TFunctionSelfReferenceNode(f)
|
|
)
|
|
}
|
|
|
|
class LambdaCallKind = Unit;
|
|
|
|
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
|
|
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) {
|
|
creation.(DataFlow::FunctionNode).getFunction() = c.asSourceCallable() and exists(kind)
|
|
}
|
|
|
|
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
|
|
overlay[global]
|
|
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
|
|
call.isSummaryCall(_, receiver.(FlowSummaryNode).getSummaryNode()) and exists(kind)
|
|
or
|
|
receiver = call.asOrdinaryCall().getCalleeNode() and
|
|
exists(kind) and
|
|
receiver.getALocalSource() instanceof DataFlow::ParameterNode
|
|
}
|
|
|
|
/** Extra data-flow steps needed for lambda flow analysis. */
|
|
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
|
|
|
|
overlay[global]
|
|
class ArgumentNode extends DataFlow::Node {
|
|
ArgumentNode() { isArgumentNodeImpl(this, _, _) }
|
|
|
|
predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
|
isArgumentNodeImpl(this, call, pos)
|
|
}
|
|
}
|
|
|
|
class ParameterNode extends DataFlow::Node {
|
|
ParameterNode() { isParameterNodeImpl(this, _, _) }
|
|
}
|
|
|
|
cached
|
|
private module OptionalSteps {
|
|
cached
|
|
predicate optionalStep(Node node1, string name, Node node2) {
|
|
FlowSummaryPrivate::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(),
|
|
MkOptionalStep(name), node2.(FlowSummaryNode).getSummaryNode())
|
|
}
|
|
|
|
cached
|
|
predicate optionalBarrier(Node node, string name) {
|
|
FlowSummaryPrivate::Steps::summaryReadStep(_, MkOptionalBarrier(name),
|
|
node.(FlowSummaryNode).getSummaryNode())
|
|
}
|
|
}
|
|
|
|
import OptionalSteps
|