Ruby: Rework call-context sensitivity logic

This commit is contained in:
Tom Hvitved
2022-11-29 13:55:50 +01:00
parent f3dca95958
commit 86e045916d

View File

@@ -975,34 +975,26 @@ private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string
result = trackSingletonMethodOnInstance(method, name, TypeTracker::end())
}
/** Same as `isInstance`, but includes local must-flow through SSA definitions. */
private predicate isInstanceLocalMustFlow(DataFlow::Node n, Module tp, boolean exact) {
isInstance(n, tp, exact)
or
exists(DataFlow::Node mid | isInstanceLocalMustFlow(mid, tp, exact) |
n.asExpr() = mid.(SsaDefinitionNode).getDefinition().getARead()
or
n.(SsaDefinitionNode).getDefinition().(Ssa::WriteDefinition).assigns(mid.asExpr())
)
}
/**
* Holds if `ctx` targets `encl`, which is the enclosing callable of `call`, the receiver
* of `call` is a parameter access, where the corresponding argument of `ctx` is `arg`.
*
* `name` is the name of the method being called by `call`.
* `name` is the name of the method being called by `call`, `source` is a
* `LocalSourceNode` that flows to `arg`, and `paramDef` is the SSA definition for the
* parameter that is the receiver of `call`.
*/
pragma[nomagic]
private predicate argFlowsToReceiver(
RelevantCall ctx, ArgumentNode arg, RelevantCall call, Callable encl, string name
private predicate argMustFlowToReceiver(
RelevantCall ctx, DataFlow::LocalSourceNode source, ArgumentNode arg, SsaDefinitionNode paramDef,
RelevantCall call, Callable encl, string name
) {
exists(
ParameterNodeImpl p, SsaDefinitionNode ssaNode, ParameterPosition ppos, ArgumentPosition apos
|
exists(ParameterNodeImpl p, ParameterPosition ppos, ArgumentPosition apos |
// the receiver of `call` references `p`
LocalFlow::localFlowSsaParamInput(p, ssaNode) and
flowsToMethodCallReceiver(pragma[only_bind_into](call), pragma[only_bind_into](ssaNode),
pragma[only_bind_into](name)) and
exists(DataFlow::Node receiver |
LocalFlow::localFlowSsaParamInput(p, paramDef) and
methodCall(pragma[only_bind_into](call), receiver, pragma[only_bind_into](name)) and
receiver.asExpr() = paramDef.getDefinition().getARead()
) and
// `p` is a parameter of `encl`,
encl = call.getScope() and
p.isParameterOf(TCfgScope(encl), ppos) and
@@ -1010,7 +1002,8 @@ private predicate argFlowsToReceiver(
getTarget(ctx) = encl and
// `arg` is the argument for `p` in the call `ctx`
arg.sourceArgumentOf(ctx, apos) and
parameterMatch(ppos, apos)
parameterMatch(ppos, apos) and
source.flowsTo(arg)
)
}
@@ -1027,20 +1020,11 @@ private predicate mayBenefitFromCallContextInstance(
RelevantCall ctx, RelevantCall call, ArgumentNode arg, Callable encl, Module tp, boolean exact,
string name
) {
argFlowsToReceiver(ctx, pragma[only_bind_into](arg), call, encl, pragma[only_bind_into](name)) and
// `arg` has a relevant instance type
isInstanceLocalMustFlow(arg, tp, exact) and
exists(lookupMethod(tp, pragma[only_bind_into](name)))
}
/** Same as `resolveConstantReadAccess`, but includes local must-flow through SSA definitions. */
private predicate resolveConstantReadAccessMustFlow(DataFlow::Node n, Module tp) {
tp = resolveConstantReadAccess(n.asExpr().getExpr())
or
exists(DataFlow::Node mid | resolveConstantReadAccessMustFlow(mid, tp) |
n.asExpr() = mid.(SsaDefinitionNode).getDefinition().getARead()
or
n.(SsaDefinitionNode).getDefinition().(Ssa::WriteDefinition).assigns(mid.asExpr())
exists(DataFlow::LocalSourceNode source |
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, _, call, encl,
pragma[only_bind_into](name)) and
source = trackInstance(tp, exact) and
exists(lookupMethod(tp, pragma[only_bind_into](name)))
)
}
@@ -1057,10 +1041,12 @@ private predicate mayBenefitFromCallContextSingleton(
RelevantCall ctx, RelevantCall call, ArgumentNode arg, Callable encl, Module tp, boolean exact,
string name
) {
argFlowsToReceiver(ctx, pragma[only_bind_into](arg), call, encl, pragma[only_bind_into](name)) and
// `arg` has a relevant module type
(
resolveConstantReadAccessMustFlow(arg, tp) and
exists(DataFlow::LocalSourceNode source |
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), pragma[only_bind_into](arg), _, call,
encl, pragma[only_bind_into](name)) and
exists(lookupSingletonMethod(tp, pragma[only_bind_into](name), exact))
|
source = trackModuleAccess(tp) and
exact = true
or
exists(SelfVariable self | arg.asExpr().getExpr() = self.getAnAccess() |
@@ -1073,8 +1059,7 @@ private predicate mayBenefitFromCallContextSingleton(
exact = false
)
)
) and
exists(lookupSingletonMethod(tp, pragma[only_bind_into](name), exact))
)
}
/**
@@ -1101,7 +1086,7 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
exists(RelevantCall call0, Callable res |
call0 = call.asCall() and
res = result.asCallable() and
res = getTarget(call0) and // make sure to not include e.g. private methods
result = viableSourceCallable(call) and // make sure to not include e.g. private methods
exists(Module m, boolean exact, string name |
mayBenefitFromCallContextInstance(ctx.asCall(), pragma[only_bind_into](call0), _, _,
pragma[only_bind_into](m), exact, pragma[only_bind_into](name)) and
@@ -1113,18 +1098,22 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
)
)
or
// `ctx` cannot provide a type bound
exists(RelevantCall call0, RelevantCall ctx0, ArgumentNode arg, string name |
// `ctx` cannot provide a type bound, and the receiver of the call is `self`;
// in this case, still apply an open-world assumption
exists(
RelevantCall call0, RelevantCall ctx0, ArgumentNode arg, SsaSelfDefinitionNode self,
string name
|
call0 = call.asCall() and
ctx0 = ctx.asCall() and
argFlowsToReceiver(ctx0, arg, call0, _, name) and
argMustFlowToReceiver(ctx0, _, arg, self, call0, _, name) and
not mayBenefitFromCallContextInstance(ctx0, call0, arg, _, _, _, name) and
not mayBenefitFromCallContextSingleton(ctx0, call0, arg, _, _, _, name) and
result = viableSourceCallable(call)
)
or
// library calls should always be able to resolve
argFlowsToReceiver(ctx.asCall(), _, call.asCall(), _, _) and
argMustFlowToReceiver(ctx.asCall(), _, _, _, call.asCall(), _, _) and
result = viableLibraryCallable(call)
)
}