Files
codeql/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll

1443 lines
48 KiB
Plaintext

/**
* INTERNAL: Do not use.
*
* Provides logic for resolving viable run-time target callables
* (non-delegate calls only).
*/
import csharp
private import semmle.code.csharp.commons.Collections
private import RuntimeCallable
/** A call. */
class DispatchCall extends Internal::TDispatchCall {
/** Gets a textual representation of this call. */
string toString() { result = this.getCall().toString() }
/** Gets the location of this call. */
Location getLocation() { result = this.getCall().getLocation() }
/** Gets the underlying expression of this call. */
Expr getCall() { result = Internal::getCall(this) }
/** Gets the control flow node of this call. */
ControlFlowNode getControlFlowNode() { result = Internal::getControlFlowNode(this) }
/** Gets the `i`th argument of this call. */
Expr getArgument(int i) { result = Internal::getArgument(this, i) }
/** Gets the qualifier of this call, if any. */
Expr getQualifier() { result = Internal::getQualifier(this) }
/** Gets a static (compile-time) target of this call. */
Callable getAStaticTarget() { result = Internal::getAStaticTarget(this) }
/** Gets a dynamic (run-time) target of this call, if any. */
RuntimeCallable getADynamicTarget() { result = Internal::getADynamicTarget(this) }
/**
* Holds if a call context may limit the set of viable source declaration
* run-time targets of this call.
*
* This is the case if the qualifier is either a `this` access or a parameter
* access, as the corresponding qualifier/argument in the call context may
* have a more precise type.
*/
predicate mayBenefitFromCallContext() { Internal::mayBenefitFromCallContext(this) }
/**
* Gets a dynamic (run-time) target of this call in call context `ctx`, if any.
*
* This predicate is restricted to calls for which `mayBenefitFromCallContext()`
* holds.
*/
RuntimeCallable getADynamicTargetInCallContext(DispatchCall ctx) {
result = Internal::getADynamicTargetInCallContext(this, ctx)
}
/** Holds if this call uses reflection. */
predicate isReflection() {
this instanceof Internal::TDispatchReflectionCall
or
this instanceof Internal::TDispatchDynamicElementAccess
or
this instanceof Internal::TDispatchDynamicMemberAccess
or
this instanceof Internal::TDispatchDynamicMethodCall
or
this instanceof Internal::TDispatchDynamicOperatorCall
or
this instanceof Internal::TDispatchDynamicEventAccess
or
this instanceof Internal::TDispatchDynamicObjectCreation
}
}
abstract private class InstanceOperatorCall extends OperatorCall {
abstract Expr getQualifier();
}
private class InstanceCompoundAssignment extends InstanceOperatorCall instanceof CompoundAssignmentOperatorCall
{
override Expr getQualifier() { result = CompoundAssignmentOperatorCall.super.getQualifier() }
}
private class InstanceMutator extends InstanceOperatorCall instanceof InstanceMutatorOperatorCall {
override Expr getQualifier() { result = InstanceMutatorOperatorCall.super.getQualifier() }
}
/** Internal implementation details. */
private module Internal {
private import OverridableCallable
private import semmle.code.csharp.Conversion
private import semmle.code.csharp.Unification
private import semmle.code.csharp.dataflow.internal.Steps
private import semmle.code.csharp.frameworks.System
private import semmle.code.csharp.frameworks.system.Reflection
private import semmle.code.csharp.dataflow.internal.BaseSSA
cached
private module Cached {
/** Internal representation of calls. */
cached
newtype TDispatchCall =
TDispatchMethodCall(MethodCall mc) {
not isReflectionCall(mc, _, _, _, _) and
not mc.isLateBound() and
not isExtensionAccessorCall(mc)
} or
TDispatchAccessorCall(AccessorCall ac, boolean isRead) {
// For compound assignments an AccessorCall can be both a read and a write
ac instanceof AssignableRead and isRead = true
or
ac instanceof AssignableWrite and isRead = false
} or
TDispatchOperatorCall(OperatorCall oc) {
not oc.isLateBound() and
not oc instanceof InstanceOperatorCall
} or
TDispatchInstanceOperatorCall(InstanceOperatorCall ioc) or
TDispatchReflectionCall(MethodCall mc, string name, Expr object, Expr qualifier, int args) {
isReflectionCall(mc, name, object, qualifier, args)
} or
TDispatchDynamicMethodCall(DynamicMethodCall dmc) or
TDispatchDynamicOperatorCall(DynamicOperatorCall doc) or
TDispatchDynamicMemberAccess(DynamicMemberAccess dma) or
TDispatchDynamicElementAccess(DynamicElementAccess dea) or
TDispatchDynamicEventAccess(AssignArithmeticExpr aao, DynamicMemberAccess dma, string name) {
isPotentialEventCall(aao, dma, name)
} or
TDispatchDynamicObjectCreation(DynamicObjectCreation doc) or
TDispatchStaticCall(Call c) {
c = any(ObjectCreation oc | not oc.isLateBound())
or
c instanceof ConstructorInitializer
or
c instanceof LocalFunctionCall
} or
TDispatchExtensionAccessorCall(MethodCall mc) { isExtensionAccessorCall(mc) }
cached
Expr getCall(DispatchCall dc) { result = dc.(DispatchCallImpl).getCall() }
cached
ControlFlowNode getControlFlowNode(DispatchCall dc) {
result = dc.(DispatchCallImpl).getControlFlowNode()
}
cached
Expr getArgument(DispatchCall dc, int i) { result = dc.(DispatchCallImpl).getArgument(i) }
cached
Expr getQualifier(DispatchCall dc) { result = dc.(DispatchCallImpl).getQualifier() }
cached
Callable getAStaticTarget(DispatchCall dc) { result = dc.(DispatchCallImpl).getAStaticTarget() }
cached
RuntimeCallable getADynamicTarget(DispatchCall dc) {
result = dc.(DispatchCallImpl).getADynamicTarget()
}
cached
predicate mayBenefitFromCallContext(DispatchOverridableCall dc) {
dc.mayBenefitFromCallContext(_, _)
}
cached
RuntimeCallable getADynamicTargetInCallContext(DispatchOverridableCall dc, DispatchCall ctx) {
result = dc.getADynamicTargetInCallContext(ctx)
}
}
import Cached
private predicate isExtensionAccessorCall(MethodCall mc) { exists(mc.getTargetAccessor()) }
/**
* Holds if `mc` is a reflection call to a method named `name`, where
* `object` is the object on which to invoke the method (`null` if a
* static method is invoked) and `qualifier` is the qualifier of the
* reflection call.
*/
private predicate isReflectionCall(
MethodCall mc, string name, Expr object, Expr qualifier, int args
) {
exists(SystemTypeClass stc |
mc.getTarget() = stc.getInvokeMemberMethod() and
mc.getArgument(0).getValue() = name and
args = 4 and
object = mc.getArgument(3) and
qualifier = mc.getQualifier()
or
exists(MethodCall mc2 |
mc2 = getAMethodCallArgSource(mc.getQualifier()) and
mc2.getTarget() = stc.getGetMethodMethod() and
mc2.getArgument(0).getValue() = name and
object = mc.getArgument(0) and
qualifier = mc2.getQualifier() and
exists(SystemReflectionMethodBaseClass srmbc |
mc.getTarget() = srmbc.getInvokeMethod1() and args = 3
or
mc.getTarget() = srmbc.getInvokeMethod2() and args = 1
)
)
)
}
private class MethodCallArg extends Expr {
MethodCallArg() {
this = any(MethodCall mc).getQualifier() or
this = any(MethodCall mc).getAnArgument()
}
}
/**
* Gets a source for expression `e`, which is an argument in a method call,
* using simple data flow.
*/
private Expr getAMethodCallArgSource(MethodCallArg e) {
Steps::stepOpen*(result, e) and
not Steps::stepOpen(_, result)
}
/**
* Holds if `aao` is a `+=` or `-=` assignment that may potentially be
* a call to an event accessor using `dynamic` types. `dma` is the
* potential event access and `name` is the name of the relevant event
* accessor.
*/
private predicate isPotentialEventCall(
AssignArithmeticExpr aao, DynamicMemberAccess dma, string name
) {
aao instanceof DynamicOperatorCall and
dma = aao.getLeftOperand() and
(
aao instanceof AssignAddExpr and
name = "add_" + dma.getLateBoundTargetName()
or
aao instanceof AssignSubExpr and
name = "remove_" + dma.getLateBoundTargetName()
)
}
/** A call. */
abstract private class DispatchCallImpl extends TDispatchCall {
/** Gets a textual representation of this call. */
string toString() { result = this.getCall().toString() }
/** Gets the location of this call. */
Location getLocation() { result = this.getCall().getLocation() }
/** Gets the underlying expression of this call. */
abstract Expr getCall();
/** Gets the control flow node of this call. */
ControlFlowNode getControlFlowNode() { result = this.getCall().getControlFlowNode() }
/** Gets the `i`th argument of this call. */
abstract Expr getArgument(int i);
/** Gets the number of arguments of this call. */
int getNumberOfArguments() { result = count(int i | exists(this.getArgument(i))) }
/** Gets the qualifier of this call, if any. */
abstract Expr getQualifier();
/** Gets the qualifier or another expression that can be used for typing purposes, if any. */
Expr getSyntheticQualifier() { result = this.getQualifier() }
/** Gets a static (compile-time) target of this call. */
abstract Callable getAStaticTarget();
/** Gets a dynamic (run-time) target of this call, if any. */
abstract RuntimeCallable getADynamicTarget();
}
/** A non-constructed overridable callable. */
private class NonConstructedOverridableCallable extends OverridableCallable {
NonConstructedOverridableCallable() { not this instanceof ConstructedMethod }
OverridableCallable getAConstructingCallableOrSelf() {
result = this
or
result = this.(UnboundGenericMethod).getAConstructedGeneric()
}
}
pragma[noinline]
private predicate hasOverrider(Gvn::GvnType t, OverridableCallable oc) {
exists(oc.getAnOverrider(any(ValueOrRefType t0 | Gvn::getGlobalValueNumber(t0) = t)))
}
pragma[noinline]
private predicate hasCallable0(Gvn::GvnType t, OverridableCallable c, OverridableCallable source) {
c.getUnboundDeclaration() = source and
any(ValueOrRefType t0 | Gvn::getGlobalValueNumber(t0) = t).hasCallable(c) and
source = any(DispatchOverridableCall call).getAStaticTargetExt()
}
pragma[noinline]
private predicate hasCallable(Gvn::GvnType t, OverridableCallable c, OverridableCallable source) {
hasCallable0(t, c, source) and
hasOverrider(t, c)
}
/**
* For `base` expressions, the extractor provides the type of the base
* class instead of the derived class; this predicate provides the latter.
*/
private Type getBaseAdjustedType(BaseAccess base) {
result = base.getEnclosingCallable().getDeclaringType()
}
abstract private class DispatchOverridableCall extends DispatchCallImpl {
pragma[noinline]
OverridableCallable getAStaticTargetExt() {
exists(OverridableCallable target | this.getAStaticTarget() = target |
result = target.getUnboundDeclaration()
or
result = target.getAnUltimateImplementor().getUnboundDeclaration()
)
}
pragma[nomagic]
predicate hasQualifierTypeInherited(Type t) {
t = getAPossibleType(this.getSyntheticQualifier(), _)
}
pragma[noinline]
private predicate hasSubsumedQualifierType(Gvn::GvnType t) {
hasOverrider(t, _) and
exists(Type t0 |
t0 = getAPossibleType(this.getSyntheticQualifier(), false) and
not t0 instanceof TypeParameter
|
t = Gvn::getGlobalValueNumber(t0)
or
Gvn::subsumes(Gvn::getGlobalValueNumber(t0), t)
)
}
pragma[noinline]
private predicate hasConstrainedTypeParameterQualifierType(
Unification::ConstrainedTypeParameter tp
) {
tp = getAPossibleType(this.getSyntheticQualifier(), false)
}
pragma[noinline]
private predicate hasUnconstrainedTypeParameterQualifierType() {
getAPossibleType(this.getSyntheticQualifier(), false) instanceof
Unification::UnconstrainedTypeParameter
}
pragma[nomagic]
predicate hasSubsumedQualifierTypeOverridden(Gvn::GvnType t, OverridableCallable c) {
this.hasSubsumedQualifierType(t) and
hasCallable(t, c, this.getAStaticTargetExt())
}
/**
* Holds if a call context may limit the set of viable source declaration
* run-time targets of this call.
*
* This is the case if the qualifier is either a `this` access or a parameter
* access, as the corresponding qualifier/argument in the call context may
* have a more precise type.
*/
predicate mayBenefitFromCallContext(Callable c, int i) {
1 < strictcount(this.getADynamicTarget().getUnboundDeclaration()) and
c = this.getCall().getEnclosingCallable().getUnboundDeclaration() and
(
exists(BaseSsa::SsaParameterInit def, Parameter p |
def.getParameter() = p and
this.getSyntheticQualifier() = def.getARead() and
p.getPosition() = i and
c.getAParameter() = p and
not p.isParams()
)
or
i = -1 and
this.getQualifier() instanceof ThisAccess
)
}
/**
* Holds if the call `ctx` might act as a context that improves the set of
* dispatch targets of this call, depending on the type of the `i`th argument
* of `ctx`.
*/
pragma[nomagic]
private predicate relevantContext(DispatchCall ctx, int i) {
this.mayBenefitFromCallContext(ctx.getADynamicTarget().getUnboundDeclaration(), i)
}
/**
* Holds if the argument of `ctx`, which is passed for the parameter that is
* accessed in the qualifier of this call, has type `t` and `ctx` is a relevant
* call context.
*/
private predicate contextArgHasType(DispatchCall ctx, Type t, boolean isExact) {
exists(Expr arg, int i |
this.relevantContext(ctx, i) and
(
t = getBaseAdjustedType(arg) and isExact = false
or
not exists(getBaseAdjustedType(arg)) and
t = getAPossibleType(arg, isExact)
)
|
ctx.getArgument(i) = arg
or
ctx.getQualifier() = arg and
i = -1
)
}
pragma[nomagic]
private predicate contextArgHasConstrainedTypeParameterType(
DispatchCall ctx, Unification::ConstrainedTypeParameter tp
) {
this.contextArgHasType(ctx, tp, false)
}
pragma[nomagic]
private predicate contextArgHasUnconstrainedTypeParameterType(DispatchCall ctx) {
this.contextArgHasType(ctx, any(Unification::UnconstrainedTypeParameter t), false)
}
pragma[nomagic]
private predicate contextArgHasNonTypeParameterType(DispatchCall ctx, Gvn::GvnType t) {
exists(Type t0 |
this.contextArgHasType(ctx, t0, false) and
not t0 instanceof TypeParameter and
t = Gvn::getGlobalValueNumber(t0)
)
}
pragma[nomagic]
private Callable getASubsumedStaticTarget0(Gvn::GvnType t) {
exists(Callable staticTarget, Type declType |
staticTarget = this.getAStaticTarget() and
declType = staticTarget.getDeclaringType() and
result = staticTarget.getUnboundDeclaration() and
Gvn::subsumes(Gvn::getGlobalValueNumber(declType), t)
)
}
/**
* Gets a callable whose source declaration matches the source declaration of
* some static target `target`, and whose declaring type is subsumed by the
* declaring type of `target`.
*/
pragma[nomagic]
private Callable getASubsumedStaticTarget() {
result = this.getAStaticTarget()
or
result.getUnboundDeclaration() =
this.getASubsumedStaticTarget0(pragma[only_bind_out](Gvn::getGlobalValueNumber(result
.getDeclaringType())))
}
/**
* Gets a callable inherited by (or defined in) the qualifier type of this
* call that overrides (or equals) a static target of this call.
*
* Example:
*
* ```csharp
* class A
* {
* public virtual void M() { }
* }
*
* class B : A
* {
* public override void M() { }
* }
*
* class C : B { }
*
* class D
* {
* void CallM()
* {
* A x = new A();
* x.M();
* x = new B();
* x.M();
* x = new C();
* x.M();
* }
* }
* ```
*
* The static target is `A.M` in all three calls on lines 14, 16, and 18,
* but the methods inherited by the actual qualifier types are `A.M`,
* `B.M`, and `B.M`, respectively.
*/
private RuntimeCallable getAViableInherited() {
exists(NonConstructedOverridableCallable c, Type t | this.hasQualifierTypeInherited(t) |
this.getASubsumedStaticTarget() = c.getAConstructingCallableOrSelf() and
result = c.getInherited(t)
or
t instanceof TypeParameter and
this.getAStaticTarget() = c.getAConstructingCallableOrSelf() and
result = c
)
}
pragma[noinline]
NonConstructedOverridableCallable getAViableOverrider0() {
getAPossibleType(this.getSyntheticQualifier(), false) instanceof TypeParameter and
result.getAConstructingCallableOrSelf() = this.getAStaticTargetExt()
}
/**
* Gets a callable that is defined in a subtype of the qualifier type of this
* call, and which overrides a static target of this call.
*
* Example:
*
* ```csharp
* class A
* {
* public virtual void M() { }
* }
*
* class B : A
* {
* public override void M() { }
* }
*
* class C : B
* {
* public override void M() { }
* }
*
* class D
* {
* void CallM()
* {
* A x = new A();
* x.M();
* x = new B();
* x.M();
* x = new C();
* x.M();
* }
* }
* ```
*
* The static target is `A.M` in all three calls on lines 16, 18, and 20,
* but the methods overriding the static targets in subtypes of the actual
* qualifier types are `B.M` and `C.M`, `C.M`, and none, respectively.
*/
private RuntimeCallable getAViableOverrider() {
exists(ValueOrRefType t, NonConstructedOverridableCallable c |
this.hasSubsumedQualifierTypeOverridden(Gvn::getGlobalValueNumber(t),
c.getAConstructingCallableOrSelf()) and
result = c.getAnOverrider(t)
)
or
exists(NonConstructedOverridableCallable c, NonConstructedOverridableCallable mid |
c = this.getAViableOverrider0() and
c = mid.getUnboundDeclaration() and
result = mid.getAnOverrider(_)
|
this.hasUnconstrainedTypeParameterQualifierType()
or
exists(Unification::ConstrainedTypeParameter tp |
this.hasConstrainedTypeParameterQualifierType(tp) and
tp.subsumes(result.getDeclaringType())
)
)
}
override RuntimeCallable getADynamicTarget() {
result = this.getAViableInherited()
or
result = this.getAViableOverrider()
or
// Simple case: target method cannot be overridden
result = this.getAStaticTarget() and
not result instanceof OverridableCallable
}
pragma[nomagic]
private RuntimeCallable getAViableInheritedInCallContext0(ValueOrRefType t) {
this.contextArgHasType(_, t, _) and
result = this.getADynamicTarget()
}
pragma[nomagic]
private RuntimeCallable getAViableInheritedInCallContext1(
NonConstructedOverridableCallable c, ValueOrRefType t
) {
result = this.getAViableInheritedInCallContext0(t) and
result = c.getInherited(t)
}
pragma[nomagic]
private RuntimeCallable getAViableInheritedInCallContext(DispatchCall ctx) {
exists(Type t, NonConstructedOverridableCallable c | this.contextArgHasType(ctx, t, _) |
this.getASubsumedStaticTarget() = c.getAConstructingCallableOrSelf() and
result = this.getAViableInheritedInCallContext1(c, t)
or
t instanceof TypeParameter and
this.getAStaticTarget() = c.getAConstructingCallableOrSelf() and
result = c
)
}
pragma[nomagic]
private RuntimeCallable getAViableOverriderInCallContext0(Gvn::GvnType t) {
exists(NonConstructedOverridableCallable c |
result = this.getAViableOverrider() and
this.contextArgHasType(_, _, false) and
result = c.getAnOverrider(any(Type t0 | t = Gvn::getGlobalValueNumber(t0))) and
this.getAStaticTarget() = c.getAConstructingCallableOrSelf()
)
}
pragma[nomagic]
private predicate contextArgHasSubsumedType(DispatchCall ctx, Gvn::GvnType t) {
hasOverrider(t, _) and
exists(Gvn::GvnType t0 | this.contextArgHasNonTypeParameterType(ctx, t0) |
t = t0
or
Gvn::subsumes(t0, t)
)
}
pragma[nomagic]
private RuntimeCallable getAViableOverriderInCallContext(DispatchCall ctx) {
exists(Gvn::GvnType t |
result = this.getAViableOverriderInCallContext0(t) and
this.contextArgHasSubsumedType(ctx, t)
)
or
result = this.getAViableOverrider() and
(
this.contextArgHasUnconstrainedTypeParameterType(ctx)
or
exists(Unification::ConstrainedTypeParameter tp |
this.contextArgHasConstrainedTypeParameterType(ctx, tp) and
tp.subsumes(result.getDeclaringType())
)
)
}
RuntimeCallable getADynamicTargetInCallContext(DispatchCall ctx) {
result = this.getAViableInheritedInCallContext(ctx)
or
result = this.getAViableOverriderInCallContext(ctx)
}
}
private class DynamicFieldOrProperty extends Assignable {
DynamicFieldOrProperty() {
(
this instanceof Field or
this instanceof Property
) and
this.getName() = any(DynamicMemberAccess dma).getLateBoundTargetName()
}
predicate isMemberOf(string name, ValueOrRefType t) {
name = this.getName() and t.hasMember(this)
}
}
private class TypeWithDynamicFieldOrProperty extends ValueOrRefType {
DynamicFieldOrProperty fp;
TypeWithDynamicFieldOrProperty() { fp.isMemberOf(_, this) }
predicate isImplicitlyConvertibleTo(string name, Type t) {
name = fp.getName() and
this.isImplicitlyConvertibleTo(t)
}
}
pragma[noinline]
private predicate isPossibleDynamicMemberAccessQualifierType(
DynamicMemberAccess dma, string name, TypeWithDynamicFieldOrProperty t
) {
exists(Type qt, boolean isExact |
qt = getAPossibleType(dma.getQualifier(), isExact) and
name = dma.getLateBoundTargetName()
|
isExact = true and t = qt
or
isExact = false and t.isImplicitlyConvertibleTo(name, qt)
)
}
/**
* Gets a possible type for expression `e`. Simple flow is used to track the
* origin of `e`, and in case `e` is a dynamic member access, only types
* corresponding to the type of a relevant field or property are included.
*/
private Type getAPossibleType(Expr e, boolean isExact) {
exists(DynamicFieldOrProperty fp, string name, TypeWithDynamicFieldOrProperty t |
isPossibleDynamicMemberAccessQualifierType(e, name, t) and
fp.isMemberOf(name, t)
|
result = fp.getType() and
isExact = false
)
or
not e instanceof DynamicMemberAccess and
result = getASourceType(e, isExact)
}
/**
* Provides the predicate `getASourceType()` for finding all relevant source
* types for a given expression.
*/
private module SimpleTypeDataFlow {
/**
* Holds if type `t` may be imprecise, that is, an expression of type `t` may
* in fact have a more precise type.
*/
private predicate typeMayBeImprecise(Type t) {
t.containsTypeParameters()
or
t.getAChild*() instanceof DynamicType
or
exists(Type other | other != t |
not other instanceof DynamicType and
other.isImplicitlyConvertibleTo(t)
)
}
private predicate downCast(Cast c) {
exists(Type fromType, Type toType |
fromType = c.getExpr().getType() and
toType = c.getType() and
not fromType.isImplicitlyConvertibleTo(toType) and
not toType instanceof DynamicType
)
}
private predicate stepExpr(Expr pred, Expr succ) {
Steps::stepOpen(pred, succ) and
// Do not step through down casts
not downCast(succ) and
// Only step when we may learn more about the actual type
typeMayBeImprecise(succ.getType())
}
private class AnalyzableFieldOrProperty extends Assignable, Modifiable {
AnalyzableFieldOrProperty() {
(
this instanceof Field or
this instanceof Property
) and
not this.isEffectivelyPublic() and
exists(this.getAnAssignedValue())
}
AssignableRead getARead() { result = this.getAnAccess() }
}
private class Source extends Expr {
Source() {
not stepExpr(_, this) and
not this = any(AnalyzableFieldOrProperty a).getARead()
}
Type getType(boolean isExact) {
result = this.getType() and
if this instanceof ObjectCreation or this instanceof BaseAccess
then isExact = true
else isExact = false
}
}
private class Sink extends Expr {
Sink() {
this = any(DynamicMemberAccess dma | dma instanceof Sink).getQualifier()
or
this = any(AccessorCall ac).getAnArgument()
or
this = any(DispatchReflectionOrDynamicCall c).getArgument(_)
or
this =
any(MethodCall mc | mc.getTarget() = any(SystemObjectClass c).getGetTypeMethod())
.getQualifier()
or
this = any(DispatchCallImpl c).getQualifier()
or
this = any(DispatchCallImpl c).getArgument(_)
}
pragma[nomagic]
Expr getAPred() { stepExpr*(result, this) }
pragma[nomagic]
AnalyzableFieldOrProperty getAPredRead() { this.getAPred() = result.getARead() }
}
/** Gets a source type for sink expression `e`, using simple data flow. */
Type getASourceType(Sink sink, boolean isExact) {
result = sink.getAPred().(Source).getType(isExact)
or
result = sink.getAPredRead().(RelevantFieldOrProperty).getASourceType(isExact)
}
private class RelevantFieldOrProperty extends AnalyzableFieldOrProperty {
RelevantFieldOrProperty() {
this = any(Sink s).getAPredRead()
or
this = any(RelevantFieldOrProperty a).getAPredRead()
}
pragma[nomagic]
Expr getAPred() { stepExpr*(result, this.getAnAssignedValue()) }
pragma[nomagic]
AnalyzableFieldOrProperty getAPredRead() { this.getAPred() = result.getARead() }
Type getASourceType(boolean isExact) {
result = this.getAPred().(Source).getType(isExact)
or
result = this.getAPredRead().(RelevantFieldOrProperty).getASourceType(isExact)
}
}
}
private import SimpleTypeDataFlow
/**
* An ordinary method call.
*
* The set of viable targets is determined by taking virtual dispatch
* into account.
*/
private class DispatchMethodCall extends DispatchOverridableCall, TDispatchMethodCall {
override MethodCall getCall() { this = TDispatchMethodCall(result) }
override Expr getArgument(int i) {
exists(Call call, Parameter p | call = this.getCall() |
p = call.getTarget().getParameter(i) and
result = call.getArgumentForParameter(p)
)
}
override Expr getQualifier() { result = this.getCall().getQualifier() }
override Method getAStaticTarget() { result = this.getCall().getTarget() }
}
/**
* A call to an extension accessor method.
*/
private class DispatchExtensionAccessorCall extends DispatchCallImpl,
TDispatchExtensionAccessorCall
{
override MethodCall getCall() { this = TDispatchExtensionAccessorCall(result) }
private Expr getArgumentForParameter(Parameter p) {
this.getCall().getTargetAccessor().getAParameter() = p and
result = this.getCall().getArgument(p.getPosition())
}
override Expr getArgument(int i) {
exists(MethodCall call, Parameter p | call = this.getCall() |
p = call.getTargetAccessor().getParameter(i) and
result = this.getArgumentForParameter(p)
)
}
override Expr getQualifier() { result = this.getCall().getQualifier() }
override Accessor getAStaticTarget() { result = this.getCall().getTargetAccessor() }
override RuntimeCallable getADynamicTarget() { result = this.getAStaticTarget() }
}
/**
* An ordinary operator call.
*
* The set of viable targets is determined by taking virtual dispatch
* into account.
*/
private class DispatchOperatorCall extends DispatchOverridableCall, TDispatchOperatorCall {
override OperatorCall getCall() { this = TDispatchOperatorCall(result) }
override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
/**
* Gets the first child expression of an operator call, which can be considered the qualifier
* expression for the dispatch call use-cases.
*/
override Expr getSyntheticQualifier() { result = this.getCall().getChildExpr(0) }
override Expr getQualifier() { none() }
override Operator getAStaticTarget() { result = this.getCall().getTarget() }
}
private class DispatchInstanceOperatorCall extends DispatchOverridableCall,
TDispatchInstanceOperatorCall
{
override InstanceOperatorCall getCall() { this = TDispatchInstanceOperatorCall(result) }
override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
override Expr getQualifier() { result = this.getCall().getQualifier() }
override Operator getAStaticTarget() { result = this.getCall().getTarget() }
}
/**
* A call to an accessor.
*
* The set of viable targets is determined by taking virtual dispatch
* into account.
*/
private class DispatchAccessorCall extends DispatchOverridableCall, TDispatchAccessorCall {
private predicate isRead() { this = TDispatchAccessorCall(_, true) }
override ControlFlowNode getControlFlowNode() {
if this.isRead()
then result = this.getCall().getControlFlowNode()
else
exists(AssignableDefinition def |
def.getTargetAccess() = this.getCall() and result = def.getExpr().getControlFlowNode()
)
}
override AccessorCall getCall() { this = TDispatchAccessorCall(result, _) }
override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
override Expr getQualifier() { result = this.getCall().(MemberAccess).getQualifier() }
override Accessor getAStaticTarget() {
if this.isRead()
then result = this.getCall().getReadTarget()
else result = this.getCall().getWriteTarget()
}
override RuntimeAccessor getADynamicTarget() {
result = DispatchOverridableCall.super.getADynamicTarget() and
// Calls to accessors may have `dynamic` expression arguments,
// so we need to check that the types match
forall(Type argumentType, int i | this.hasDynamicArg(i, argumentType) |
argumentType.isImplicitlyConvertibleTo(result.getParameter(i).getType())
)
}
private predicate hasDynamicArg(int i, Type argumentType) {
exists(Expr argument |
argument = this.getArgument(i) and
argument.stripImplicit().getType() instanceof DynamicType and
argumentType = getAPossibleType(argument, _)
)
}
}
/** A reflection-based call or a call using dynamic types. */
abstract private class DispatchReflectionOrDynamicCall extends DispatchCallImpl {
/** Gets the name of the callable being called in this call. */
abstract string getName();
pragma[nomagic]
private predicate hasQualifierType(Type qualifierType, boolean isExactType) {
exists(Type t | t = getAPossibleType(this.getSyntheticQualifier(), isExactType) |
qualifierType = t and
not t instanceof TypeParameter
or
Unification::subsumes(t, qualifierType)
or
qualifierType = t.(TypeParameter).getAnUltimatelySuppliedType()
)
}
/**
* Gets a potential qualifier type of this call.
*
* For reflection/dynamic calls, unless the type of the qualifier is exact,
* all subtypes of the qualifier type must be considered relevant. Example:
*
* ```csharp
* class A
* {
* public void M() { Console.WriteLine("A"); }
* }
*
* class B : A
* {
* new public void M() { Console.WriteLine("B"); }
* }
*
* class C
* {
* void InvokeMDyn(A x) { ((dynamic) x).M(); }
*
* void CallM() {
* InvokeMDyn(new A()); // prints "A"
* InvokeMDyn(new B()); // prints "B"
* }
* }
* ```
*
* In `InvokeMDyn()` on line 10, the (non-exact) type of `x` is `A`, but
* if we only consider methods that override the method `M` in `A`, then
* `B.M` will not be included (`M` is not a virtual method). Consequently,
* we must include `B` as a possible qualifier type as well.
*
* This predicate only returns a type when `hasUnknownQualifierType()`
* does not hold.
*/
private RefType getAQualifierType() {
// Exact qualifier type
this.hasQualifierType(result, true)
or
// Non-exact qualifier type
exists(Type qualifierType | this.hasQualifierType(qualifierType, false) |
result = getANonExactQualifierSubType(qualifierType)
)
}
/**
* Holds if the qualifier type is unknown (it is either `object` or
* `dynamic`).
*/
private predicate hasUnknownQualifierType() {
exists(Type qualifierType | this.hasQualifierType(qualifierType, false) |
isUnknownType(qualifierType)
)
}
// The set of static targets is all callables with matching
// names and number of parameters. This set is further reduced in
// `getADynamicTarget()` by taking type information into account.
override Callable getAStaticTarget() {
result = this.getACallableWithMatchingName() and
exists(int minArgs |
minArgs =
count(Parameter p |
p = result.getAParameter() and
not p.hasDefaultValue() and
not p.isParams()
) and
this.getNumberOfArguments() >= minArgs and
(
result.(Method).hasParams() or
this.getNumberOfArguments() <= result.getNumberOfParameters()
)
)
}
private RuntimeCallable getACallableWithMatchingName() {
result.(Operator).getFunctionName() = this.getName()
or
not result instanceof Operator and
result.getUndecoratedName() = this.getName()
}
// A callable is viable if the following conditions are all satisfied:
//
// 1. It is a viable candidate (see `getADynamicTargetCandidate()`).
// 2. The argument types are compatible with the parameter types. Here,
// type compatibility means that an argument type must be implicitly
// convertible to a type that equals the corresponding parameter type
// modulo type parameters. For example, an argument type `int` is
// compatible with a parameter type `IEquatable<T>`, because `int` is
// implicitly convertible to `IEquatable<int>`, which equals
// `IEquatable<T>` modulo type parameters. Note that potential type
// parameter constraints are not taken into account, nor is the
// possibility of matching a given type parameter with multiple,
// conflicting types (for example, `Tuple<int, string>` is considered
// compatible with `Tuple<T, T>`).
override RuntimeCallable getADynamicTarget() {
result = this.getADynamicTarget(this.getNumberOfArguments() - 1)
}
private RuntimeCallable getADynamicTarget(int i) {
i = -1 and
result = this.getADynamicTargetCandidate()
or
result = this.getADynamicTarget(i - 1) and
exists(Type parameterType, Type argumentType |
parameterType = this.getAParameterType(result, i) and
argumentType = getAPossibleType(this.getArgument(i), _)
|
argumentType.isImplicitlyConvertibleTo(parameterType)
or
argumentType instanceof NullType and
result.getParameter(i).isOut() and
parameterType instanceof SimpleType
or
reflectionOrDynamicArgEqualsParamModuloTypeParameters(argumentType, parameterType)
)
or
result = this.getADynamicTarget(i - 1) and
exists(Type parameterType, Type t | parameterType = this.getAParameterType(result, i) |
this.argumentConvConstExpr(i, t) and
t.isImplicitlyConvertibleTo(parameterType)
)
}
private Type getAParameterType(RuntimeCallable c, int i) {
exists(this.getArgument(i)) and
c = this.getADynamicTargetCandidate() and
(
result = c.getParameter(i).getType()
or
i >= c.getNumberOfParameters() - 1 and
result = c.(Method).getParamsType()
)
}
pragma[noinline]
private predicate argumentConvConstExpr(int i, Type t) {
convConstantExpr(this.getArgument(i), t)
}
/**
* Gets a candidate for a viable run-time callable.
*
* A candidate must fulfill the following conditions:
*
* - It has the right name.
* - It is an instance method defined in (a subtype of) the type of
* the qualifier, it is an instance accessor defined in (a subtype
* of) the type of the qualifier, or it is an operator defined in the
* type of one of the arguments.
*/
RuntimeCallable getADynamicTargetCandidate() {
result = this.getAStaticTarget() and
(
result = getADynamicTargetCandidateInstanceMethod(this.getAQualifierType())
or
result instanceof RuntimeInstanceMethod and
this.hasUnknownQualifierType()
or
result = getADynamicTargetCandidateInstanceAccessor(this.getAQualifierType())
or
result instanceof RuntimeInstanceAccessor and
this.hasUnknownQualifierType()
or
result = this.getADynamicTargetCandidateOperator()
)
}
pragma[noinline]
private RuntimeOperator getADynamicTargetCandidateOperator() {
result = this.getAStaticTarget() and
result.getDeclaringType() = result.getAParameter().getType()
}
}
pragma[noinline]
private RuntimeInstanceMethod getADynamicTargetCandidateInstanceMethod(RefType qualifierType) {
exists(DispatchReflectionOrDynamicCall c |
result = c.getAStaticTarget() and
qualifierType.hasMethod(result)
)
}
pragma[noinline]
private RuntimeInstanceAccessor getADynamicTargetCandidateInstanceAccessor(RefType qualifierType) {
exists(DispatchReflectionOrDynamicCall c |
result = c.getAStaticTarget() and
qualifierType.hasCallable(result)
)
}
private predicate isUnknownType(RefType t) {
t instanceof DynamicType or
t instanceof ObjectType
}
pragma[noinline]
private RefType getANonExactQualifierSubType(Type qualifierType) {
isRelevantReflectionOrDynamicQualifierType(result) and
result.isImplicitlyConvertibleTo(qualifierType) and
not isUnknownType(qualifierType) and
not result instanceof NullType // null types are implicitly convertible to all types, but not actual subtypes
}
pragma[noinline]
private predicate isRelevantReflectionOrDynamicQualifierType(RefType rt) {
rt.hasCallable(any(DispatchReflectionOrDynamicCall c).getAStaticTarget())
}
private predicate reflectionOrDynamicArgEqualsParamModuloTypeParameters(
Type argumentType, Type parameterType
) {
exists(Type t |
reflectionOrDynamicArgumentTypeIsImplicitlyConvertibleTo(argumentType, t) and
isReflectionOrDynamicCallArgumentWithTypeParameters(t, parameterType)
|
parameterType = t
or
t instanceof Unification::UnconstrainedTypeParameter
or
parameterType instanceof Unification::UnconstrainedTypeParameter
or
t.(Unification::ConstrainedTypeParameter).unifiable(parameterType)
or
parameterType.(Unification::ConstrainedTypeParameter).unifiable(t)
or
Unification::unifiable(parameterType, t)
)
}
pragma[noinline]
private predicate reflectionOrDynamicArgumentTypeIsImplicitlyConvertibleTo(
Type argumentType, Type t
) {
isReflectionOrDynamicArgumentType(argumentType) and
argumentType.isImplicitlyConvertibleTo(t)
}
pragma[noinline]
private predicate isReflectionOrDynamicArgumentType(Type t) {
exists(DispatchReflectionOrDynamicCall c | t = getAPossibleType(c.getArgument(_), _))
}
/**
* Holds if `argType` is the argument type of a reflection-based call
* or a call using dynamic types, where either `argType` contains
* type parameters, or the relevant parameter type `paramType` of a
* potential run-time target contains type parameters.
*/
private predicate isReflectionOrDynamicCallArgumentWithTypeParameters(Type argType, Type paramType) {
exists(DispatchReflectionOrDynamicCall call, Parameter p, int i, int j |
p = call.getADynamicTargetCandidate().getParameter(i) and
(
if p.isParams()
then (
j >= i and
paramType = p.getType().(ParamsCollectionType).getElementType()
) else (
i = j and
paramType = p.getType()
)
) and
argType = getAPossibleType(call.getArgument(j), _)
|
argType.containsTypeParameters()
or
paramType.containsTypeParameters()
)
}
/** A call using reflection. */
private class DispatchReflectionCall extends DispatchReflectionOrDynamicCall,
TDispatchReflectionCall
{
override MethodCall getCall() { this = TDispatchReflectionCall(result, _, _, _, _) }
override string getName() { this = TDispatchReflectionCall(_, result, _, _, _) }
override Expr getQualifier() {
this = TDispatchReflectionCall(_, _, result, _, _) and
not result instanceof NullLiteral
}
/** Gets the type containing the static callable being called, if any. */
private ValueOrRefType getStaticType() {
exists(Expr object, Expr qualifier |
this = TDispatchReflectionCall(_, _, object, qualifier, _) and
object instanceof NullLiteral and
(
result = getAMethodCallArgSource(qualifier).(TypeofExpr).getTypeAccess().getType()
or
exists(MethodCall mc |
mc = getAMethodCallArgSource(qualifier) and
mc.getTarget() = any(SystemObjectClass c).getGetTypeMethod() and
result = getAPossibleType(mc.getQualifier(), _)
)
)
)
}
override RuntimeCallable getADynamicTargetCandidate() {
result = DispatchReflectionOrDynamicCall.super.getADynamicTargetCandidate()
or
// Static callables can be called using reflection as well
result = this.getAStaticTarget() and
result.getDeclaringType() = this.getStaticType() and
result.(Modifiable).isStatic()
}
// Does not take named arguments into account
override Expr getArgument(int i) {
exists(int args, ArrayCreation ac |
this = TDispatchReflectionCall(_, _, _, _, args) and
ac = getAMethodCallArgSource(this.getCall().getArgument(args)) and
result = ac.getInitializer().getElement(i)
)
}
}
/** A method call using dynamic types. */
private class DispatchDynamicMethodCall extends DispatchReflectionOrDynamicCall,
TDispatchDynamicMethodCall
{
override DynamicMethodCall getCall() { this = TDispatchDynamicMethodCall(result) }
override string getName() { result = this.getCall().getLateBoundTargetName() }
override Expr getQualifier() { result = this.getCall().getQualifier() }
override RuntimeMethod getADynamicTargetCandidate() {
if exists(this.getCall().getTarget())
then
// static method call
result = this.getCall().getTarget()
else result = DispatchReflectionOrDynamicCall.super.getADynamicTargetCandidate()
}
// Does not take named arguments into account
override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
}
/** An operator call using dynamic types. */
private class DispatchDynamicOperatorCall extends DispatchReflectionOrDynamicCall,
TDispatchDynamicOperatorCall
{
override DynamicOperatorCall getCall() { this = TDispatchDynamicOperatorCall(result) }
override string getName() {
exists(Operator o |
o.getName() = this.getCall().getLateBoundTargetName() and
result = o.getFunctionName()
)
}
override Expr getQualifier() { none() }
override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
}
/** A (potential) call to a property accessor using dynamic types. */
private class DispatchDynamicMemberAccess extends DispatchReflectionOrDynamicCall,
TDispatchDynamicMemberAccess
{
override DynamicMemberAccess getCall() { this = TDispatchDynamicMemberAccess(result) }
override string getName() {
exists(DynamicMemberAccess dma | dma = this.getCall() |
result = "get_" + dma.(DynamicMemberRead).getLateBoundTargetName()
or
result = "set_" + dma.(DynamicMemberWrite).getLateBoundTargetName()
)
}
override Expr getQualifier() { result = this.getCall().getQualifier() }
override Expr getArgument(int i) {
exists(DynamicMemberAccess dma | dma = this.getCall() |
// Only calls to setters have an argument
i = 0 and
exists(AssignableDefinition def | def.getTargetAccess() = dma | result = def.getSource())
)
}
}
/** A (potential) call to an indexer accessor using dynamic types. */
private class DispatchDynamicElementAccess extends DispatchReflectionOrDynamicCall,
TDispatchDynamicElementAccess
{
override DynamicElementAccess getCall() { this = TDispatchDynamicElementAccess(result) }
override string getName() {
exists(DynamicElementAccess dea | dea = this.getCall() |
dea instanceof DynamicElementRead and result = "get_Item"
or
dea instanceof DynamicElementWrite and result = "set_Item"
)
}
override Expr getQualifier() { result = this.getCall().getQualifier() }
override Expr getArgument(int i) {
exists(DynamicElementAccess dea | dea = this.getCall() |
result = dea.getIndex(i)
or
// Calls to setters have an extra argument
i = count(dea.getAnIndex()) and
exists(AssignableDefinition def | def.getTargetAccess() = dea | result = def.getSource())
)
}
}
/** A (potential) call to an event accessor using dynamic types. */
private class DispatchDynamicEventAccess extends DispatchReflectionOrDynamicCall,
TDispatchDynamicEventAccess
{
override AssignArithmeticExpr getCall() { this = TDispatchDynamicEventAccess(result, _, _) }
override string getName() { this = TDispatchDynamicEventAccess(_, _, result) }
override Expr getQualifier() {
result =
any(DynamicMemberAccess dma | this = TDispatchDynamicEventAccess(_, dma, _)).getQualifier()
}
override Expr getArgument(int i) { i = 0 and result = this.getCall().getRightOperand() }
}
/** A call to a constructor using dynamic types. */
private class DispatchDynamicObjectCreation extends DispatchReflectionOrDynamicCall,
TDispatchDynamicObjectCreation
{
override DynamicObjectCreation getCall() { this = TDispatchDynamicObjectCreation(result) }
override string getName() { none() }
override Expr getQualifier() { none() }
override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
override RuntimeCallable getADynamicTargetCandidate() { result = this.getCall().getTarget() }
}
/** A call where the target can be resolved statically. */
private class DispatchStaticCall extends DispatchCallImpl, TDispatchStaticCall {
override Call getCall() { this = TDispatchStaticCall(result) }
override Expr getQualifier() { none() }
override Expr getArgument(int i) {
exists(Call call, Parameter p | call = this.getCall() |
p = call.getTarget().getParameter(i) and
result = call.getArgumentForParameter(p)
)
}
override Callable getAStaticTarget() { result = this.getCall().getTarget() }
override RuntimeCallable getADynamicTarget() { result = this.getCall().getTarget() }
}
}