Files
codeql/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
Anders Schack-Mulligen e7edf15031 C#: Clean up.
2026-03-16 08:51:51 +01:00

2958 lines
95 KiB
Plaintext

private import csharp
private import DataFlowPublic
private import DataFlowDispatch
private import DataFlowImplCommon
private import FlowSummaryImpl as FlowSummaryImpl
private import semmle.code.csharp.dataflow.FlowSummary as FlowSummary
private import semmle.code.csharp.dataflow.internal.ExternalFlow
private import semmle.code.csharp.commons.Collections
private import semmle.code.csharp.Conversion
private import semmle.code.csharp.exprs.internal.Expr
private import semmle.code.csharp.dataflow.internal.SsaImpl as SsaImpl
private import semmle.code.csharp.ExprOrStmtParent
private import semmle.code.csharp.Unification
private import semmle.code.csharp.controlflow.Guards
private import semmle.code.csharp.dispatch.Dispatch
private import semmle.code.csharp.frameworks.EntityFramework
private import semmle.code.csharp.frameworks.system.linq.Expressions
private import semmle.code.csharp.frameworks.NHibernate
private import semmle.code.csharp.frameworks.Razor
private import semmle.code.csharp.frameworks.system.threading.Tasks
private import semmle.code.csharp.internal.Location
private import codeql.util.Unit
private import codeql.util.Boolean
/** Gets the callable in which this node occurs. */
DataFlowCallable nodeGetEnclosingCallable(Node n) {
result = n.(NodeImpl).getEnclosingCallableImpl()
}
/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
p.(ParameterNodeImpl).isParameterOf(c, pos)
}
/** Holds if `arg` is an `ArgumentNode` of `c` with position `pos`. */
predicate isArgumentNode(ArgumentNode arg, DataFlowCall c, ArgumentPosition pos) {
arg.argumentOf(c, pos)
}
/**
* Gets a control flow node used for data flow purposes for the primary constructor
* parameter access `pa`.
*/
private ControlFlow::Node getAPrimaryConstructorParameterCfn(ParameterAccess pa) {
pa.getTarget().getCallable() instanceof PrimaryConstructor and
(
result = pa.(ParameterRead).getAControlFlowNode()
or
pa =
any(AssignableDefinition def | result = def.getExpr().getAControlFlowNode()).getTargetAccess()
)
}
abstract class NodeImpl extends Node {
/** Do not call: use `getEnclosingCallable()` instead. */
abstract DataFlowCallable getEnclosingCallableImpl();
/** Do not call: use `getType()` instead. */
cached
abstract Type getTypeImpl();
/** Gets the type of this node used for type pruning. */
DataFlowType getDataFlowType() {
forceCachingInSameStage() and
exists(Type t0 | result.asGvnType() = Gvn::getGlobalValueNumber(t0) |
t0 = this.getType()
or
not exists(this.getType()) and
t0 instanceof ObjectType
)
}
/** Do not call: use `getControlFlowNode()` instead. */
cached
abstract ControlFlow::Node getControlFlowNodeImpl();
/** Do not call: use `getLocation()` instead. */
cached
abstract Location getLocationImpl();
/** Do not call: use `toString()` instead. */
cached
abstract string toStringImpl();
}
// TODO: Remove once static initializers are folded into the
// static constructors
private DataFlowCallable getEnclosingStaticFieldOrProperty(Expr e) {
result.asFieldOrProperty() =
any(FieldOrProperty f |
f.isStatic() and
e = f.getAChild+() and
not exists(e.getEnclosingCallable())
)
}
private class ExprNodeImpl extends ExprNode, NodeImpl {
override DataFlowCallable getEnclosingCallableImpl() {
result.getAControlFlowNode() = this.getControlFlowNodeImpl()
or
result = getEnclosingStaticFieldOrProperty(this.asExpr())
}
override Type getTypeImpl() {
forceCachingInSameStage() and
result = this.getExpr().getType()
}
override ControlFlow::Nodes::ElementNode getControlFlowNodeImpl() {
forceCachingInSameStage() and this = TExprNode(result)
}
override Location getLocationImpl() {
forceCachingInSameStage() and result = this.getExpr().getLocation()
}
override string toStringImpl() {
forceCachingInSameStage() and
result = this.getControlFlowNodeImpl().toString()
}
}
/**
* A node that represents the creation of a local function.
*
* Needed for flow through captured variables, where we treat local functions
* as if they were lambdas.
*/
abstract private class LocalFunctionCreationNode extends NodeImpl, TLocalFunctionCreationNode {
ControlFlow::Nodes::ElementNode cfn;
LocalFunction function;
boolean isPostUpdate;
LocalFunctionCreationNode() {
this = TLocalFunctionCreationNode(cfn, isPostUpdate) and
function = cfn.getAstNode().(LocalFunctionStmt).getLocalFunction()
}
LocalFunction getFunction() { result = function }
ExprNode getAnAccess(boolean inSameCallable) {
isLocalFunctionCallReceiver(_, result.getExpr(), this.getFunction()) and
if result.getEnclosingCallable() = this.getEnclosingCallable()
then inSameCallable = true
else inSameCallable = false
}
override DataFlowCallable getEnclosingCallableImpl() { result.getAControlFlowNode() = cfn }
override Type getTypeImpl() { none() }
override DataFlowType getDataFlowType() { result.asDelegate() = function }
override ControlFlow::Nodes::ElementNode getControlFlowNodeImpl() { none() }
ControlFlow::Nodes::ElementNode getUnderlyingControlFlowNode() { result = cfn }
override Location getLocationImpl() { result = cfn.getLocation() }
}
private class LocalFunctionCreationPreNode extends LocalFunctionCreationNode {
LocalFunctionCreationPreNode() { isPostUpdate = false }
override string toStringImpl() { result = cfn.toString() }
}
/** Calculation of the relative order in which `this` references are read. */
private module ThisFlow {
private class BasicBlock = ControlFlow::BasicBlock;
/** Holds if `e` is a `this` access. */
predicate thisAccessExpr(Expr e) { e instanceof ThisAccess or e instanceof BaseAccess }
/** Holds if `n` is a `this` access at control flow node `cfn`. */
private predicate thisAccess(Node n, ControlFlow::Node cfn) {
thisAccessExpr(n.asExprAtNode(cfn))
or
cfn = n.(InstanceParameterAccessPreNode).getUnderlyingControlFlowNode()
}
private predicate primaryConstructorThisAccess(Node n, BasicBlock bb, int ppos) {
exists(Parameter p |
n.(PrimaryConstructorThisAccessPreNode).getParameter() = p and
bb.getCallable() = p.getCallable() and
ppos = p.getPosition()
)
}
private int numberOfPrimaryConstructorParameters(BasicBlock bb) {
result = strictcount(int primaryParamPos | primaryConstructorThisAccess(_, bb, primaryParamPos))
}
private predicate thisAccess(Node n, BasicBlock bb, int i) {
thisAccess(n, bb.getNode(i))
or
exists(int ppos |
primaryConstructorThisAccess(n, bb, ppos) and
i = ppos - numberOfPrimaryConstructorParameters(bb)
)
or
exists(DataFlowCallable c, ControlFlow::BasicBlocks::EntryBlock entry |
n.(InstanceParameterNode).isParameterOf(c, _) and
exists(ControlFlow::Node succ |
succ = c.getAControlFlowNode() and
succ = entry.getFirstNode().getASuccessor() and
// In case `c` has multiple bodies, we want each body to gets its own implicit
// entry definition. In case `c` doesn't have multiple bodies, the line below
// is simply the same as `bb = entry`, because `entry.getFirstNode().getASuccessor()`
// will be in the entry block.
bb = succ.getBasicBlock()
|
i = -1 - numberOfPrimaryConstructorParameters(bb)
or
not exists(numberOfPrimaryConstructorParameters(bb)) and i = -1
)
)
}
private predicate thisRank(Node n, BasicBlock bb, int rankix) {
exists(int i |
i = rank[rankix](int j | thisAccess(_, bb, j)) and
thisAccess(n, bb, i)
)
}
private int lastRank(BasicBlock bb) { result = max(int rankix | thisRank(_, bb, rankix)) }
private predicate blockPrecedesThisAccess(BasicBlock bb) { thisAccess(_, bb.getASuccessor*(), _) }
private predicate thisAccessBlockReaches(BasicBlock bb1, BasicBlock bb2) {
thisAccess(_, bb1, _) and bb2 = bb1.getASuccessor()
or
exists(BasicBlock mid |
thisAccessBlockReaches(bb1, mid) and
bb2 = mid.getASuccessor() and
not thisAccess(_, mid, _) and
blockPrecedesThisAccess(bb2)
)
}
private predicate thisAccessBlockStep(BasicBlock bb1, BasicBlock bb2) {
thisAccessBlockReaches(bb1, bb2) and
thisAccess(_, bb2, _)
}
/** Holds if `n1` and `n2` are control-flow adjacent references to `this`. */
predicate adjacentThisRefs(Node n1, Node n2) {
exists(int rankix, BasicBlock bb |
thisRank(n1, bb, rankix) and
thisRank(n2, bb, rankix + 1)
)
or
exists(BasicBlock bb1, BasicBlock bb2 |
thisRank(n1, bb1, lastRank(bb1)) and
thisAccessBlockStep(bb1, bb2) and
thisRank(n2, bb2, 1)
)
}
}
/** Provides logic related to captured variables. */
module VariableCapture {
private import codeql.dataflow.VariableCapture as Shared
private import semmle.code.csharp.controlflow.BasicBlocks as BasicBlocks
private predicate closureFlowStep(ControlFlow::Nodes::ExprNode e1, ControlFlow::Nodes::ExprNode e2) {
e1.getExpr() = LocalFlow::getALastEvalNode(e2.getExpr())
or
exists(Ssa::Definition def, AssignableDefinition adef |
LocalFlow::defAssigns(adef, _, _, e1) and
def.getAnUltimateDefinition().(Ssa::ExplicitDefinition).getADefinition() = adef and
exists(def.getAReadAtNode(e2))
)
}
private module CaptureInput implements Shared::InputSig<Location, BasicBlocks::BasicBlock> {
private import csharp as Cs
private import semmle.code.csharp.controlflow.ControlFlowGraph as Cfg
private import TaintTrackingPrivate as TaintTrackingPrivate
Callable basicBlockGetEnclosingCallable(BasicBlocks::BasicBlock bb) {
result = bb.getCallable()
}
private predicate thisAccess(ControlFlow::Node cfn, InstanceCallable c) {
ThisFlow::thisAccessExpr(cfn.getAstNode()) and
cfn.getEnclosingCallable().getEnclosingCallable*() = c
}
private predicate capturedThisAccess(ControlFlow::Node cfn, InstanceCallable c) {
thisAccess(cfn, c) and
cfn.getEnclosingCallable() != c
}
private newtype TCapturedVariable =
TCapturedLocalScopeVariable(LocalScopeVariable v) {
v.isCaptured() and not v.(Parameter).getCallable() instanceof PrimaryConstructor
} or
TCapturedThis(Callable c) { capturedThisAccess(_, c) }
/** A captured local scope variable. Includes captured `this` variables. */
class CapturedVariable extends TCapturedVariable {
LocalScopeVariable asLocalScopeVariable() { this = TCapturedLocalScopeVariable(result) }
Callable asThis() { this = TCapturedThis(result) }
Callable getCallable() {
result = this.asLocalScopeVariable().getCallable()
or
result = this.asThis()
}
Type getType() {
result = this.asLocalScopeVariable().getType()
or
result = this.asThis().getDeclaringType()
}
string toString() {
result = this.asLocalScopeVariable().toString()
or
result = "this in " + this.asThis()
}
Location getLocation() {
result = this.asLocalScopeVariable().getLocation()
or
result = this.asThis().getLocation()
}
}
abstract class CapturedParameter extends CapturedVariable {
abstract ParameterNodeImpl getParameterNode();
}
private class CapturedExplicitParameter extends CapturedParameter, TCapturedLocalScopeVariable {
private Parameter p;
CapturedExplicitParameter() { p = this.asLocalScopeVariable() }
override ExplicitParameterNode getParameterNode() { result.asParameter() = p }
}
private class CapturedThisParameter extends CapturedParameter, TCapturedThis {
override InstanceParameterNode getParameterNode() {
result = TInstanceParameterNode(this.asThis(), _)
}
}
class Expr extends ControlFlow::Node {
predicate hasCfgNode(BasicBlocks::BasicBlock bb, int i) { this = bb.getNode(i) }
}
class VariableWrite extends Expr {
CapturedVariable v;
AssignableDefinition def;
VariableWrite() {
def.getTarget() = v.asLocalScopeVariable() and
this = def.getExpr().getAControlFlowNode()
}
ControlFlow::Node getRhs() { LocalFlow::defAssigns(def, this, _, result) }
CapturedVariable getVariable() { result = v }
}
class VariableRead extends Expr {
CapturedVariable v;
VariableRead() {
this.getAstNode().(AssignableRead).getTarget() = v.asLocalScopeVariable()
or
thisAccess(this, v.asThis())
}
CapturedVariable getVariable() { result = v }
}
class ClosureExpr extends Expr {
Callable c;
ClosureExpr() { lambdaCreationExpr(this.getAstNode(), c) }
predicate hasBody(Callable body) { body = c }
predicate hasAliasedAccess(Expr f) {
closureFlowStep+(this, f) and not closureFlowStep(f, _)
or
isLocalFunctionCallReceiver(_, f.getAstNode(), c)
}
}
class Callable extends Cs::Callable {
predicate isConstructor() { this instanceof Constructor }
}
}
class CapturedVariable = CaptureInput::CapturedVariable;
class ClosureExpr = CaptureInput::ClosureExpr;
module Flow = Shared::Flow<Location, BasicBlocks::Cfg, CaptureInput>;
private Flow::ClosureNode asClosureNode(Node n) {
result = n.(CaptureNode).getSynthesizedCaptureNode()
or
result.(Flow::ExprNode).getExpr() =
[
n.(ExprNode).getControlFlowNode(),
n.(LocalFunctionCreationPreNode).getUnderlyingControlFlowNode()
]
or
result.(Flow::VariableWriteSourceNode).getVariableWrite().getRhs() =
n.(ExprNode).getControlFlowNode()
or
result.(Flow::ExprPostUpdateNode).getExpr() =
[
n.(PostUpdateNode).getPreUpdateNode().(ExprNode).getControlFlowNode(),
n.(LocalFunctionCreationPostUpdateNode).getUnderlyingControlFlowNode()
]
or
result.(Flow::ParameterNode).getParameter().getParameterNode() = n
or
result.(Flow::ThisParameterNode).getCallable() = n.(DelegateSelfReferenceNode).getCallable()
}
CapturedVariable getCapturedVariableContent(CapturedVariableContent c) {
c = TCapturedVariableContent(result)
}
predicate storeStep(Node node1, CapturedVariableContent c, Node node2) {
Flow::storeStep(asClosureNode(node1), getCapturedVariableContent(c), asClosureNode(node2))
}
predicate readStep(Node node1, CapturedVariableContent c, Node node2) {
Flow::readStep(asClosureNode(node1), getCapturedVariableContent(c), asClosureNode(node2))
}
predicate valueStep(Node node1, Node node2) {
Flow::localFlowStep(asClosureNode(node1), asClosureNode(node2))
}
predicate clearsContent(Node node, CapturedVariableContent c) {
Flow::clearsContent(asClosureNode(node), getCapturedVariableContent(c))
}
class CapturedSsaSourceVariable extends Ssa::SourceVariable {
CapturedSsaSourceVariable() {
this.getAssignable() = any(CapturedVariable v).asLocalScopeVariable()
}
}
private predicate flowInsensitiveWriteStep(
ExprNode node1, FlowInsensitiveCapturedVariableNode node2, LocalScopeVariable v
) {
exists(AssignableDefinition def |
def.getSource() = node1.getExpr() and
def.getTarget() = v and
node2.getVariable() = v
)
}
private predicate flowInsensitiveReadStep(
FlowInsensitiveCapturedVariableNode node1, ExprNode node2, LocalScopeVariable v
) {
node1.getVariable() = v and
node2.getExpr().(VariableRead).getTarget() = v
}
/**
* Holds if there is control-flow insensitive data-flow from `node1` to `node2`
* involving a captured variable. Only used in lambda flow.
*/
predicate flowInsensitiveStep(Node node1, Node node2) {
exists(LocalScopeVariable v |
flowInsensitiveWriteStep(node1, node2, v) and
flowInsensitiveReadStep(_, _, v)
or
flowInsensitiveReadStep(node1, node2, v) and
flowInsensitiveWriteStep(_, _, v)
)
}
}
/** Provides logic related to SSA. */
module SsaFlow {
private module Impl = SsaImpl::DataFlowIntegration;
Impl::Node asNode(Node n) {
n = TSsaNode(result)
or
result.(Impl::ExprNode).getExpr() = n.(ExprNode).getControlFlowNode()
or
result.(Impl::ExprPostUpdateNode).getExpr() =
n.(PostUpdateNode).getPreUpdateNode().(ExprNode).getControlFlowNode()
or
result.(Impl::WriteDefSourceNode).getDefinition() = n.(ExplicitParameterNode).getSsaDefinition()
}
predicate localFlowStep(Ssa::SourceVariable v, Node nodeFrom, Node nodeTo, boolean isUseStep) {
Impl::localFlowStep(v, asNode(nodeFrom), asNode(nodeTo), isUseStep)
}
predicate localMustFlowStep(Ssa::SourceVariable v, Node nodeFrom, Node nodeTo) {
Impl::localMustFlowStep(v, asNode(nodeFrom), asNode(nodeTo))
}
}
/** Provides predicates related to local data flow. */
module LocalFlow {
predicate localExprStep(Expr e1, Expr e2) {
e1 = e2.(ParenthesizedExpr).getExpr()
or
e1 = e2.(NullCoalescingExpr).getAnOperand()
or
e1 = e2.(SuppressNullableWarningExpr).getExpr()
or
e2 =
any(ConditionalExpr ce |
e1 = ce.getThen() or
e1 = ce.getElse()
)
or
e1 = e2.(Cast).getExpr()
or
// An `=` expression, where the result of the expression is used
e2 =
any(AssignExpr ae |
ae.getParent() = any(ControlFlowElement cfe | not cfe instanceof ExprStmt) and
e1 = ae.getRValue()
)
or
e1 = e2.(ObjectCreation).getInitializer()
or
e1 = e2.(ArrayCreation).getInitializer()
or
e1 = e2.(SwitchExpr).getACase().getBody()
or
e1 = e2.(CheckedExpr).getExpr()
or
e1 = e2.(UncheckedExpr).getExpr()
or
e1 = e2.(CollectionExpression).getAnElement() and
e1 instanceof SpreadElementExpr
or
e1 = e2.(SpreadElementExpr).getExpr()
or
exists(WithExpr we |
e1 = we.getExpr() and
e2 = we.getInitializer()
or
e1 = we.getInitializer() and
e2 = we
)
or
exists(AssignExpr ae | ae.getLValue().(TupleExpr) = e2 and ae.getRValue() = e1)
or
exists(ControlFlowElement cfe | cfe = e2.(TupleExpr).(PatternExpr).getPatternMatch() |
cfe.(IsExpr).getExpr() = e1
or
exists(Switch sw | sw.getACase() = cfe and sw.getExpr() = e1)
)
}
predicate defAssigns(
AssignableDefinition def, ControlFlow::Node cfnDef, Expr value, ControlFlow::Node valueCfn
) {
def.getSource() = value and
valueCfn = value.getControlFlowNode() and
cfnDef = def.getExpr().getAControlFlowNode()
}
private predicate defAssigns(ExprNode value, AssignableDefinitionNode defNode) {
exists(ControlFlow::Node cfn, AssignableDefinition def, ControlFlow::Node cfnDef |
defAssigns(def, cfnDef, value.getExpr(), _) and
cfn = value.getControlFlowNode() and
defNode = TAssignableDefinitionNode(def, cfnDef)
)
}
/**
* Holds if the source variable `v` is an instance field.
*/
predicate isInstanceField(Ssa::SourceVariables::FieldOrPropSourceVariable v) {
not v.getAssignable().(Modifiable).isStatic()
}
predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
localExprStep(nodeFrom.asExpr(), nodeTo.asExpr())
or
defAssigns(nodeFrom, nodeTo)
or
ThisFlow::adjacentThisRefs(nodeFrom, nodeTo) and
nodeFrom != nodeTo
or
ThisFlow::adjacentThisRefs(nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo)
or
exists(AssignableDefinition def, ControlFlow::Node cfn, Ssa::ExplicitDefinition ssaDef |
ssaDef.getADefinition() = def and
ssaDef.getControlFlowNode() = cfn and
nodeFrom = TAssignableDefinitionNode(def, cfn) and
nodeTo.(SsaDefinitionNode).getDefinition() = ssaDef
)
}
/**
* Holds if node `n` should not be included in the exposed local data/taint
* flow relations. This is the case for nodes that are only relevant for
* inter-procedurality or field-sensitivity.
*/
predicate excludeFromExposedRelations(Node n) {
n instanceof FlowSummaryNode or
n instanceof CaptureNode
}
/**
* Gets a node that may execute last in `e`, and which, when it executes last,
* will be the value of `e`.
*/
Expr getALastEvalNode(Expr e) {
localExprStep(result, e) and
(
e instanceof ConditionalExpr or
e instanceof Cast or
e instanceof NullCoalescingExpr or
e instanceof SwitchExpr or
e instanceof SuppressNullableWarningExpr or
e instanceof AssignExpr
)
}
/**
* Holds if a reverse local flow step should be added from the post-update node
* for `e` to the post-update node for the result.
*
* This is needed to allow for side-effects on compound expressions to propagate
* to sub components. For example, in
*
* ```csharp
* m(b ? x : y)
* ```
*
* we add a reverse flow step from `[post] b ? x : y` to `[post] x` and to
* `[post] y`, in order for the side-effect of `m` to reach both `x` and `y`.
*/
Expr getPostUpdateReverseStep(Expr e) { result = getALastEvalNode(e) }
/**
* Holds if the value of `node2` is given by `node1`.
*/
predicate localMustFlowStep(Node node1, Node node2) {
exists(DataFlowCallable c, Expr e |
node1.(InstanceParameterNode).getEnclosingCallableImpl() = c and
node2.getControlFlowNode() = c.getAControlFlowNode() and
node2.asExpr() = e
|
e instanceof ThisAccess or e instanceof BaseAccess
)
or
defAssigns(node1, node2)
or
localExprStep(node1.asExpr(), node2.asExpr()) and
(
node2.asExpr() instanceof Cast or
node2.asExpr() instanceof AssignExpr
)
or
SsaFlow::localMustFlowStep(_, node1, node2)
or
node2 = node1.(LocalFunctionCreationNode).getAnAccess(true)
or
FlowSummaryImpl::Private::Steps::summaryLocalMustFlowStep(node1
.(FlowSummaryNode)
.getSummaryNode(), node2.(FlowSummaryNode).getSummaryNode())
}
}
predicate localMustFlowStep = LocalFlow::localMustFlowStep/2;
/**
* This is the local flow predicate that is used as a building block in global
* data flow. It excludes SSA flow through instance fields, as flow through fields
* is handled by the global data-flow library, but includes various other steps
* that are only relevant for global flow.
*/
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo, string model) {
(
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
exists(Ssa::SourceVariable v, boolean isUseStep |
SsaFlow::localFlowStep(v, nodeFrom, nodeTo, isUseStep) and
not LocalFlow::isInstanceField(v) and
not v instanceof VariableCapture::CapturedSsaSourceVariable
|
isUseStep = false
or
isUseStep = true and
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
)
or
nodeTo.(ObjectCreationNode).getPreUpdateNode() = nodeFrom.(ObjectInitializerNode)
or
VariableCapture::valueStep(nodeFrom, nodeTo)
or
nodeTo = nodeFrom.(LocalFunctionCreationNode).getAnAccess(true)
or
nodeTo.(PostUpdateNode).getPreUpdateNode().asExpr() =
LocalFlow::getPostUpdateReverseStep(nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr())
) and
model = ""
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
nodeTo.(FlowSummaryNode).getSummaryNode(), true, model)
}
/**
* Holds if `arg` is a `params` argument of `c`, for parameter `p`, and `arg` will
* be wrapped in an collection by the C# compiler.
*/
private predicate isParamsArg(Call c, Expr arg, Parameter p) {
exists(Callable target, int numArgs |
target = c.getTarget() and
p = target.getAParameter() and
p.isParams() and
numArgs = c.getNumberOfArguments() and
arg = c.getArgumentForParameter(p)
|
numArgs > target.getNumberOfParameters()
or
not arg.getType().isImplicitlyConvertibleTo(p.getType())
)
}
/** An argument of a C# call (including qualifier arguments). */
private class Argument extends Expr {
private Expr call;
private ArgumentPosition arg;
Argument() {
call =
any(DispatchCall dc |
this = dc.getArgument(arg.getPosition()) and
not isParamsArg(_, this, _)
or
this = dc.getQualifier() and
arg.isQualifier() and
not dc.getAStaticTarget().(Modifiable).isStatic()
).getCall()
or
this = call.(DelegateLikeCall).getArgument(arg.getPosition())
}
/**
* Holds if this expression is the `i`th argument of `c`.
*
* Qualifier arguments have index `-1`.
*/
predicate isArgumentOf(Expr c, ArgumentPosition pos) { c = call and pos = arg }
}
/**
* Holds if there is an assignment of `src` to field or property `c` of `q`.
*
* `postUpdate` indicates whether the store targets a post-update node.
*/
private predicate fieldOrPropertyStore(ContentSet c, Expr src, Expr q, boolean postUpdate) {
exists(FieldOrProperty f |
c = f.getContentSet() and
(
f.isFieldLike() and
f instanceof InstanceFieldOrProperty
or
exists(
FlowSummaryImpl::Private::SummarizedCallableImpl sc,
FlowSummaryImpl::Private::SummaryComponentStack input, ContentSet readSet
|
sc.propagatesFlow(input, _, _, _, _, _) and
input.contains(FlowSummaryImpl::Private::SummaryComponent::content(readSet)) and
c.getAStoreContent() = readSet.getAReadContent()
)
)
|
// Direct assignment, `q.f = src`
exists(FieldOrPropertyAccess fa, AssignableDefinition def |
def.getTargetAccess() = fa and
f = fa.getTarget() and
src = def.getSource() and
q = fa.getQualifier() and
postUpdate = true
)
or
// `with` expression initializer, `x with { f = src }`
exists(WithExpr we, MemberInitializer mi |
q = we and
mi = we.getInitializer().getAMemberInitializer() and
f = mi.getInitializedMember() and
src = mi.getRValue() and
postUpdate = false
)
or
// Object initializer, `new C() { f = src }`
exists(MemberInitializer mi |
mi = q.(ObjectInitializer).getAMemberInitializer() and
q.getParent() instanceof ObjectCreation and
f = mi.getInitializedMember() and
src = mi.getRValue() and
postUpdate = false
)
or
// Tuple element, `(..., src, ...)` `f` is `ItemX` of tuple `q`
exists(TupleExpr te, int i |
te = q and
src = te.getArgument(i) and
te.isConstruction() and
f = q.getType().(TupleType).getElement(i) and
postUpdate = false
)
)
or
// A write to a dynamic property
exists(DynamicMemberAccess dma, AssignableDefinition def, DynamicProperty dp |
def.getTargetAccess() = dma and
dp.getAnAccess() = dma and
c.isDynamicProperty(dp) and
src = def.getSource() and
q = dma.getQualifier() and
postUpdate = true
)
}
/**
* Holds if `e2` is an expression that reads field or property `c` from
* expression `e1`.
*/
private predicate fieldOrPropertyRead(Expr e1, ContentSet c, FieldOrPropertyRead e2) {
e1 = e2.getQualifier() and
c = e2.getTarget().(FieldOrProperty).getContentSet()
}
/**
* Holds if `e2` is an expression that reads the dynamic property `c` from
* expression `e1`.
*/
private predicate dynamicPropertyRead(Expr e1, ContentSet c, DynamicMemberRead e2) {
exists(DynamicPropertyContent dpc |
e1 = e2.getQualifier() and
dpc.getAnAccess() = e2 and
c.isDynamicProperty(dpc.getName())
)
}
/**
* Holds if `ce` is a collection expression that adds `src` to the collection `ce`.
*/
private predicate collectionStore(Expr src, CollectionExpression ce) {
// Collection expression, `[1, src, 3]`
src = ce.getAnElement() and
not src instanceof SpreadElementExpr
}
/**
* Holds if there is an expression that adds `src` to array `a`.
*
* `postUpdate` indicates whether the store targets a post-update node.
*/
private predicate arrayStore(Expr src, Expr a, boolean postUpdate) {
// Direct assignment, `a[i] = src`
exists(AssignableDefinition def |
a = def.getTargetAccess().(ArrayWrite).getQualifier() and
src = def.getSource() and
postUpdate = true
)
or
// Array initializer, `new [] { src }`
src = a.(ArrayInitializer).getAnElement() and
postUpdate = false
or
// Member initializer, `new C { Array = { [i] = src } }`
exists(MemberInitializer mi |
mi = a.(ObjectInitializer).getAMemberInitializer() and
mi.getLValue() instanceof ArrayAccess and
mi.getRValue() = src and
postUpdate = false
)
}
/**
* Holds if `e2` is an expression that reads an array element from
* from expresion `e1`.
*/
private predicate arrayRead(Expr e1, ArrayRead e2) { e1 = e2.getQualifier() }
private class RelevantGvnType extends Gvn::GvnType {
RelevantGvnType() { this = any(NodeImpl n).getDataFlowType().asGvnType() }
}
/** A GVN type that is either a `DataFlowType` or unifiable with a `DataFlowType`. */
private class DataFlowTypeOrUnifiable extends Gvn::GvnType {
pragma[nomagic]
DataFlowTypeOrUnifiable() {
this instanceof RelevantGvnType or
Gvn::unifiable(any(RelevantGvnType t), this)
}
}
pragma[noinline]
private TypeParameter getATypeParameterSubType(DataFlowTypeOrUnifiable t) {
not t instanceof Gvn::TypeParameterGvnType and
exists(Type t0 | t = Gvn::getGlobalValueNumber(t0) | implicitConversionRestricted(result, t0))
}
pragma[noinline]
private TypeParameter getATypeParameterSubTypeRestricted(RelevantGvnType t) {
result = getATypeParameterSubType(t)
}
pragma[noinline]
private Gvn::GvnType getANonTypeParameterSubType(DataFlowTypeOrUnifiable t) {
not t instanceof Gvn::TypeParameterGvnType and
not result instanceof Gvn::TypeParameterGvnType and
exists(Type t1, Type t2 |
implicitConversionRestricted(t1, t2) and
result = Gvn::getGlobalValueNumber(t1) and
t = Gvn::getGlobalValueNumber(t2)
)
}
pragma[noinline]
private Gvn::GvnType getANonTypeParameterSubTypeRestricted(RelevantGvnType t) {
result = getANonTypeParameterSubType(t)
}
/** A callable with an implicit `this` parameter. */
private class InstanceCallable extends Callable {
private Location l;
InstanceCallable() {
not this.(Modifiable).isStatic() and
// local functions and delegate capture `this` and should therefore
// not have a `this` parameter
not this instanceof LocalFunction and
not this instanceof AnonymousFunctionExpr
}
Location getARelevantLocation() { result = l }
}
/**
* A callable which is either itself defined in source or which is the target
* of some call in source, and therefore ought to have dataflow nodes created.
*
* Note that for library methods these are always unbound declarations, since
* generic instantiations never have dataflow nodes constructed.
*/
private class CallableUsedInSource extends Callable {
CallableUsedInSource() {
// Should generate nodes even for abstract methods declared in source
this.fromSource()
or
// Should generate nodes even for synthetic methods derived from source
this.hasBody()
or
exists(Callable target |
exists(Call c |
// Note that getADynamicTarget does not always include getTarget.
target = c.getTarget()
or
// Note that getARuntimeTarget cannot be used here, because the
// DelegateLikeCall case depends on lambda-flow, which in turn
// uses the dataflow library; hence this would introduce recursion
// into the definition of data-flow nodes.
exists(DispatchCall dc | c = dc.getCall() | target = dc.getADynamicTarget())
)
or
target = any(CallableAccess ca).getTarget()
|
this = target.getUnboundDeclaration()
)
}
}
/**
* A field or property which is either itself defined in source or which is the target
* of some access in source, and therefore ought to have dataflow nodes created.
*/
private class FieldOrPropertyUsedInSource extends FieldOrProperty {
FieldOrPropertyUsedInSource() {
this.fromSource()
or
this.getAnAccess().fromSource()
}
}
/**
* Hold if `e` has a type that allows for it to have a post-update node.
*/
predicate exprMayHavePostUpdateNode(Expr e) {
exists(Type t | t = e.stripCasts().getType() |
t instanceof RefType and
not t instanceof NullType
or
t = any(TypeParameter tp | not tp.isValueType())
or
t instanceof Struct
)
}
/** A collection of cached types and predicates to be evaluated in the same stage. */
cached
private module Cached {
private import TaintTrackingPrivate as TaintTrackingPrivate
// Add artificial dependencies to enforce all cached predicates are evaluated
// in the "DataFlowImplCommon stage"
cached
predicate forceCaching() {
TaintTrackingPrivate::forceCachingInSameStage() or
exists(any(NodeImpl n).getTypeImpl()) or
exists(any(NodeImpl n).getControlFlowNodeImpl()) or
exists(any(NodeImpl n).getLocationImpl()) or
exists(any(NodeImpl n).toStringImpl())
}
cached
newtype TNode =
TExprNode(ControlFlow::Nodes::ElementNode cfn) { cfn.getAstNode() instanceof Expr } or
TSsaNode(SsaImpl::DataFlowIntegration::SsaNode node) or
TAssignableDefinitionNode(AssignableDefinition def, ControlFlow::Node cfn) {
cfn = def.getExpr().getAControlFlowNode()
} or
TExplicitParameterNode(Parameter p, DataFlowCallable c) {
p = c.asCallable(_).(CallableUsedInSource).getAParameter()
} or
TInstanceParameterNode(InstanceCallable c, Location l) {
c = any(DataFlowCallable dfc).asCallable(l) and
c instanceof CallableUsedInSource and
l = c.getARelevantLocation()
} or
TDelegateSelfReferenceNode(Callable c) { lambdaCreationExpr(_, c) } or
TLocalFunctionCreationNode(ControlFlow::Nodes::ElementNode cfn, Boolean isPostUpdate) {
cfn.getAstNode() instanceof LocalFunctionStmt
} or
TYieldReturnNode(ControlFlow::Nodes::ElementNode cfn) {
any(Callable c).canYieldReturn(cfn.getAstNode())
} or
TAsyncReturnNode(ControlFlow::Nodes::ElementNode cfn) {
any(Callable c | c.(Modifiable).isAsync()).canReturn(cfn.getAstNode())
} or
TMallocNode(ControlFlow::Nodes::ElementNode cfn) { cfn.getAstNode() instanceof ObjectCreation } or
TObjectInitializerNode(ControlFlow::Nodes::ElementNode cfn) {
cfn.getAstNode().(ObjectCreation).hasInitializer()
} or
TExprPostUpdateNode(ControlFlow::Nodes::ExprNode cfn) {
(
cfn.getExpr() instanceof Argument
or
cfn.getExpr() =
LocalFlow::getPostUpdateReverseStep(any(SourcePostUpdateNode p)
.getPreUpdateNode()
.asExpr())
) and
exprMayHavePostUpdateNode(cfn.getExpr())
or
exists(Expr e | e = cfn.getExpr() |
fieldOrPropertyStore(_, _, e, true)
or
arrayStore(_, e, true)
or
// needed for reverse stores; e.g. `x.f1.f2 = y` induces
// a store step of `f1` into `x`
exists(TExprPostUpdateNode upd, Expr read |
upd = TExprPostUpdateNode(read.getAControlFlowNode())
|
fieldOrPropertyRead(e, _, read)
or
dynamicPropertyRead(e, _, read)
or
arrayRead(e, read)
)
)
or
lambdaCallExpr(_, _, cfn)
} or
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) {
sn.getSummarizedCallable() instanceof CallableUsedInSource
} or
TParamsArgumentNode(ControlFlow::Node callCfn) {
callCfn = any(Call c | isParamsArg(c, _, _)).getAControlFlowNode()
} or
TFlowInsensitiveFieldNode(FieldOrPropertyUsedInSource f) { f.isFieldLike() } or
TFlowInsensitiveCapturedVariableNode(LocalScopeVariable v) { v.isCaptured() } or
TInstanceParameterAccessNode(ControlFlow::Node cfn, Boolean isPostUpdate) {
cfn = getAPrimaryConstructorParameterCfn(_)
} or
TPrimaryConstructorThisAccessNode(Parameter p, Boolean isPostUpdate, DataFlowCallable c) {
p = c.asCallable(_).(PrimaryConstructor).getAParameter()
} or
TCaptureNode(VariableCapture::Flow::SynthesizedCaptureNode cn)
/**
* Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
* (intra-procedural) step.
*/
cached
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
SsaFlow::localFlowStep(_, nodeFrom, nodeTo, _)
or
// Simple flow through library code is included in the exposed local
// step relation, even though flow is technically inter-procedural
FlowSummaryImpl::Private::Steps::summaryThroughStepValue(nodeFrom, nodeTo, _)
}
cached
newtype TContent =
TFieldContent(Field f) { f.isUnboundDeclaration() } or
TPropertyContent(Property p) { p.isUnboundDeclaration() } or
TDynamicPropertyContent(DynamicProperty dp) or
TElementContent() or
TSyntheticFieldContent(SyntheticField f) or
TPrimaryConstructorParameterContent(Parameter p) {
p.getCallable() instanceof PrimaryConstructor
} or
TCapturedVariableContent(VariableCapture::CapturedVariable v) or
TDelegateCallArgumentContent(int i) {
i = [0 .. max(any(DelegateLikeCall dc).getNumberOfArguments()) - 1]
} or
TDelegateCallReturnContent()
cached
newtype TContentSet =
TSingletonContent(Content c) { not c instanceof PropertyContent } or
TPropertyContentSet(Property p) { p.isUnboundDeclaration() } or
TDynamicPropertyContentSet(DynamicProperty dp)
cached
newtype TContentApprox =
TFieldApproxContent(string firstChar) { firstChar = approximateFieldContent(_) } or
TPropertyApproxContent(string firstChar) { firstChar = approximatePropertyContent(_) } or
TDynamicPropertyApproxContent(string firstChar) {
firstChar = approximateDynamicPropertyContent(_)
} or
TElementApproxContent() or
TSyntheticFieldApproxContent() or
TPrimaryConstructorParameterApproxContent(string firstChar) {
firstChar = approximatePrimaryConstructorParameterContent(_)
} or
TCapturedVariableContentApprox(VariableCapture::CapturedVariable v) or
TDelegateCallArgumentApproxContent() or
TDelegateCallReturnApproxContent()
pragma[nomagic]
private predicate commonSubTypeGeneral(DataFlowTypeOrUnifiable t1, RelevantGvnType t2) {
not t1 instanceof Gvn::TypeParameterGvnType and
t1 = t2
or
getATypeParameterSubType(t1) = getATypeParameterSubTypeRestricted(t2)
or
getANonTypeParameterSubType(t1) = getANonTypeParameterSubTypeRestricted(t2)
}
/**
* Holds if GVNs `t1` and `t2` may have a common sub type. Neither `t1` nor
* `t2` are allowed to be type parameters.
*/
cached
predicate commonSubType(RelevantGvnType t1, RelevantGvnType t2) { commonSubTypeGeneral(t1, t2) }
cached
predicate commonSubTypeUnifiableLeft(RelevantGvnType t1, RelevantGvnType t2) {
exists(Gvn::GvnType t |
Gvn::unifiable(t1, t) and
commonSubTypeGeneral(t, t2)
)
}
cached
newtype TDataFlowType =
TGvnDataFlowType(Gvn::GvnType t) or
TDelegateDataFlowType(Callable lambda) { lambdaCreationExpr(_, lambda) }
}
import Cached
/** Holds if `n` should be hidden from path explanations. */
predicate nodeIsHidden(Node n) {
n instanceof SsaNode
or
exists(Parameter p | p = n.(ParameterNode).getParameter() | not p.fromSource())
or
n =
TInstanceParameterNode(any(Callable c |
not c.fromSource() or c instanceof FlowSummary::SummarizedCallable
), _)
or
n instanceof YieldReturnNode
or
n instanceof AsyncReturnNode
or
n instanceof MallocNode
or
n instanceof FlowSummaryNode
or
n instanceof ParamsArgumentNode
or
n.asExpr() = any(WithExpr we).getInitializer()
or
n instanceof FlowInsensitiveFieldNode
or
n instanceof InstanceParameterAccessNode
or
n instanceof PrimaryConstructorThisAccessNode
or
n = any(AssignableDefinitionNode def | not exists(def.getDefinition().getTargetAccess()))
or
n instanceof DelegateSelfReferenceNode
or
n instanceof CaptureNode
}
/** An SSA node. */
class SsaNode extends NodeImpl, TSsaNode {
SsaImpl::DataFlowIntegration::SsaNode node;
SsaNode() { this = TSsaNode(node) }
override DataFlowCallable getEnclosingCallableImpl() {
result.getAControlFlowNode().getBasicBlock() = node.getBasicBlock()
}
override Type getTypeImpl() { result = node.getSourceVariable().getType() }
override ControlFlow::Node getControlFlowNodeImpl() { none() }
override Location getLocationImpl() { result = node.getLocation() }
override string toStringImpl() { result = node.toString() }
}
/** An SSA definition, viewed as a node in a data flow graph. */
class SsaDefinitionNode extends SsaNode {
override SsaImpl::DataFlowIntegration::SsaDefinitionNode node;
Ssa::Definition getDefinition() { result = node.getDefinition() }
override ControlFlow::Node getControlFlowNodeImpl() {
result = this.getDefinition().getControlFlowNode()
}
}
/** A definition, viewed as a node in a data flow graph. */
class AssignableDefinitionNodeImpl extends NodeImpl, TAssignableDefinitionNode {
private AssignableDefinition def;
private ControlFlow::Node cfn_;
AssignableDefinitionNodeImpl() { this = TAssignableDefinitionNode(def, cfn_) }
/** Gets the underlying definition. */
AssignableDefinition getDefinition() { result = def }
/** Gets the underlying definition, at control flow node `cfn`, if any. */
AssignableDefinition getDefinitionAtNode(ControlFlow::Node cfn) {
result = def and
cfn = cfn_
}
override DataFlowCallable getEnclosingCallableImpl() { result.getAControlFlowNode() = cfn_ }
override Type getTypeImpl() { result = def.getTarget().getType() }
override ControlFlow::Node getControlFlowNodeImpl() { result = cfn_ }
override Location getLocationImpl() {
result = def.getTargetAccess().getLocation()
or
not exists(def.getTargetAccess()) and result = def.getLocation()
}
override string toStringImpl() {
result = def.getTargetAccess().toString()
or
not exists(def.getTargetAccess()) and result = def.toString()
}
}
abstract class ParameterNodeImpl extends NodeImpl {
abstract predicate isParameterOf(DataFlowCallable c, ParameterPosition pos);
}
private module NearestLocationInputParamAfterCallable implements NearestLocationInputSig {
class C = Parameter;
predicate relevantLocations(Parameter p, Location l1, Location l2) {
exists(DataFlowCallable c |
c.asCallable(l1).getParameter(_) = p and
l2 = [p.getLocation(), getASourceLocation(p)]
)
}
}
private module ParameterNodes {
pragma[nomagic]
private predicate ssaParamDef(Ssa::ImplicitParameterDefinition ssaDef, Parameter p, Location l) {
p = ssaDef.getParameter() and
l = ssaDef.getLocation()
}
private module NearestLocationInputParamBeforeCallable implements NearestLocationInputSig {
class C = Parameter;
predicate relevantLocations(Parameter p, Location l1, Location l2) {
exists(DataFlowCallable c |
c.asCallable(l2).getParameter(_) = p and
l1 = [p.getLocation(), getASourceLocation(p)]
)
}
}
/**
* The value of an explicit parameter at function entry, viewed as a node in a data
* flow graph.
*/
class ExplicitParameterNode extends ParameterNodeImpl, TExplicitParameterNode {
private Parameter parameter;
private DataFlowCallable callable;
ExplicitParameterNode() { this = TExplicitParameterNode(parameter, callable) }
Parameter getParameter() { result = parameter }
pragma[nomagic]
private Location getNearestParameterLocation() {
exists(Location cloc | exists(callable.asCallable(cloc)) |
// typical scenario: parameter is syntactically after the callable
NearestLocation<NearestLocationInputParamAfterCallable>::nearestLocation(parameter, cloc,
result)
or
// atypical scenario: parameter is syntactically before the callable, for example
// `int this[int x] { get => x }`, where the parameter `x` is syntactically
// before the the callable `get_Item`
NearestLocation<NearestLocationInputParamBeforeCallable>::nearestLocation(parameter, result,
cloc)
)
}
pragma[nomagic]
private Location getParameterLocation(Parameter p) {
p = parameter and
(
result = this.getNearestParameterLocation()
or
not exists(this.getNearestParameterLocation()) and
result = parameter.getLocation()
)
}
/** Gets the SSA definition corresponding to this parameter, if any. */
Ssa::ImplicitParameterDefinition getSsaDefinition() {
exists(Parameter p, Location l |
l = this.getParameterLocation(p) and
ssaParamDef(result, p, l)
)
}
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
c = callable and
c.asCallable(_).getParameter(pos.getPosition()) = parameter
}
override DataFlowCallable getEnclosingCallableImpl() { this.isParameterOf(result, _) }
override Type getTypeImpl() { result = parameter.getType() }
override ControlFlow::Node getControlFlowNodeImpl() { none() }
override Location getLocationImpl() { result = this.getParameterLocation(_) }
override string toStringImpl() { result = parameter.toString() }
}
/** An implicit instance (`this`) parameter. */
class InstanceParameterNode extends ParameterNodeImpl, TInstanceParameterNode {
private Callable callable;
private Location location;
InstanceParameterNode() { this = TInstanceParameterNode(callable, location) }
/** Gets the callable containing this implicit instance parameter. */
Callable getCallable(Location loc) { result = callable and location = loc }
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
callable = c.asCallable(location) and pos.isThisParameter()
}
override DataFlowCallable getEnclosingCallableImpl() { result.asCallable(location) = callable }
override Type getTypeImpl() { result = callable.getDeclaringType() }
override ControlFlow::Node getControlFlowNodeImpl() { none() }
override Location getLocationImpl() { result = location }
override string toStringImpl() { result = "this" }
}
/**
* The value of a delegate itself at function entry, viewed as a node in a data
* flow graph.
*
* This is used for improving lambda dispatch, and will eventually also be
* used for tracking flow through captured variables.
*/
class DelegateSelfReferenceNode extends ParameterNodeImpl, TDelegateSelfReferenceNode {
private Callable callable;
DelegateSelfReferenceNode() { this = TDelegateSelfReferenceNode(callable) }
final Callable getCallable() { result = callable }
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
callable = c.asCallable(_) and pos.isDelegateSelf()
}
override ControlFlow::Node getControlFlowNodeImpl() { none() }
override DataFlowCallable getEnclosingCallableImpl() { result.asCallable(_) = callable }
override Location getLocationImpl() { result = callable.getLocation() }
override Type getTypeImpl() { none() }
override DataFlowType getDataFlowType() { callable = result.asDelegate() }
override string toStringImpl() { result = "delegate self in " + callable }
}
/** An implicit entry definition for a captured variable. */
class SsaCapturedEntryDefinition extends Ssa::ImplicitEntryDefinition {
private LocalScopeVariable v;
SsaCapturedEntryDefinition() { this.getSourceVariable().getAssignable() = v }
LocalScopeVariable getVariable() { result = v }
}
/** A parameter for a library callable with a flow summary. */
class SummaryParameterNode extends ParameterNodeImpl, FlowSummaryNode {
private ParameterPosition pos_;
SummaryParameterNode() {
FlowSummaryImpl::Private::summaryParameterNode(this.getSummaryNode(), pos_)
}
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
this.getSummarizedCallable() = c.asSummarizedCallable() and pos = pos_
}
}
}
import ParameterNodes
/** A data-flow node that represents a call argument. */
class ArgumentNode extends Node instanceof ArgumentNodeImpl {
/** Holds if this argument occurs at the given position in the given call. */
final predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
super.argumentOf(call, pos)
}
/** Gets the call in which this node is an argument. */
DataFlowCall getCall() { this.argumentOf(result, _) }
}
abstract private class ArgumentNodeImpl extends Node {
abstract predicate argumentOf(DataFlowCall call, ArgumentPosition pos);
}
private module ArgumentNodes {
/** A data-flow node that represents an explicit call argument. */
class ExplicitArgumentNode extends ArgumentNodeImpl {
ExplicitArgumentNode() { this.asExpr() instanceof Argument }
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
exists(Expr c, Argument arg |
arg = this.asExpr() and
c = call.getExpr() and
arg.isArgumentOf(c, pos)
)
}
}
/** A data-flow node that represents a delegate passed into itself. */
class DelegateSelfArgumentNode extends ArgumentNodeImpl, ExprNode {
private DataFlowCall call_;
DelegateSelfArgumentNode() { lambdaCallExpr(call_, this.getExpr(), _) }
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
call = call_ and
pos.isDelegateSelf()
}
}
/**
* A node that corresponds to the value of an object creation (`new C()`) before
* the constructor has run.
*/
class MallocNode extends ArgumentNodeImpl, NodeImpl, TMallocNode {
private ControlFlow::Nodes::ElementNode cfn;
MallocNode() { this = TMallocNode(cfn) }
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
call = TNonDelegateCall(cfn, _) and
pos.isQualifier()
}
override ControlFlow::Node getControlFlowNodeImpl() { result = cfn }
override DataFlowCallable getEnclosingCallableImpl() {
result.getAControlFlowNode() = cfn
or
result = getEnclosingStaticFieldOrProperty(cfn.getAstNode())
}
override Type getTypeImpl() { result = cfn.getAstNode().(Expr).getType() }
override Location getLocationImpl() { result = cfn.getLocation() }
override string toStringImpl() { result = "malloc" }
}
/**
* A data-flow node that represents the implicit collection creation in a call to a
* callable with a `params` parameter. For example, there is an implicit array
* creation `new [] { "a", "b", "c" }` in
*
* ```csharp
* void Foo(params string[] args) { ... }
* Foo("a", "b", "c");
* ```
*
* Note that array creations are not inserted when there is only one argument,
* and that argument is itself a compatible array, for example
* `Foo(new[] { "a", "b", "c" })`.
*/
class ParamsArgumentNode extends ArgumentNodeImpl, NodeImpl, TParamsArgumentNode {
private ControlFlow::Node callCfn;
ParamsArgumentNode() { this = TParamsArgumentNode(callCfn) }
private Parameter getParameter() {
callCfn = any(Call c | isParamsArg(c, _, result)).getAControlFlowNode()
}
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
callCfn = call.getControlFlowNode() and
pos.getPosition() = this.getParameter().getPosition()
}
override DataFlowCallable getEnclosingCallableImpl() {
result.getAControlFlowNode() = callCfn
or
result = getEnclosingStaticFieldOrProperty(callCfn.getAstNode())
}
override Type getTypeImpl() { result = this.getParameter().getType() }
override ControlFlow::Node getControlFlowNodeImpl() { none() }
override Location getLocationImpl() { result = callCfn.getLocation() }
override string toStringImpl() { result = "[implicit collection creation] " + callCfn }
}
private class SummaryArgumentNode extends FlowSummaryNode, ArgumentNodeImpl {
private SummaryCall call_;
private ArgumentPosition pos_;
SummaryArgumentNode() {
FlowSummaryImpl::Private::summaryArgumentNode(call_.getReceiver(), this.getSummaryNode(), pos_)
}
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
call = call_ and pos = pos_
}
}
}
import ArgumentNodes
/** A data-flow node that represents a value returned by a callable. */
abstract class ReturnNode extends Node {
/** Gets the kind of this return node. */
abstract ReturnKind getKind();
}
private module ReturnNodes {
/**
* A data-flow node that represents an expression returned by a callable,
* either using a `return` statement or an expression body (`=>`).
*/
class ExprReturnNode extends ReturnNode, ExprNode {
ExprReturnNode() {
exists(Callable c, Expr e | e = this.getExpr() |
c.canReturn(e) and not c.(Modifiable).isAsync()
)
}
override NormalReturnKind getKind() { exists(result) }
}
/**
* A data-flow node that represents an assignment to an `out` or a `ref`
* parameter.
*/
class OutRefReturnNode extends ReturnNode, SsaDefinitionNode {
OutRefReturnKind kind;
OutRefReturnNode() {
exists(Parameter p |
this.getDefinition().isLiveOutRefParameterDefinition(p) and
kind.getPosition() = p.getPosition()
|
p.isOut() and kind instanceof OutReturnKind
or
p.isRef() and kind instanceof RefReturnKind
)
}
override ReturnKind getKind() { result = kind }
}
/**
* A `yield return` node. A node is synthesized in order to be able to model
* `yield return`s as stores into collections, i.e., there is flow from `e`
* to `yield return e [e]`.
*/
class YieldReturnNode extends ReturnNode, NodeImpl, TYieldReturnNode {
private ControlFlow::Nodes::ElementNode cfn;
private YieldReturnStmt yrs;
YieldReturnNode() { this = TYieldReturnNode(cfn) and yrs.getExpr().getAControlFlowNode() = cfn }
YieldReturnStmt getYieldReturnStmt() { result = yrs }
override NormalReturnKind getKind() { any() }
override DataFlowCallable getEnclosingCallableImpl() { result.getAControlFlowNode() = cfn }
override Type getTypeImpl() { result = yrs.getEnclosingCallable().getReturnType() }
override ControlFlow::Node getControlFlowNodeImpl() { result = cfn }
override Location getLocationImpl() { result = yrs.getLocation() }
override string toStringImpl() { result = yrs.toString() }
}
/**
* A synthesized `return` node for returned expressions inside `async` methods.
*/
class AsyncReturnNode extends ReturnNode, NodeImpl, TAsyncReturnNode {
private ControlFlow::Nodes::ElementNode cfn;
private Expr expr;
AsyncReturnNode() { this = TAsyncReturnNode(cfn) and expr = cfn.getAstNode() }
Expr getExpr() { result = expr }
override NormalReturnKind getKind() { any() }
override DataFlowCallable getEnclosingCallableImpl() { result.getAControlFlowNode() = cfn }
override Type getTypeImpl() { result = expr.getEnclosingCallable().getReturnType() }
override ControlFlow::Node getControlFlowNodeImpl() { result = cfn }
override Location getLocationImpl() { result = expr.getLocation() }
override string toStringImpl() { result = expr.toString() }
}
private class SummaryReturnNode extends FlowSummaryNode, ReturnNode {
private ReturnKind rk;
SummaryReturnNode() {
FlowSummaryImpl::Private::summaryReturnNode(this.getSummaryNode(), rk)
or
exists(Parameter p, int pos |
summaryPostUpdateNodeIsOutOrRef(this, p) and
pos = p.getPosition()
|
p.isOut() and rk.(OutReturnKind).getPosition() = pos
or
p.isRef() and rk.(RefReturnKind).getPosition() = pos
)
}
override ReturnKind getKind() { result = rk }
}
}
/**
* Holds if summary node `n` is a post-update node for `out`/`ref` parameter `p`.
* In this case we adjust it to instead be a return node.
*/
private predicate summaryPostUpdateNodeIsOutOrRef(FlowSummaryNode n, Parameter p) {
exists(SummaryParameterNode pn, DataFlowCallable c, ParameterPosition pos |
FlowSummaryImpl::Private::summaryPostUpdateNode(n.getSummaryNode(), pn.getSummaryNode()) and
pn.isParameterOf(c, pos) and
p = c.asSummarizedCallable().getParameter(pos.getPosition()) and
p.isOutOrRef()
)
}
import ReturnNodes
/** A data-flow node that represents the output of a call. */
abstract class OutNode extends Node {
/** Gets the underlying call, where this node is a corresponding output of kind `kind`. */
abstract DataFlowCall getCall(ReturnKind kind);
}
private module OutNodes {
private import semmle.code.csharp.frameworks.system.Collections
private import semmle.code.csharp.frameworks.system.collections.Generic
private DataFlowCall csharpCall(Expr e, ControlFlow::Node cfn) {
e = any(DispatchCall dc | result = TNonDelegateCall(cfn, dc)).getCall() or
result = TExplicitDelegateLikeCall(cfn, e)
}
/**
* A data-flow node that reads a value returned directly by a callable.
*/
class ExprOutNode extends OutNode, ExprNode {
private DataFlowCall call;
ExprOutNode() {
exists(Expr e | e = this.getExpr() | call = csharpCall(e, this.getControlFlowNode()))
}
override DataFlowCall getCall(ReturnKind kind) {
result = call and
(
kind instanceof NormalReturnKind and
not call.getExpr().getType() instanceof VoidType
)
}
}
/**
* A data-flow node that reads a value returned by a callable using an
* `out` or `ref` parameter.
*/
class ParamOutNode extends OutNode, AssignableDefinitionNode {
private AssignableDefinitions::OutRefDefinition outRefDef;
private ControlFlow::Node cfn;
ParamOutNode() { outRefDef = this.getDefinitionAtNode(cfn) }
override DataFlowCall getCall(ReturnKind kind) {
result = csharpCall(_, cfn) and
exists(Parameter p |
p.getUnboundDeclaration().getPosition() = kind.(OutRefReturnKind).getPosition() and
outRefDef.getTargetAccess() = result.getExpr().(Call).getArgumentForParameter(p)
)
}
}
private class SummaryOutNode extends FlowSummaryNode, OutNode {
private SummaryCall call;
private ReturnKind kind_;
SummaryOutNode() {
FlowSummaryImpl::Private::summaryOutNode(call.getReceiver(), this.getSummaryNode(), kind_)
}
override DataFlowCall getCall(ReturnKind kind) { result = call and kind = kind_ }
}
}
import OutNodes
/** A data-flow node used to model flow summaries. */
class FlowSummaryNode extends NodeImpl, TFlowSummaryNode {
FlowSummaryImpl::Private::SummaryNode getSummaryNode() { this = TFlowSummaryNode(result) }
FlowSummary::SummarizedCallable getSummarizedCallable() {
result = this.getSummaryNode().getSummarizedCallable()
}
override DataFlowCallable getEnclosingCallableImpl() {
result.asSummarizedCallable() = this.getSummarizedCallable()
}
override DataFlowType getDataFlowType() {
result = FlowSummaryImpl::Private::summaryNodeType(this.getSummaryNode())
}
override Type getTypeImpl() { none() }
override ControlFlow::Node getControlFlowNodeImpl() { none() }
override Location getLocationImpl() { result = this.getSummarizedCallable().getLocation() }
override string toStringImpl() { result = this.getSummaryNode().toString() }
}
/**
* A data-flow node used to model reading and writing of primary constructor parameters.
* For example, in
* ```csharp
* public class C(object o)
* {
* public object GetParam() => o; // (1)
*
* public void SetParam(object value) => o = value; // (2)
* }
* ```
* the first access to `o` (1) is modeled as `this.o_backing_field` and
* the second access to `o` (2) is modeled as `this.o_backing_field = value`.
* Both models need a pre-update this node, and the latter need an additional post-update this access,
* all of which are represented by an `InstanceParameterAccessNode` node.
*/
abstract private class InstanceParameterAccessNode extends NodeImpl, TInstanceParameterAccessNode {
ControlFlow::Node cfn;
boolean isPostUpdate;
Parameter p;
InstanceParameterAccessNode() {
this = TInstanceParameterAccessNode(cfn, isPostUpdate) and
exists(ParameterAccess pa | cfn = getAPrimaryConstructorParameterCfn(pa) and pa.getTarget() = p)
}
override DataFlowCallable getEnclosingCallableImpl() { result.getAControlFlowNode() = cfn }
override Type getTypeImpl() { result = cfn.getEnclosingCallable().getDeclaringType() }
override ControlFlow::Nodes::ElementNode getControlFlowNodeImpl() { none() }
override Location getLocationImpl() { result = cfn.getLocation() }
/**
* Gets the underlying control flow node.
*/
ControlFlow::Node getUnderlyingControlFlowNode() { result = cfn }
/**
* Gets the primary constructor parameter that this is a this access to.
*/
Parameter getParameter() { result = p }
}
private class InstanceParameterAccessPreNode extends InstanceParameterAccessNode {
InstanceParameterAccessPreNode() { isPostUpdate = false }
override string toStringImpl() { result = "this" }
}
/**
* A data-flow node used to synthesize the body of a primary constructor.
*
* For example, in
* ```csharp
* public class C(object o) { }
* ```
* we synthesize the primary constructor for `C` as
* ```csharp
* public C(object o) => this.o_backing_field = o;
* ```
* The synthesized (pre/post-update) this access is represented an `PrimaryConstructorThisAccessNode` node.
*/
abstract private class PrimaryConstructorThisAccessNode extends NodeImpl,
TPrimaryConstructorThisAccessNode
{
Parameter p;
boolean isPostUpdate;
DataFlowCallable callable;
PrimaryConstructorThisAccessNode() {
this = TPrimaryConstructorThisAccessNode(p, isPostUpdate, callable)
}
override DataFlowCallable getEnclosingCallableImpl() { result = callable }
override Type getTypeImpl() { result = p.getCallable().getDeclaringType() }
override ControlFlow::Nodes::ElementNode getControlFlowNodeImpl() { none() }
override Location getLocationImpl() {
NearestLocation<NearestLocationInputParamAfterCallable>::nearestLocation(p,
pragma[only_bind_out](callable).getLocation(), result)
}
override string toStringImpl() { result = "this" }
/**
* Gets the primary constructor parameter that this is a this access to.
*/
Parameter getParameter() { result = p }
/**
* Holds if this is a this access for a primary constructor parameter write.
*/
predicate isPostUpdate() { isPostUpdate = true }
}
private class PrimaryConstructorThisAccessPreNode extends PrimaryConstructorThisAccessNode {
PrimaryConstructorThisAccessPreNode() { isPostUpdate = false }
override string toStringImpl() { result = "this" }
}
/**
* A synthesized data flow node representing a closure object that tracks
* captured variables.
*/
class CaptureNode extends NodeImpl, TCaptureNode {
VariableCapture::Flow::SynthesizedCaptureNode cn;
CaptureNode() { this = TCaptureNode(cn) }
VariableCapture::Flow::SynthesizedCaptureNode getSynthesizedCaptureNode() { result = cn }
override DataFlowCallable getEnclosingCallableImpl() {
result.getAControlFlowNode().getBasicBlock() = cn.getBasicBlock()
}
override Type getTypeImpl() {
exists(VariableCapture::CapturedVariable v | cn.isVariableAccess(v) and result = v.getType())
}
override DataFlowType getDataFlowType() {
if cn.isInstanceAccess()
then result.asDelegate() = cn.getEnclosingCallable()
else result = super.getDataFlowType()
}
override ControlFlow::Node getControlFlowNodeImpl() { none() }
override Location getLocationImpl() { result = cn.getLocation() }
override string toStringImpl() { result = cn.toString() }
}
/**
* Holds if a property has accessors declared in multiple locations, and where
* all accessors have at least one declaration without a body.
* This can happen if both a "real" and a "stub" implementation is included in the
* same database (which is the case for .NET Runtime).
*/
private predicate hasAutoImplementation(Property p) {
forex(Accessor a | a = p.getAnAccessor() |
strictcount(getASourceLocation(a)) > count(a.getBody())
)
}
/** A field or a property. */
class FieldOrProperty extends Assignable, Modifiable {
FieldOrProperty() {
this instanceof Field
or
this instanceof Property
}
/** Holds if this is either a field or a field-like property. */
predicate isFieldLike() {
this instanceof Field or
this =
any(Property p |
not p.isOverridableOrImplementable() and
(
p.isAutoImplemented()
or
p.isAutoImplementedReadOnly()
or
p.getDeclaringType() instanceof AnonymousClass
or
hasAutoImplementation(p)
)
or
p.fromLibrary()
)
}
/** Gets the content that matches this field or property. */
ContentSet getContentSet() {
result.isField(this.getUnboundDeclaration())
or
result.isProperty(this.getUnboundDeclaration())
}
}
/** A string that is a reference to a late-bound target of a dynamic member access. */
class DynamicProperty extends string {
private DynamicMemberAccess dma;
DynamicProperty() { this = dma.getLateBoundTargetName() }
ContentSet getContentSet() { result.isDynamicProperty(this) }
/** Gets an access of this dynamic property. */
DynamicMemberAccess getAnAccess() { result = dma }
}
private class InstanceFieldOrProperty extends FieldOrProperty {
InstanceFieldOrProperty() { not this.isStatic() }
}
/**
* An access to a field or a property.
*/
class FieldOrPropertyAccess extends AssignableAccess, QualifiableExpr {
FieldOrPropertyAccess() { this.getTarget() instanceof FieldOrProperty }
}
private class FieldOrPropertyRead extends FieldOrPropertyAccess, AssignableRead {
/**
* Holds if this field-like read is not completely determined by explicit
* SSA updates.
*/
predicate hasNonlocalValue() {
exists(Ssa::Definition def, Ssa::ImplicitDefinition idef |
def.getARead() = this and
idef = def.getAnUltimateDefinition()
|
idef instanceof Ssa::ImplicitEntryDefinition or
idef instanceof Ssa::ImplicitCallDefinition
)
}
}
/**
* A data flow node used for control-flow insensitive flow through fields
* and properties.
*
* In global data flow this is used to model flow through static fields and
* properties, while for lambda flow we additionally use it to track assignments
* in constructors to uses within the same class.
*/
class FlowInsensitiveFieldNode extends NodeImpl, TFlowInsensitiveFieldNode {
private FieldOrProperty f;
FlowInsensitiveFieldNode() { this = TFlowInsensitiveFieldNode(f) }
override DataFlowCallable getEnclosingCallableImpl() { result.asFieldOrProperty() = f }
override Type getTypeImpl() { result = f.getType() }
override ControlFlow::Node getControlFlowNodeImpl() { none() }
override Location getLocationImpl() { result = f.getLocation() }
override string toStringImpl() { result = "[flow-insensitive] " + f }
}
/**
* A data flow node used for control-flow insensitive flow through captured
* variables.
*
* Only used in lambda flow.
*/
class FlowInsensitiveCapturedVariableNode extends NodeImpl, TFlowInsensitiveCapturedVariableNode {
private LocalScopeVariable v;
FlowInsensitiveCapturedVariableNode() { this = TFlowInsensitiveCapturedVariableNode(v) }
LocalScopeVariable getVariable() { result = v }
override DataFlowCallable getEnclosingCallableImpl() { result.asCapturedVariable() = v }
override Type getTypeImpl() { result = v.getType() }
override ControlFlow::Node getControlFlowNodeImpl() { none() }
override Location getLocationImpl() { result = v.getLocation() }
override string toStringImpl() { result = "[flow-insensitive] " + v }
}
/**
* Holds if `pred` can flow to `succ`, by jumping from one callable to
* another. Additional steps specified by the configuration are *not*
* taken into account.
*/
predicate jumpStep(Node pred, Node succ) {
pred.(NonLocalJumpNode).getAJumpSuccessor(true) = succ
or
exists(FieldOrProperty f | f.isStatic() |
f.getAnAssignedValue() = pred.asExpr() and
succ = TFlowInsensitiveFieldNode(f)
or
exists(FieldOrPropertyRead fr | f.getAnAccess() = fr |
fr = pred.(PostUpdateNode).getPreUpdateNode().asExpr() and
succ = TFlowInsensitiveFieldNode(f)
or
pred = TFlowInsensitiveFieldNode(f) and
fr = succ.asExpr() and
fr.hasNonlocalValue()
)
)
or
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(),
succ.(FlowSummaryNode).getSummaryNode())
or
succ = pred.(LocalFunctionCreationNode).getAnAccess(false)
}
pragma[nomagic]
private ContentSet getResultContent() {
result.isProperty(any(SystemThreadingTasksTaskTClass c_).getResultProperty())
}
private predicate primaryConstructorParameterStore(
AssignableDefinitionNode node1, PrimaryConstructorParameterContent c, Node node2
) {
exists(AssignableDefinition def, ControlFlow::Node cfn, Parameter p |
node1 = TAssignableDefinitionNode(def, cfn) and
p = def.getTarget() and
node2 = TInstanceParameterAccessNode(cfn, true) and
c.getParameter() = p
)
}
pragma[nomagic]
private predicate recordParameter(RecordType t, Parameter p, string name) {
p.getName() = name and p.getCallable().getDeclaringType() = t
}
private predicate storeContentStep(Node node1, Content c, Node node2) {
exists(ExprNode node, boolean postUpdate |
if postUpdate = true then node = node2.(PostUpdateNode).getPreUpdateNode() else node = node2
|
arrayStore(node1.asExpr(), node.getExpr(), postUpdate) and c instanceof ElementContent
)
or
collectionStore(node1.asExpr(), node2.asExpr()) and c instanceof ElementContent
or
exists(Call call |
node2 = TParamsArgumentNode(call.getControlFlowNode()) and
isParamsArg(call, node1.asExpr(), _) and
c instanceof ElementContent
)
or
exists(Expr e |
e = node1.asExpr() and
node2.(YieldReturnNode).getYieldReturnStmt().getExpr() = e and
c instanceof ElementContent
)
or
primaryConstructorParameterStore(node1, c, node2)
or
exists(Parameter p, DataFlowCallable callable |
node1 = TExplicitParameterNode(p, callable) and
node2 = TPrimaryConstructorThisAccessNode(p, true, callable) and
not recordParameter(_, p, _) and
c.(PrimaryConstructorParameterContent).getParameter() = p
)
or
VariableCapture::storeStep(node1, c, node2)
}
pragma[nomagic]
private predicate recordProperty(RecordType t, ContentSet c, string name) {
exists(Property p |
c.isProperty(p) and
p.getName() = name and
p.getDeclaringType() = t
)
}
/**
* Holds if data can flow from `node1` to `node2` via an assignment to
* the content set `c` of a delegate call.
*
* If there is a delegate call f(x), then we store "x" on "f"
* using a delegate argument content set.
*/
private predicate storeStepDelegateCall(ExplicitArgumentNode node1, ContentSet c, Node node2) {
exists(ExplicitDelegateLikeDataFlowCall call, int i |
node1.argumentOf(call, TPositionalArgumentPosition(i)) and
lambdaCall(call, _, node2.(PostUpdateNode).getPreUpdateNode()) and
c.isDelegateCallArgument(i)
)
}
/**
* Holds if data can flow from `node1` to `node2` via an assignment to
* content `c`.
*/
predicate storeStep(Node node1, ContentSet c, Node node2) {
exists(Content cont |
storeContentStep(node1, cont, node2) and
c.isSingleton(cont)
)
or
exists(ExprNode node, boolean postUpdate |
if postUpdate = true then node = node2.(PostUpdateNode).getPreUpdateNode() else node = node2
|
fieldOrPropertyStore(c, node1.asExpr(), node.getExpr(), postUpdate)
)
or
exists(Expr e |
e = node1.asExpr() and
node2.(AsyncReturnNode).getExpr() = e and
c = getResultContent()
)
or
exists(Parameter p, DataFlowCallable callable, RecordType t, string name |
node1 = TExplicitParameterNode(p, callable) and
node2 = TPrimaryConstructorThisAccessNode(p, true, callable) and
recordParameter(t, p, name) and
recordProperty(t, c, name)
)
or
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
node2.(FlowSummaryNode).getSummaryNode())
or
storeStepDelegateCall(node1, c, node2)
}
private predicate readContentStep(Node node1, Content c, Node node2) {
arrayRead(node1.asExpr(), node2.asExpr()) and
c instanceof ElementContent
or
exists(
ForeachStmt fs, Ssa::ExplicitDefinition def,
AssignableDefinitions::LocalVariableDefinition defTo
|
node1.asExpr() = fs.getIterableExpr() and
defTo.getDeclaration() = fs.getVariableDeclExpr() and
def.getADefinition() = defTo and
node2.(SsaDefinitionNode).getDefinition() = def and
c instanceof ElementContent
)
or
node1 =
any(InstanceParameterAccessPreNode n |
n.getUnderlyingControlFlowNode() = node2.(ExprNode).getControlFlowNode() and
n.getParameter() = c.(PrimaryConstructorParameterContent).getParameter()
) and
node2.asExpr() instanceof ParameterRead
or
// node1 = (..., node2, ...)
// node1.ItemX flows to node2
exists(TupleExpr te, int i, Expr item |
te = node1.asExpr() and
not te.isConstruction() and
c.(FieldContent).getField() = te.getType().(TupleType).getElement(i).getUnboundDeclaration() and
// node1 = (..., item, ...)
te.getArgument(i) = item
|
// item = (..., ..., ...) in node1 = (..., (..., ..., ...), ...)
node2.asExpr().(TupleExpr) = item
or
// item = variable in node1 = (..., variable, ...)
exists(AssignableDefinitions::TupleAssignmentDefinition tad |
node2.(AssignableDefinitionNode).getDefinition() = tad and
tad.getLeaf() = item
)
or
// item = variable in node1 = (..., variable, ...) in a case/is var (..., ...)
isPatternExprDescendant(te) and
exists(AssignableDefinitions::LocalVariableDefinition lvd |
node2.(AssignableDefinitionNode).getDefinition() = lvd and
lvd.getDeclaration() = item
)
)
or
VariableCapture::readStep(node1, c, node2)
}
/**
* Holds if data can flow from `node1` to `node2` via an assignment to
* the content set `c` of a delegate call.
*
* If there is a delegate call f(x), then we read the return of the delegate
* call.
*/
private predicate readStepDelegateCall(Node node1, ContentSet c, OutNode node2) {
exists(ExplicitDelegateLikeDataFlowCall call |
lambdaCall(call, _, node1) and
node2.getCall(TNormalReturnKind()) = call and
c.isDelegateCallReturn()
)
}
/**
* Holds if data can flow from `node1` to `node2` via a read of content `c`.
*/
predicate readStep(Node node1, ContentSet c, Node node2) {
exists(Content cont |
readContentStep(node1, cont, node2) and
c.isSingleton(cont)
)
or
fieldOrPropertyRead(node1.asExpr(), c, node2.asExpr())
or
dynamicPropertyRead(node1.asExpr(), c, node2.asExpr())
or
node2.asExpr().(AwaitExpr).getExpr() = node1.asExpr() and
c = getResultContent()
or
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c,
node2.(FlowSummaryNode).getSummaryNode())
or
readStepDelegateCall(node1, c, node2)
}
private predicate clearsCont(Node n, Content c) {
exists(Argument a, Struct s, Field f |
a = n.(PostUpdateNode).getPreUpdateNode().asExpr() and
a.getType() = s and
f = s.getAField() and
c.(FieldContent).getField() = f.getUnboundDeclaration() and
not f.getType() instanceof CollectionType and
not f.isRef()
)
or
n = any(PostUpdateNode n1 | primaryConstructorParameterStore(_, c, n1)).getPreUpdateNode()
or
VariableCapture::clearsContent(n, c)
}
/**
* 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`.
*/
predicate clearsContent(Node n, ContentSet c) {
exists(Content cont |
clearsCont(n, cont) and
c.isSingleton(cont)
)
or
fieldOrPropertyStore(c, _, n.asExpr(), true)
or
fieldOrPropertyStore(c, _, n.(ObjectInitializerNode).getInitializer(), false)
or
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
or
exists(WithExpr we, ObjectInitializer oi, FieldOrProperty f |
oi = we.getInitializer() and
n.asExpr() = oi and
f = oi.getAMemberInitializer().getInitializedMember() and
c = f.getContentSet()
)
}
/**
* Holds if the value that is being tracked is expected to be stored inside content `c`
* at node `n`.
*/
predicate expectsContent(Node n, ContentSet c) {
FlowSummaryImpl::Private::Steps::summaryExpectsContent(n.(FlowSummaryNode).getSummaryNode(), c)
or
n.asExpr() instanceof SpreadElementExpr and c.isElement()
}
class NodeRegion instanceof ControlFlow::BasicBlock {
string toString() { result = "NodeRegion" }
predicate contains(Node n) { this = n.getControlFlowNode().getBasicBlock() }
}
/**
* Holds if the nodes in `nr` are unreachable when the call context is `call`.
*/
predicate isUnreachableInCall(NodeRegion nr, DataFlowCall call) {
exists(ExplicitParameterNode paramNode, Guard guard, GuardValue val |
viableConstantParamArg(paramNode, val.getDualValue(), call) and
paramNode.getSsaDefinition().getARead() = guard and
guard.valueControls(nr, val)
)
}
/**
* An entity used to represent the type of data-flow node. Two nodes will have
* the same `DataFlowType` when the underlying `Type`s are structurally equal
* modulo type parameters and identity conversions.
*
* For example, `Func<T, int>` and `Func<S, int>` are mapped to the same
* `DataFlowType`, while `Func<T, int>` and `Func<string, int>` are not, because
* `string` is not a type parameter.
*
* For delegates, we use the delegate itself instead of its type, in order to
* improve dispatch.
*/
class DataFlowType extends TDataFlowType {
Gvn::GvnType asGvnType() { this = TGvnDataFlowType(result) }
Callable asDelegate() { this = TDelegateDataFlowType(result) }
/**
* Gets an expression that creates a delegate of this type.
*
* For methods used as method groups in calls there can be multiple
* creations associated with the same type.
*/
ControlFlowElement getADelegateCreation() {
exists(Callable callable | this = TDelegateDataFlowType(callable) |
lambdaCreationExpr(result, callable)
or
isLocalFunctionCallReceiver(_, result, callable)
)
}
final string toString() {
result = this.asGvnType().toString()
or
result = this.asDelegate().toString()
}
}
/** Gets the type of `n` used for type pruning. */
DataFlowType getNodeType(Node n) {
result = n.(NodeImpl).getDataFlowType() and
not lambdaCreation(n, _, _) and
not isLocalFunctionCallReceiver(_, n.asExpr(), _)
or
[
n.asExpr().(ControlFlowElement),
n.(LocalFunctionCreationPreNode).getUnderlyingControlFlowNode().getAstNode()
] = result.getADelegateCreation()
}
private class DataFlowNullType extends Gvn::GvnType {
DataFlowNullType() { this = Gvn::getGlobalValueNumber(any(NullType nt)) }
pragma[noinline]
predicate isConvertibleTo(Gvn::GvnType t) {
defaultNullConversion(_, any(Type t0 | t = Gvn::getGlobalValueNumber(t0)))
}
}
private class GvnUnknownType extends Gvn::GvnType {
GvnUnknownType() { this = Gvn::getGlobalValueNumber(any(UnknownType ut)) }
}
pragma[nomagic]
private predicate uselessTypebound(DataFlowType dt) {
dt.asGvnType() =
any(Gvn::GvnType t |
t instanceof GvnUnknownType or
t instanceof Gvn::TypeParameterGvnType
)
}
pragma[nomagic]
private predicate compatibleTypesDelegateLeft(DataFlowType dt1, DataFlowType dt2) {
exists(Gvn::GvnType t1, Gvn::GvnType t2 |
t1 = exprNode(dt1.getADelegateCreation()).(NodeImpl).getDataFlowType().asGvnType() and
t2 = dt2.asGvnType()
|
commonSubType(t1, t2)
or
commonSubTypeUnifiableLeft(t1, t2)
or
commonSubTypeUnifiableLeft(t2, t1)
or
t2.(DataFlowNullType).isConvertibleTo(t1)
or
t2 instanceof Gvn::TypeParameterGvnType
or
t2 instanceof GvnUnknownType
)
}
/**
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from
* a node of type `t1` to a node of type `t2`.
*/
pragma[nomagic]
predicate compatibleTypes(DataFlowType dt1, DataFlowType dt2) {
exists(Gvn::GvnType t1, Gvn::GvnType t2 |
t1 = dt1.asGvnType() and
t2 = dt2.asGvnType()
|
commonSubType(t1, t2)
or
commonSubTypeUnifiableLeft(t1, t2)
or
commonSubTypeUnifiableLeft(t2, t1)
or
t1.(DataFlowNullType).isConvertibleTo(t2)
or
t2.(DataFlowNullType).isConvertibleTo(t1)
)
or
exists(dt1.asGvnType()) and uselessTypebound(dt2)
or
uselessTypebound(dt1) and exists(dt2.asGvnType())
or
compatibleTypesDelegateLeft(dt1, dt2)
or
compatibleTypesDelegateLeft(dt2, dt1)
or
dt1.asDelegate() = dt2.asDelegate()
}
pragma[nomagic]
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) {
t1 != t2 and
t1.asGvnType() = getANonTypeParameterSubTypeRestricted(t2.asGvnType())
or
t1.asGvnType() instanceof RelevantGvnType and
not uselessTypebound(t1) and
uselessTypebound(t2)
or
compatibleTypesDelegateLeft(t1, t2)
}
/**
* A node associated with an object after an operation that might have
* changed its state.
*
* This can be either the argument to a callable after the callable returns
* (which might have mutated the argument), or the qualifier of a field after
* an update to the field.
*
* Nodes corresponding to AST elements, for example `ExprNode`, usually refer
* to the value before the update with the exception of `ObjectCreation`,
* which represents the value after the constructor has run.
*/
abstract class PostUpdateNode extends Node {
/** Gets the node before the state update. */
abstract Node getPreUpdateNode();
}
module PostUpdateNodes {
abstract class SourcePostUpdateNode extends PostUpdateNode {
abstract Node getPreUpdateSourceNode();
final override Node getPreUpdateNode() { result = this.getPreUpdateSourceNode() }
}
class ObjectCreationNode extends SourcePostUpdateNode, ExprNode, TExprNode {
private ObjectCreation oc;
ObjectCreationNode() { this = TExprNode(oc.getAControlFlowNode()) }
override Node getPreUpdateSourceNode() {
exists(ControlFlow::Nodes::ElementNode cfn | this = TExprNode(cfn) |
result = TObjectInitializerNode(cfn)
or
not oc.hasInitializer() and
result = TMallocNode(cfn)
)
}
}
/**
* A node that represents the value of a newly created object after the object
* has been created, but before the object initializer has been executed.
*
* Such a node acts as both a post-update node for the `MallocNode`, as well as
* a pre-update node for the `ObjectCreationNode`.
*/
class ObjectInitializerNode extends SourcePostUpdateNode, NodeImpl, ArgumentNodeImpl,
TObjectInitializerNode
{
private ObjectCreation oc;
private ControlFlow::Nodes::ElementNode cfn;
ObjectInitializerNode() {
this = TObjectInitializerNode(cfn) and
cfn = oc.getAControlFlowNode()
}
/** Gets the initializer to which this initializer node belongs. */
ObjectOrCollectionInitializer getInitializer() { result = oc.getInitializer() }
override MallocNode getPreUpdateSourceNode() { result = TMallocNode(cfn) }
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
pos.isQualifier() and
exists(ObjectOrCollectionInitializer init | init = oc.getInitializer() |
// E.g. `new Dictionary<int, string>{ {0, "a"}, {1, "b"} }`
call.getExpr() = init.(CollectionInitializer).getAnElementInitializer()
or
// E.g. `new Dictionary<int, string>() { [0] = "a", [1] = "b" }`
call.getExpr() = init.(ObjectInitializer).getAMemberInitializer().getLValue()
)
}
override DataFlowCallable getEnclosingCallableImpl() {
result.getAControlFlowNode() = cfn
or
result = getEnclosingStaticFieldOrProperty(oc)
}
override Type getTypeImpl() { result = oc.getType() }
override ControlFlow::Nodes::ElementNode getControlFlowNodeImpl() { result = cfn }
override Location getLocationImpl() { result = cfn.getLocation() }
override string toStringImpl() { result = "[pre-initializer] " + cfn }
}
class ExprPostUpdateNode extends SourcePostUpdateNode, NodeImpl, TExprPostUpdateNode {
private ControlFlow::Nodes::ElementNode cfn;
ExprPostUpdateNode() { this = TExprPostUpdateNode(cfn) }
override ExprNode getPreUpdateSourceNode() { result = TExprNode(cfn) }
override DataFlowCallable getEnclosingCallableImpl() {
result.getAControlFlowNode() = cfn
or
result = getEnclosingStaticFieldOrProperty(cfn.getAstNode())
}
override Type getTypeImpl() { result = cfn.getAstNode().(Expr).getType() }
override ControlFlow::Node getControlFlowNodeImpl() { none() }
override Location getLocationImpl() { result = cfn.getLocation() }
override string toStringImpl() { result = "[post] " + cfn.toString() }
}
private class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNode {
private FlowSummaryImpl::Private::SummaryNode preUpdateNode;
SummaryPostUpdateNode() {
FlowSummaryImpl::Private::summaryPostUpdateNode(this.getSummaryNode(), preUpdateNode) and
not summaryPostUpdateNodeIsOutOrRef(this, _)
}
override Node getPreUpdateNode() { result.(FlowSummaryNode).getSummaryNode() = preUpdateNode }
}
private class InstanceParameterAccessPostUpdateNode extends SourcePostUpdateNode,
InstanceParameterAccessNode
{
InstanceParameterAccessPostUpdateNode() { isPostUpdate = true }
override InstanceParameterAccessPreNode getPreUpdateSourceNode() {
result = TInstanceParameterAccessNode(cfn, false)
}
override string toStringImpl() { result = "[post] this" }
}
private class PrimaryConstructorThisAccessPostUpdateNode extends SourcePostUpdateNode,
PrimaryConstructorThisAccessNode
{
PrimaryConstructorThisAccessPostUpdateNode() { isPostUpdate = true }
override PrimaryConstructorThisAccessPreNode getPreUpdateSourceNode() {
result = TPrimaryConstructorThisAccessNode(p, false, callable)
}
override string toStringImpl() { result = "[post] this" }
}
class LocalFunctionCreationPostUpdateNode extends LocalFunctionCreationNode, SourcePostUpdateNode {
LocalFunctionCreationPostUpdateNode() { isPostUpdate = true }
override LocalFunctionCreationPreNode getPreUpdateSourceNode() {
result = TLocalFunctionCreationNode(cfn, false)
}
override string toStringImpl() { result = "[post] " + cfn }
}
private class CapturePostUpdateNode extends SourcePostUpdateNode, CaptureNode {
private CaptureNode pre;
CapturePostUpdateNode() {
VariableCapture::Flow::capturePostUpdateNode(this.getSynthesizedCaptureNode(),
pre.getSynthesizedCaptureNode())
}
override CaptureNode getPreUpdateSourceNode() { result = pre }
override string toStringImpl() { result = "[post] " + cn }
}
}
private import PostUpdateNodes
/** A node that performs a type cast. */
class CastNode extends Node {
CastNode() {
this.asExpr() instanceof Cast
or
this.(AssignableDefinitionNode).getDefinition() instanceof
AssignableDefinitions::PatternDefinition
}
}
class DataFlowExpr = Expr;
/** An argument that always has the same value. */
private class ConstantArgumentNode extends ExprNode {
ConstantArgumentNode() { Guards::InternalUtil::exprHasValue(this.(ArgumentNode).asExpr(), _) }
/** Gets the value of this expression. */
GuardValue getValue() { Guards::InternalUtil::exprHasValue(this.getExpr(), result) }
}
pragma[noinline]
private predicate viableConstantParamArg(ParameterNode paramNode, GuardValue val, DataFlowCall call) {
exists(ConstantArgumentNode arg |
viableParamArg(call, paramNode, arg) and
val = arg.getValue()
)
}
int accessPathLimit() { result = 5 }
/**
* Holds if access paths with `c` at their head always should be tracked at high
* precision. This disables adaptive access path precision for such access paths.
*/
predicate forceHighPrecision(Content c) { c instanceof ElementContent }
private predicate lambdaCreationExpr(ControlFlowElement creation, Callable c) {
c =
[
creation.(AnonymousFunctionExpr),
creation.(DelegateCreation).getArgument().(CallableAccess).getTarget().getUnboundDeclaration(),
creation.(AddressOfExpr).getOperand().(CallableAccess).getTarget().getUnboundDeclaration(),
creation.(LocalFunctionStmt).getLocalFunction()
]
}
class LambdaCallKind = Unit;
/** Holds if `creation` is an expression that creates a delegate for `c`. */
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) {
lambdaCreationExpr(creation.asExpr(), c.asCallable(_)) and
exists(kind)
}
private predicate isLocalFunctionCallReceiver(
LocalFunctionCall call, LocalFunctionAccess receiver, LocalFunction f
) {
receiver.getParent() = call and
f = receiver.getTarget().getUnboundDeclaration()
}
private predicate lambdaCallExpr(DataFlowCall call, Expr receiver, ControlFlow::Node receiverCfn) {
exists(DelegateLikeCall dc |
call.(ExplicitDelegateLikeDataFlowCall).getCall() = dc and
receiver = dc.getExpr() and
receiverCfn = receiver.getControlFlowNode()
)
or
// In local function calls, `F()`, we use the local function access `F`
// to represent the receiver. Only needed for flow through captured variables.
exists(LocalFunctionCall fc |
receiver = fc.getAChild() and
receiverCfn = receiver.getControlFlowNode() and
fc.getControlFlowNode() = call.getControlFlowNode()
)
}
/** Holds if `call` is a lambda call where `receiver` is the lambda expression. */
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
(
lambdaCallExpr(call, receiver.asExpr(), _) and
// local function calls can be resolved directly without a flow analysis
not call.getControlFlowNode().getAstNode() instanceof LocalFunctionCall
or
receiver.(FlowSummaryNode).getSummaryNode() = call.(SummaryCall).getReceiver()
) and
exists(kind)
}
private predicate delegateCreationStep(Node nodeFrom, Node nodeTo) {
exists(DelegateCreation dc |
dc.getArgument() = nodeFrom.asExpr() and
dc = nodeTo.asExpr()
)
}
/** Extra data-flow steps needed for lambda flow analysis. */
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) {
exists(Ssa::SourceVariable v |
SsaFlow::localFlowStep(v, nodeFrom, nodeTo, _) and
preservesValue = true
|
LocalFlow::isInstanceField(v)
or
v instanceof VariableCapture::CapturedSsaSourceVariable
)
or
delegateCreationStep(nodeFrom, nodeTo) and
preservesValue = true
or
exists(AddEventExpr aee |
nodeFrom.asExpr() = aee.getRValue() and
nodeTo.asExpr().(EventRead).getTarget() = aee.getTarget() and
preservesValue = false
)
or
preservesValue = true and
exists(FieldOrProperty f, FieldOrPropertyAccess fa |
fa = f.getAnAccess() and
fa.targetIsLocalInstance()
|
exists(AssignableDefinition def |
def.getTargetAccess() = fa and
nodeFrom.asExpr() = def.getSource() and
nodeTo = TFlowInsensitiveFieldNode(f)
|
nodeFrom.getEnclosingCallable() instanceof Constructor
or
nodeFrom.getEnclosingCallable() instanceof ObjectInitMethod
)
or
nodeFrom = TFlowInsensitiveFieldNode(f) and
f.getAnAccess() = fa and
fa = nodeTo.asExpr() and
fa.(FieldOrPropertyRead).hasNonlocalValue()
)
or
VariableCapture::flowInsensitiveStep(nodeFrom, nodeTo) and
preservesValue = true
}
predicate knownSourceModel(Node source, string model) { sourceNode(source, _, model) }
predicate knownSinkModel(Node sink, string model) { sinkNode(sink, _, model) }
class DataFlowSecondLevelScope = Unit;
/**
* 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 c, ParameterPosition pos |
parameterNode(p, c, pos) and
FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(c.asSummarizedCallable(), pos)
)
or
VariableCapture::Flow::heuristicAllowInstanceParameterReturnInSelf(p.(DelegateSelfReferenceNode)
.getCallable())
or
// Allow field initializers to access Primary Constructor parameters
p.getEnclosingCallable() instanceof ObjectInitMethod
}
/** An approximated `Content`. */
class ContentApprox extends TContentApprox {
/** Gets a textual representation of this approximated `Content`. */
string toString() {
exists(string firstChar |
this = TFieldApproxContent(firstChar) and result = "approximated field " + firstChar
)
or
exists(string firstChar |
this = TPropertyApproxContent(firstChar) and result = "approximated property " + firstChar
)
or
exists(string firstChar |
this = TDynamicPropertyApproxContent(firstChar) and
result = "approximated dynamic property " + firstChar
)
or
this = TElementApproxContent() and result = "element"
or
this = TSyntheticFieldApproxContent() and result = "approximated synthetic field"
or
exists(string firstChar |
this = TPrimaryConstructorParameterApproxContent(firstChar) and
result = "approximated parameter field " + firstChar
)
or
exists(VariableCapture::CapturedVariable v |
this = TCapturedVariableContentApprox(v) and result = "captured " + v
)
or
this = TDelegateCallArgumentApproxContent() and
result = "approximated delegate call argument"
or
this = TDelegateCallReturnApproxContent() and
result = "approximated delegate call return"
}
}
/** Gets a string for approximating the name of a field. */
private string approximateFieldContent(FieldContent fc) {
result = fc.getField().getName().prefix(1)
}
/** Gets a string for approximating the name of a property. */
private string approximatePropertyContent(PropertyContent pc) {
result = pc.getProperty().getName().prefix(1)
}
/** Gets a string for approximating the name of a dynamic property. */
private string approximateDynamicPropertyContent(DynamicPropertyContent dpc) {
result = dpc.getName().prefix(1)
}
/**
* Gets a string for approximating the name of a synthetic field corresponding
* to a primary constructor parameter.
*/
private string approximatePrimaryConstructorParameterContent(PrimaryConstructorParameterContent pc) {
result = pc.getParameter().getName().prefix(1)
}
/** Gets an approximated value for content `c`. */
pragma[nomagic]
ContentApprox getContentApprox(Content c) {
result = TFieldApproxContent(approximateFieldContent(c))
or
result = TPropertyApproxContent(approximatePropertyContent(c))
or
result = TDynamicPropertyApproxContent(approximateDynamicPropertyContent(c))
or
c instanceof ElementContent and result = TElementApproxContent()
or
c instanceof SyntheticFieldContent and result = TSyntheticFieldApproxContent()
or
result =
TPrimaryConstructorParameterApproxContent(approximatePrimaryConstructorParameterContent(c))
or
result = TCapturedVariableContentApprox(VariableCapture::getCapturedVariableContent(c))
or
c instanceof DelegateCallArgumentContent and
result = TDelegateCallArgumentApproxContent()
or
c instanceof DelegateCallReturnContent and
result = TDelegateCallReturnApproxContent()
}
/**
* A module importing the modules that provide synthetic field declarations,
* ensuring that they are visible to the taint tracking / data flow library.
*/
private module SyntheticFields {
private import semmle.code.csharp.dataflow.internal.ExternalFlow
private import semmle.code.csharp.frameworks.system.threading.Tasks
private import semmle.code.csharp.frameworks.system.runtime.CompilerServices
}
/** A synthetic field. */
abstract class SyntheticField extends string {
bindingset[this]
SyntheticField() { any() }
/** Gets the type of this synthetic field. */
Type getType() { result instanceof ObjectType }
}