Dataflow: Refactor dispatch with call context.

This commit is contained in:
Anders Schack-Mulligen
2020-06-25 14:13:36 +02:00
parent b24fba8df0
commit 6c679c328d
8 changed files with 278 additions and 161 deletions

View File

@@ -67,7 +67,6 @@ private predicate transitiveCapturedCallTarget(ControlFlow::Nodes::ElementNode c
cached
private module Cached {
private import CallContext
private import semmle.code.csharp.Caching
cached
@@ -108,6 +107,12 @@ private module Cached {
/** Gets a viable run-time target for the call `call`. */
cached
DataFlowCallable viableImpl(DataFlowCall call) { result = call.getARuntimeTarget() }
}
import Cached
private module DispatchImpl {
private import CallContext
/**
* Gets a viable run-time target for the delegate call `call`, requiring
@@ -123,7 +128,7 @@ private module Cached {
* call is a delegate call, or if the qualifier accesses a parameter of
* the enclosing callable `c` (including the implicit `this` parameter).
*/
private predicate mayBenefitFromCallContext(DataFlowCall call, Callable c) {
predicate mayBenefitFromCallContext(DataFlowCall call, Callable c) {
c = call.getEnclosingCallable() and
(
exists(CallContext cc | exists(viableDelegateCallable(call, cc)) |
@@ -134,26 +139,11 @@ private module Cached {
)
}
/**
* Holds if the call context `ctx` reduces the set of viable run-time
* targets of call `call` in `c`.
*/
cached
predicate reducedViableImplInCallContext(DataFlowCall call, DataFlowCallable c, DataFlowCall ctx) {
exists(int tgts, int ctxtgts |
mayBenefitFromCallContext(call, c) and
c = viableCallable(ctx) and
ctxtgts = count(viableImplInCallContext(call, ctx)) and
tgts = strictcount(viableImpl(call)) and
ctxtgts < tgts
)
}
/**
* Gets a viable dispatch target of `call` in the context `ctx`. This is
* restricted to those `call`s for which a context might make a difference.
*/
private DotNet::Callable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
DotNet::Callable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
exists(ArgumentCallContext cc | result = viableDelegateCallable(call, cc) |
cc.isArgument(ctx.getExpr(), _)
)
@@ -164,47 +154,9 @@ private module Cached {
.getDispatchCall()
.getADynamicTargetInCallContext(ctx.(NonDelegateDataFlowCall).getDispatchCall())
}
/**
* Gets a viable run-time target for the call `call` in the context
* `ctx`. This is restricted to those call nodes for which a context
* might make a difference.
*/
cached
DotNet::Callable prunedViableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
result = viableImplInCallContext(call, ctx) and
reducedViableImplInCallContext(call, _, ctx)
}
/**
* Holds if flow returning from callable `c` to call `call` might return
* further and if this path restricts the set of call sites that can be
* returned to.
*/
cached
predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) {
exists(int tgts, int ctxtgts |
mayBenefitFromCallContext(call, _) and
c = viableImpl(call) and
ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContext(call, ctx)) and
tgts = strictcount(DataFlowCall ctx | viableCallable(ctx) = call.getEnclosingCallable()) and
ctxtgts < tgts
)
}
/**
* Gets a viable run-time target for the call `call` in the context `ctx`.
* This is restricted to those call nodes and results for which the return
* flow from the result to `call` restricts the possible context `ctx`.
*/
cached
DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall call, DataFlowCall ctx) {
result = viableImplInCallContext(call, ctx) and
reducedViableImplInReturn(result, call)
}
}
import Cached
import DispatchImpl
/**
* Gets a node that can read the value returned from `call` with return kind

View File

@@ -330,6 +330,67 @@ private module Cached {
import Final
}
import FlowThrough
cached
private module DispatchWithCallContext {
/**
* Holds if the call context `ctx` reduces the set of viable run-time
* dispatch targets of call `call` in `c`.
*/
cached
predicate reducedViableImplInCallContext(DataFlowCall call, DataFlowCallable c, DataFlowCall ctx) {
exists(int tgts, int ctxtgts |
mayBenefitFromCallContext(call, c) and
c = viableCallable(ctx) and
ctxtgts = count(viableImplInCallContext(call, ctx)) and
tgts = strictcount(viableImpl(call)) and
ctxtgts < tgts
)
}
/**
* Gets a viable run-time dispatch target for the call `call` in the
* context `ctx`. This is restricted to those calls for which a context
* makes a difference.
*/
cached
DataFlowCallable prunedViableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
result = viableImplInCallContext(call, ctx) and
reducedViableImplInCallContext(call, _, ctx)
}
/**
* Holds if flow returning from callable `c` to call `call` might return
* further and if this path restricts the set of call sites that can be
* returned to.
*/
cached
predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) {
exists(int tgts, int ctxtgts |
mayBenefitFromCallContext(call, _) and
c = viableImpl(call) and
ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContext(call, ctx)) and
tgts = strictcount(DataFlowCall ctx | viableCallable(ctx) = call.getEnclosingCallable()) and
ctxtgts < tgts
)
}
/**
* Gets a viable run-time dispatch target for the call `call` in the
* context `ctx`. This is restricted to those calls and results for which
* the return flow from the result to `call` restricts the possible context
* `ctx`.
*/
cached
DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall call, DataFlowCall ctx) {
result = viableImplInCallContext(call, ctx) and
reducedViableImplInReturn(result, call)
}
}
import DispatchWithCallContext
/**
* Holds if `p` can flow to the pre-update node associated with post-update
* node `n`, in the same callable, using only value-preserving steps.
@@ -371,8 +432,6 @@ private module Cached {
store(node1, tc.getContent(), node2, contentType, tc.getContainerType())
}
import FlowThrough
/**
* Holds if the call context `call` either improves virtual dispatch in
* `callable` or if it allows us to prune unreachable nodes in `callable`.