mirror of
https://github.com/github/codeql.git
synced 2026-03-30 20:28:15 +02:00
2958 lines
95 KiB
Plaintext
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 }
|
|
}
|