diff --git a/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll b/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll index b540cf7946bfd..b1e5da87f3296 100644 --- a/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll +++ b/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll @@ -84,6 +84,372 @@ module MakeImplCommon Lang> { private module TypeTrackingInput implements Tt::TypeTrackingInput { final class Node = Lang::Node; + /** Provides predicates for calculating flow-through summaries. */ + private module FlowThrough { + /** + * The first flow-through approximation: + * + * - Input access paths are abstracted with a Boolean parameter + * that indicates (non-)emptiness. + */ + private module Cand { + /** + * Holds if `p` can flow to `node` in the same callable using only + * value-preserving steps. + * + * `read` indicates whether it is contents of `p` that can flow to `node`. + */ + pragma[nomagic] + private predicate parameterValueFlowCand(ParamNode p, Node node, boolean read) { + ( + p = node and + read = false + or + // local flow + exists(Node mid | + parameterValueFlowCand(p, mid, read) and + simpleLocalFlowStep(mid, node, _) and + validParameterAliasStep(mid, node) + ) + or + // read + exists(Node mid | + parameterValueFlowCand(p, mid, false) and + readSet(mid, _, node) and + read = true + ) + or + // flow through: no prior read + exists(ArgNode arg | + parameterValueFlowArgCand(p, arg, false) and + argumentValueFlowsThroughCand(arg, node, read) + ) + or + // flow through: no read inside method + exists(ArgNode arg | + parameterValueFlowArgCand(p, arg, read) and + argumentValueFlowsThroughCand(arg, node, false) + ) + ) and + not expectsContent(node, _) + } + + pragma[nomagic] + private predicate parameterValueFlowArgCand(ParamNode p, ArgNode arg, boolean read) { + parameterValueFlowCand(p, arg, read) + } + + pragma[nomagic] + predicate parameterValueFlowsToPreUpdateCand(ParamNode p, PostUpdateNode n) { + parameterValueFlowCand(p, n.getPreUpdateNode(), false) + } + + /** + * Holds if `p` can flow to a return node of kind `kind` in the same + * callable using only value-preserving steps, not taking call contexts + * into account. + * + * `read` indicates whether it is contents of `p` that can flow to the return + * node. + */ + predicate parameterValueFlowReturnCand(ParamNode p, ReturnKind kind, boolean read) { + exists(ReturnNode ret | + parameterValueFlowCand(p, ret, read) and + kind = ret.getKind() + ) + } + + pragma[nomagic] + private predicate argumentValueFlowsThroughCand0( + DataFlowCall call, ArgNode arg, ReturnKind kind, boolean read + ) { + exists(ParamNode param | viableParamArg(call, param, arg) | + parameterValueFlowReturnCand(param, kind, read) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only value-preserving steps, + * not taking call contexts into account. + * + * `read` indicates whether it is contents of `arg` that can flow to `out`. + */ + predicate argumentValueFlowsThroughCand(ArgNode arg, Node out, boolean read) { + exists(DataFlowCall call, ReturnKind kind | + argumentValueFlowsThroughCand0(call, arg, kind, read) and + out = getAnOutNode(call, kind) + ) + } + + predicate cand(ParamNode p, Node n) { + parameterValueFlowCand(p, n, _) and + ( + parameterValueFlowReturnCand(p, _, _) + or + parameterValueFlowsToPreUpdateCand(p, _) + ) + } + } + + /** + * The final flow-through calculation: + * + * - Calculated flow is either value-preserving (`read = TReadStepTypesNone()`) + * or summarized as a single read step with before and after types recorded + * in the `ReadStepTypesOption` parameter. + * - Types are checked using the `compatibleTypes()` relation. + * - Call contexts are taken into account. + */ + private module Final { + /** + * Holds if `p` can flow to `node` in the same callable using only + * value-preserving steps and possibly a single read step, not taking + * call contexts into account. + * + * If a read step was taken, then `read` captures the `Content`, the + * container type, and the content type. + */ + predicate parameterValueFlow( + ParamNode p, Node node, ReadStepTypesOption read, string model, + CachedCallContextSensitivity::CcNoCall ctx + ) { + parameterValueFlow0(p, node, read, model, ctx) and + Cand::cand(p, node) and + if node instanceof CastingNode + then + // normal flow through + read = TReadStepTypesNone() and + compatibleTypesFilter(getNodeDataFlowType(p), getNodeDataFlowType(node)) + or + // getter + compatibleTypesFilter(read.getContentType(), getNodeDataFlowType(node)) + else any() + } + + bindingset[model1, model2] + pragma[inline_late] + private string mergeModels(string model1, string model2) { + if model1 = "" then result = model2 else result = model1 + } + + pragma[nomagic] + private predicate parameterValueFlow0( + ParamNode p, Node node, ReadStepTypesOption read, string model, + CachedCallContextSensitivity::CcNoCall ctx + ) { + p = node and + Cand::cand(p, _) and + read = TReadStepTypesNone() and + model = "" and + CachedCallContextSensitivity::viableImplNotCallContextReducedReverse(ctx) + or + // local flow + exists(Node mid, string model1, string model2 | + parameterValueFlow(p, mid, read, model1, ctx) and + simpleLocalFlowStep(mid, node, model2) and + validParameterAliasStep(mid, node) and + model = mergeModels(model1, model2) + ) + or + // read + exists(Node mid | + parameterValueFlow(p, mid, TReadStepTypesNone(), model, ctx) and + readStepWithTypes(mid, read.getContainerType(), read.getContent(), node, + read.getContentType()) and + Cand::parameterValueFlowReturnCand(p, _, true) and + compatibleTypesFilter(getNodeDataFlowType(p), read.getContainerType()) + ) + or + parameterValueFlow0_0(TReadStepTypesNone(), p, node, read, model, ctx) + } + + bindingset[ctx1, ctx2] + pragma[inline_late] + private CachedCallContextSensitivity::CcNoCall mergeContexts( + CachedCallContextSensitivity::CcNoCall ctx1, CachedCallContextSensitivity::CcNoCall ctx2 + ) { + if CachedCallContextSensitivity::viableImplNotCallContextReducedReverse(ctx1) + then result = ctx2 + else + if CachedCallContextSensitivity::viableImplNotCallContextReducedReverse(ctx2) + then result = ctx1 + else + // check that `ctx1` is compatible with `ctx2` for at least _some_ outer call, + // and then (arbitrarily) continue with `ctx2` + exists(DataFlowCall someOuterCall, DataFlowCallable callable | + someOuterCall = + CachedCallContextSensitivity::viableImplCallContextReducedReverse(callable, ctx1) and + someOuterCall = + CachedCallContextSensitivity::viableImplCallContextReducedReverse(callable, ctx2) and + result = ctx2 + ) + } + + pragma[nomagic] + private predicate parameterValueFlow0_0( + ReadStepTypesOption mustBeNone, ParamNode p, Node node, ReadStepTypesOption read, + string model, CachedCallContextSensitivity::CcNoCall ctx + ) { + exists( + ArgNode arg, string model1, string model2, CachedCallContextSensitivity::CcNoCall ctx1, + CachedCallContextSensitivity::CcNoCall ctx2 + | + model = mergeModels(model1, model2) and + ctx = mergeContexts(ctx1, ctx2) + | + // flow through: no prior read + parameterValueFlowArg(p, arg, mustBeNone, model1, ctx1) and + argumentValueFlowsThrough(arg, read, node, model2, ctx2) + or + // flow through: no read inside method + parameterValueFlowArg(p, arg, read, model1, ctx1) and + argumentValueFlowsThrough(arg, mustBeNone, node, model2, ctx2) + ) + } + + pragma[nomagic] + private predicate parameterValueFlowArg( + ParamNode p, ArgNode arg, ReadStepTypesOption read, string model, + CachedCallContextSensitivity::CcNoCall ctx + ) { + parameterValueFlow(p, arg, read, model, ctx) and + Cand::argumentValueFlowsThroughCand(arg, _, _) + } + + pragma[nomagic] + private predicate argumentValueFlowsThrough0( + DataFlowCall call, ArgNode arg, ReturnKind kind, ReadStepTypesOption read, string model, + CachedCallContextSensitivity::CcNoCall outerCtx + ) { + exists( + ParamNode param, DataFlowCallable callable, + CachedCallContextSensitivity::CcNoCall innerCtx + | + viableParamArg(call, param, arg) and + parameterValueFlowReturn(param, kind, read, model, innerCtx) and + callable = nodeGetEnclosingCallable(param) and + outerCtx = + CachedCallContextSensitivity::getCallContextReturn(callable, TNormalDataFlowCall(call)) + | + CachedCallContextSensitivity::viableImplNotCallContextReducedReverse(innerCtx) + or + call = + CachedCallContextSensitivity::viableImplCallContextReducedReverse(callable, innerCtx) + ) + } + + pragma[nomagic] + private predicate argumentValueFlowsThrough( + ArgNode arg, ReadStepTypesOption read, Node out, string model, + CachedCallContextSensitivity::CcNoCall ctx + ) { + exists(DataFlowCall call, ReturnKind kind | + argumentValueFlowsThrough0(call, arg, kind, read, model, ctx) and + out = getAnOutNode(call, kind) + | + // normal flow through + read = TReadStepTypesNone() and + compatibleTypesFilter(getNodeDataFlowType(arg), getNodeDataFlowType(out)) + or + // getter + compatibleTypesFilter(getNodeDataFlowType(arg), read.getContainerType()) and + compatibleTypesFilter(read.getContentType(), getNodeDataFlowType(out)) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only + * value-preserving steps and possibly a single read step, not taking + * call contexts into account. + * + * If a read step was taken, then `read` captures the `Content`, the + * container type, and the content type. + */ + predicate argumentValueFlowsThrough( + ArgNode arg, ReadStepTypesOption read, Node out, string model + ) { + argumentValueFlowsThrough(arg, read, out, model, _) + } + + /** + * Holds if `arg` flows to `out` through a call using only + * value-preserving steps and a single read step, not taking call + * contexts into account, thus representing a getter-step. + * + * This predicate is exposed for testing only. + */ + predicate getterStep(ArgNode arg, ContentSet c, Node out) { + argumentValueFlowsThrough(arg, TReadStepTypesSome(_, c, _), out, _) + } + + /** + * Holds if `p` can flow to a return node of kind `kind` in the same + * callable using only value-preserving steps and possibly a single read + * step. + * + * If a read step was taken, then `read` captures the `Content`, the + * container type, and the content type. + */ + private predicate parameterValueFlowReturn( + ParamNode p, ReturnKind kind, ReadStepTypesOption read, string model, + CachedCallContextSensitivity::CcNoCall ctx + ) { + exists(ReturnNode ret | + parameterValueFlow(p, ret, read, model, ctx) and + kind = ret.getKind() + ) + } + } + + import Final + } + + /** + * 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. + */ + private predicate parameterValueFlowsToPreUpdate(ParamNode p, PostUpdateNode n) { + FlowThrough::parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone(), _, _) + } + + pragma[nomagic] + private predicate paramReturnNode( + PostUpdateNode n, ParamNode p, SndLevelScopeOption scope, ReturnKindExt k + ) { + exists(ParameterPosition pos | + parameterValueFlowsToPreUpdate(p, n) and + p.isParameterOf(_, pos) and + k = TParamUpdate(pos) and + scope = getSecondLevelScope0(n) + ) + } + + pragma[nomagic] + private predicate hasParamReturnKindIn( + PostUpdateNode n, ParamNode p, ReturnKindExt kind, DataFlowCallable c + ) { + c = getNodeEnclosingCallable(n) and + paramReturnNode(n, p, _, kind) + } + + cached + private module Cached { + cached + ReturnPosition getParamReturnPosition(PostUpdateNode n, ParamNode p) { + exists(ReturnKindExt kind, DataFlowCallable c | + hasParamReturnKindIn(n, p, kind, c) and + result = TReturnPosition0(c, kind) + ) + } + + cached + predicate argumentValueFlowsThrough(ArgNode arg, ReadStepTypesOption read, Node out) { + FlowThrough::argumentValueFlowsThrough(arg, read, out, _) + } + } + + private import Cached + class LocalSourceNode extends Node { LocalSourceNode() { storeStep(_, this, _) or @@ -115,7 +481,7 @@ module MakeImplCommon Lang> { predicate levelStepNoCall(Node n1, LocalSourceNode n2) { none() } predicate levelStepCall(Node n1, LocalSourceNode n2) { - argumentValueFlowsThrough(n1, TReadStepTypesNone(), n2, _) + argumentValueFlowsThrough(n1, TReadStepTypesNone(), n2) } // TODO: support setters @@ -125,14 +491,14 @@ module MakeImplCommon Lang> { exists(Node pre1, Node pre2 | pre1 = n1.(PostUpdateNode).getPreUpdateNode() and pre2 = n2.(PostUpdateNode).getPreUpdateNode() and - argumentValueFlowsThrough(pre2, TReadStepTypesSome(_, f, _), pre1, _) + argumentValueFlowsThrough(pre2, TReadStepTypesSome(_, f, _), pre1) ) } private predicate loadStep0(Node n1, Node n2, Content f) { readSet(n1, f, n2) or - argumentValueFlowsThrough(n1, TReadStepTypesSome(_, f, _), n2, _) + argumentValueFlowsThrough(n1, TReadStepTypesSome(_, f, _), n2) } predicate loadStep(Node n1, LocalSourceNode n2, Content f) { loadStep0(n1, n2, f) } @@ -1356,18 +1722,6 @@ module MakeImplCommon Lang> { ) } - pragma[nomagic] - private predicate paramReturnNode( - PostUpdateNode n, ParamNode p, SndLevelScopeOption scope, ReturnKindExt k - ) { - exists(ParameterPosition pos | - parameterValueFlowsToPreUpdate(p, n) and - p.isParameterOf(_, pos) and - k = TParamUpdate(pos) and - scope = getSecondLevelScope0(n) - ) - } - pragma[nomagic] private predicate paramReturnNode(ParamNode p, ReturnKindExt k) { exists(ParameterPosition pos | @@ -1409,557 +1763,226 @@ module MakeImplCommon Lang> { * * `lastCall` records the call required to reach `call` in order for the result * to be a viable target, if any. - */ - cached - DataFlowCallable viableCallableLambda(DataFlowCall call, DataFlowCallOption lastCall) { - exists(Node creation, LambdaCallKind kind | - LambdaFlow::revLambdaFlow(call, kind, creation, _, _, _, lastCall) and - lambdaCreation(creation, kind, result) - ) - } - - /** - * Holds if the set of viable implementations that can be called by `call` - * might be improved by knowing the call context. - */ - cached - predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) { - ( - mayBenefitFromCallContext(call) - or - exists(viableCallableLambda(call, TDataFlowCallSome(_))) - ) and - callEnclosingCallable(call, callable) - } - - /** - * 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. - */ - cached - DataFlowCallable viableImplInCallContextExt(DataFlowCall call, DataFlowCall ctx) { - result = viableImplInCallContext(call, ctx) and - result = viableCallable(call) - or - result = viableCallableLambda(call, TDataFlowCallSome(ctx)) - or - exists(DataFlowCallable enclosing | - mayBenefitFromCallContextExt(call, enclosing) and - enclosing = viableCallableExt(ctx) and - result = viableCallableLambda(call, TDataFlowCallNone()) - ) - } - - /** - * A cached version of the `CallContextSensitivity` module. Only used in - * pruning stages 1+2 and flow exploration; all subsequent pruning stages use a - * pruned version, based on the relevant call edges from the previous stage. - */ - cached - module CachedCallContextSensitivity { - private module CallContextSensitivityInput implements CallContextSensitivityInputSig { - predicate relevantCallEdgeIn(DataFlowCall call, DataFlowCallable c) { - c = viableCallableExt(call) - } - - predicate relevantCallEdgeOut(DataFlowCall call, DataFlowCallable c) { - c = viableCallableExt(call) - } - } - - private module Impl1 = CallContextSensitivity; - - cached - predicate reducedViableImplInCallContext( - DataFlowCall call, DataFlowCallable c, DataFlowCall ctx - ) { - Impl1::reducedViableImplInCallContext(call, c, ctx) - } - - cached - predicate recordDataFlowCallSiteUnreachable(DataFlowCall call, DataFlowCallable c) { - Impl1::recordDataFlowCallSiteUnreachable(call, c) - } - - cached - predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) { - Impl1::reducedViableImplInReturn(c, call) - } - - cached - CcCall getSpecificCallContextCall(DataFlowCall call, DataFlowCallable c) { - result = Impl1::getSpecificCallContextCall(call, c) - } - - cached - predicate callContextAffectsDispatch(DataFlowCall call, Cc ctx) { - Impl1::callContextAffectsDispatch(call, ctx) - } - - cached - CcNoCall getSpecificCallContextReturn(DataFlowCallable c, DataFlowCall call) { - result = Impl1::getSpecificCallContextReturn(c, call) - } - - private module PrunedViableImplInput implements Impl1::PrunedViableImplInputSig { - predicate reducedViableImplInCallContext = - CachedCallContextSensitivity::reducedViableImplInCallContext/3; - - predicate recordDataFlowCallSiteUnreachable = - CachedCallContextSensitivity::recordDataFlowCallSiteUnreachable/2; - - predicate getSpecificCallContextCall = - CachedCallContextSensitivity::getSpecificCallContextCall/2; - - predicate callContextAffectsDispatch = - CachedCallContextSensitivity::callContextAffectsDispatch/2; - - predicate getSpecificCallContextReturn = - CachedCallContextSensitivity::getSpecificCallContextReturn/2; - } - - private module Impl2 = Impl1::PrunedViableImpl; - - import Impl2 - - cached - predicate instanceofCc(Cc cc) { any() } - - cached - predicate instanceofCcCall(CcCall cc) { any() } - - cached - predicate instanceofCcNoCall(CcNoCall cc) { any() } - - cached - DataFlowCallable viableImplCallContextReduced(DataFlowCall call, CcCall ctx) { - result = Impl2::viableImplCallContextReduced(call, ctx) - } - - cached - DataFlowCall viableImplCallContextReducedReverse(DataFlowCallable callable, CcNoCall ctx) { - result = Impl2::viableImplCallContextReducedReverse(callable, ctx) - } - } - - /** - * Holds if `p` is the parameter of a viable dispatch target of `call`, - * and `p` has position `ppos`. - */ - pragma[nomagic] - private predicate viableParam(DataFlowCall call, ParameterPosition ppos, ParamNode p) { - p.isParameterOf(viableCallableExt(call), ppos) - } - - /** - * Holds if `p` is the parameter of a viable dispatch target of `call`, - * and `p` has position `ppos`. - */ - pragma[nomagic] - private predicate viableParamEx(DataFlowCall call, ParameterPositionEx ppos, ParamNodeEx p) { - p.isParameterOf(viableCallableExt(call), ppos) - } - - /** - * Holds if `arg` is a possible argument to `p` in `call`, taking virtual - * dispatch into account. - */ - cached - predicate viableParamArg(DataFlowCall call, ParamNode p, ArgNode arg) { - exists(ParameterPosition ppos | - viableParam(call, ppos, p) and - argumentPositionMatch(call, arg, ppos) and - compatibleTypesFilter(getNodeDataFlowType(arg), getNodeDataFlowType(p)) and - golangSpecificParamArgFilter(call, p, arg) - ) - } - - bindingset[call, p, arg] - private predicate golangSpecificParamArgFilterEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { - golangSpecificParamArgFilter(call, p.asNode(), arg.asNode()) - or - not p.asNode() instanceof ParameterNode - or - not arg.asNode() instanceof ArgumentNode - } - - /** - * Holds if `arg` is a possible argument to `p` in `call`, taking virtual - * dispatch into account. - */ - cached - predicate viableParamArgEx(DataFlowCallEx call, ParamNodeEx p, ArgNodeEx arg) { - // viableParamArg(call, p.asNode(), arg.asNode()) - // or - exists(ParameterPositionEx ppos, DataFlowCall underlyingCall | - underlyingCall = call.projectCall() and - viableParamEx(underlyingCall, ppos, p) and - argumentPositionMatchEx(call, arg, ppos) and - compatibleTypesFilter(arg.getDataFlowType(), p.getDataFlowType()) and - golangSpecificParamArgFilterEx(underlyingCall, p, arg) + */ + cached + DataFlowCallable viableCallableLambda(DataFlowCall call, DataFlowCallOption lastCall) { + exists(Node creation, LambdaCallKind kind | + LambdaFlow::revLambdaFlow(call, kind, creation, _, _, _, lastCall) and + lambdaCreation(creation, kind, result) ) } - pragma[nomagic] - private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { - viableCallableExt(call) = result.getCallable() and - kind = result.getKind() - } - /** - * Holds if a value at return position `pos` can be returned to `out` via `call`, - * taking virtual dispatch into account. + * Holds if the set of viable implementations that can be called by `call` + * might be improved by knowing the call context. */ cached - predicate viableReturnPosOut(DataFlowCall call, ReturnPosition pos, OutNodeExt out) { - exists(ReturnKindExt kind | - pos = viableReturnPos(call, kind) and - out = getAnOutNodeExt(call, kind) - ) + predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) { + ( + mayBenefitFromCallContext(call) + or + exists(viableCallableLambda(call, TDataFlowCallSome(_))) + ) and + callEnclosingCallable(call, callable) } /** - * Holds if a value at return position `pos` can be returned to `out` via `call`, - * taking virtual dispatch into account. + * 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. */ cached - predicate viableReturnPosOutEx(DataFlowCallEx call, ReturnPosition pos, OutNodeEx out) { - exists(ReturnKindExt kind | - pos = viableReturnPos(call.projectCall(), kind) and - out = kind.getAnOutNodeEx(call) //and - // call.toString().matches("%GetBox1%") + DataFlowCallable viableImplInCallContextExt(DataFlowCall call, DataFlowCall ctx) { + result = viableImplInCallContext(call, ctx) and + result = viableCallable(call) + or + result = viableCallableLambda(call, TDataFlowCallSome(ctx)) + or + exists(DataFlowCallable enclosing | + mayBenefitFromCallContextExt(call, enclosing) and + enclosing = viableCallableExt(ctx) and + result = viableCallableLambda(call, TDataFlowCallNone()) ) } - /** Provides predicates for calculating flow-through summaries. */ - private module FlowThrough { - /** - * The first flow-through approximation: - * - * - Input access paths are abstracted with a Boolean parameter - * that indicates (non-)emptiness. - */ - private module Cand { - /** - * Holds if `p` can flow to `node` in the same callable using only - * value-preserving steps. - * - * `read` indicates whether it is contents of `p` that can flow to `node`. - */ - pragma[nomagic] - private predicate parameterValueFlowCand(ParamNode p, Node node, boolean read) { - ( - p = node and - read = false - or - // local flow - exists(Node mid | - parameterValueFlowCand(p, mid, read) and - simpleLocalFlowStep(mid, node, _) and - validParameterAliasStep(mid, node) - ) - or - // read - exists(Node mid | - parameterValueFlowCand(p, mid, false) and - readSet(mid, _, node) and - read = true - ) - or - // flow through: no prior read - exists(ArgNode arg | - parameterValueFlowArgCand(p, arg, false) and - argumentValueFlowsThroughCand(arg, node, read) - ) - or - // flow through: no read inside method - exists(ArgNode arg | - parameterValueFlowArgCand(p, arg, read) and - argumentValueFlowsThroughCand(arg, node, false) - ) - ) and - not expectsContent(node, _) + /** + * A cached version of the `CallContextSensitivity` module. Only used in + * pruning stages 1+2 and flow exploration; all subsequent pruning stages use a + * pruned version, based on the relevant call edges from the previous stage. + */ + cached + module CachedCallContextSensitivity { + private module CallContextSensitivityInput implements CallContextSensitivityInputSig { + predicate relevantCallEdgeIn(DataFlowCall call, DataFlowCallable c) { + c = viableCallableExt(call) } - pragma[nomagic] - private predicate parameterValueFlowArgCand(ParamNode p, ArgNode arg, boolean read) { - parameterValueFlowCand(p, arg, read) + predicate relevantCallEdgeOut(DataFlowCall call, DataFlowCallable c) { + c = viableCallableExt(call) } + } - pragma[nomagic] - predicate parameterValueFlowsToPreUpdateCand(ParamNode p, PostUpdateNode n) { - parameterValueFlowCand(p, n.getPreUpdateNode(), false) - } + private module Impl1 = CallContextSensitivity; - /** - * Holds if `p` can flow to a return node of kind `kind` in the same - * callable using only value-preserving steps, not taking call contexts - * into account. - * - * `read` indicates whether it is contents of `p` that can flow to the return - * node. - */ - predicate parameterValueFlowReturnCand(ParamNode p, ReturnKind kind, boolean read) { - exists(ReturnNode ret | - parameterValueFlowCand(p, ret, read) and - kind = ret.getKind() - ) - } + cached + predicate reducedViableImplInCallContext( + DataFlowCall call, DataFlowCallable c, DataFlowCall ctx + ) { + Impl1::reducedViableImplInCallContext(call, c, ctx) + } - pragma[nomagic] - private predicate argumentValueFlowsThroughCand0( - DataFlowCall call, ArgNode arg, ReturnKind kind, boolean read - ) { - exists(ParamNode param | viableParamArg(call, param, arg) | - parameterValueFlowReturnCand(param, kind, read) - ) - } + cached + predicate recordDataFlowCallSiteUnreachable(DataFlowCall call, DataFlowCallable c) { + Impl1::recordDataFlowCallSiteUnreachable(call, c) + } - /** - * Holds if `arg` flows to `out` through a call using only value-preserving steps, - * not taking call contexts into account. - * - * `read` indicates whether it is contents of `arg` that can flow to `out`. - */ - predicate argumentValueFlowsThroughCand(ArgNode arg, Node out, boolean read) { - exists(DataFlowCall call, ReturnKind kind | - argumentValueFlowsThroughCand0(call, arg, kind, read) and - out = getAnOutNode(call, kind) - ) - } + cached + predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) { + Impl1::reducedViableImplInReturn(c, call) + } - predicate cand(ParamNode p, Node n) { - parameterValueFlowCand(p, n, _) and - ( - parameterValueFlowReturnCand(p, _, _) - or - parameterValueFlowsToPreUpdateCand(p, _) - ) - } + cached + CcCall getSpecificCallContextCall(DataFlowCall call, DataFlowCallable c) { + result = Impl1::getSpecificCallContextCall(call, c) } - /** - * The final flow-through calculation: - * - * - Calculated flow is either value-preserving (`read = TReadStepTypesNone()`) - * or summarized as a single read step with before and after types recorded - * in the `ReadStepTypesOption` parameter. - * - Types are checked using the `compatibleTypes()` relation. - * - Call contexts are taken into account. - */ - private module Final { - /** - * Holds if `p` can flow to `node` in the same callable using only - * value-preserving steps and possibly a single read step, not taking - * call contexts into account. - * - * If a read step was taken, then `read` captures the `Content`, the - * container type, and the content type. - */ - predicate parameterValueFlow( - ParamNode p, Node node, ReadStepTypesOption read, string model, - CachedCallContextSensitivity::CcNoCall ctx - ) { - parameterValueFlow0(p, node, read, model, ctx) and - Cand::cand(p, node) and - if node instanceof CastingNode - then - // normal flow through - read = TReadStepTypesNone() and - compatibleTypesFilter(getNodeDataFlowType(p), getNodeDataFlowType(node)) - or - // getter - compatibleTypesFilter(read.getContentType(), getNodeDataFlowType(node)) - else any() - } + cached + predicate callContextAffectsDispatch(DataFlowCall call, Cc ctx) { + Impl1::callContextAffectsDispatch(call, ctx) + } - bindingset[model1, model2] - pragma[inline_late] - private string mergeModels(string model1, string model2) { - if model1 = "" then result = model2 else result = model1 - } + cached + CcNoCall getSpecificCallContextReturn(DataFlowCallable c, DataFlowCall call) { + result = Impl1::getSpecificCallContextReturn(c, call) + } - pragma[nomagic] - private predicate parameterValueFlow0( - ParamNode p, Node node, ReadStepTypesOption read, string model, - CachedCallContextSensitivity::CcNoCall ctx - ) { - p = node and - Cand::cand(p, _) and - read = TReadStepTypesNone() and - model = "" and - CachedCallContextSensitivity::viableImplNotCallContextReducedReverse(ctx) - or - // local flow - exists(Node mid, string model1, string model2 | - parameterValueFlow(p, mid, read, model1, ctx) and - simpleLocalFlowStep(mid, node, model2) and - validParameterAliasStep(mid, node) and - model = mergeModels(model1, model2) - ) - or - // read - exists(Node mid | - parameterValueFlow(p, mid, TReadStepTypesNone(), model, ctx) and - readStepWithTypes(mid, read.getContainerType(), read.getContent(), node, - read.getContentType()) and - Cand::parameterValueFlowReturnCand(p, _, true) and - compatibleTypesFilter(getNodeDataFlowType(p), read.getContainerType()) - ) - or - parameterValueFlow0_0(TReadStepTypesNone(), p, node, read, model, ctx) - } + private module PrunedViableImplInput implements Impl1::PrunedViableImplInputSig { + predicate reducedViableImplInCallContext = + CachedCallContextSensitivity::reducedViableImplInCallContext/3; + + predicate recordDataFlowCallSiteUnreachable = + CachedCallContextSensitivity::recordDataFlowCallSiteUnreachable/2; - bindingset[ctx1, ctx2] - pragma[inline_late] - private CachedCallContextSensitivity::CcNoCall mergeContexts( - CachedCallContextSensitivity::CcNoCall ctx1, CachedCallContextSensitivity::CcNoCall ctx2 - ) { - if CachedCallContextSensitivity::viableImplNotCallContextReducedReverse(ctx1) - then result = ctx2 - else - if CachedCallContextSensitivity::viableImplNotCallContextReducedReverse(ctx2) - then result = ctx1 - else - // check that `ctx1` is compatible with `ctx2` for at least _some_ outer call, - // and then (arbitrarily) continue with `ctx2` - exists(DataFlowCall someOuterCall, DataFlowCallable callable | - someOuterCall = - CachedCallContextSensitivity::viableImplCallContextReducedReverse(callable, ctx1) and - someOuterCall = - CachedCallContextSensitivity::viableImplCallContextReducedReverse(callable, ctx2) and - result = ctx2 - ) - } + predicate getSpecificCallContextCall = + CachedCallContextSensitivity::getSpecificCallContextCall/2; - pragma[nomagic] - private predicate parameterValueFlow0_0( - ReadStepTypesOption mustBeNone, ParamNode p, Node node, ReadStepTypesOption read, - string model, CachedCallContextSensitivity::CcNoCall ctx - ) { - exists( - ArgNode arg, string model1, string model2, CachedCallContextSensitivity::CcNoCall ctx1, - CachedCallContextSensitivity::CcNoCall ctx2 - | - model = mergeModels(model1, model2) and - ctx = mergeContexts(ctx1, ctx2) - | - // flow through: no prior read - parameterValueFlowArg(p, arg, mustBeNone, model1, ctx1) and - argumentValueFlowsThrough(arg, read, node, model2, ctx2) - or - // flow through: no read inside method - parameterValueFlowArg(p, arg, read, model1, ctx1) and - argumentValueFlowsThrough(arg, mustBeNone, node, model2, ctx2) - ) - } + predicate callContextAffectsDispatch = + CachedCallContextSensitivity::callContextAffectsDispatch/2; - pragma[nomagic] - private predicate parameterValueFlowArg( - ParamNode p, ArgNode arg, ReadStepTypesOption read, string model, - CachedCallContextSensitivity::CcNoCall ctx - ) { - parameterValueFlow(p, arg, read, model, ctx) and - Cand::argumentValueFlowsThroughCand(arg, _, _) - } + predicate getSpecificCallContextReturn = + CachedCallContextSensitivity::getSpecificCallContextReturn/2; + } - pragma[nomagic] - private predicate argumentValueFlowsThrough0( - DataFlowCall call, ArgNode arg, ReturnKind kind, ReadStepTypesOption read, string model, - CachedCallContextSensitivity::CcNoCall outerCtx - ) { - exists( - ParamNode param, DataFlowCallable callable, - CachedCallContextSensitivity::CcNoCall innerCtx - | - viableParamArg(call, param, arg) and - parameterValueFlowReturn(param, kind, read, model, innerCtx) and - callable = nodeGetEnclosingCallable(param) and - outerCtx = - CachedCallContextSensitivity::getCallContextReturn(callable, TNormalDataFlowCall(call)) - | - CachedCallContextSensitivity::viableImplNotCallContextReducedReverse(innerCtx) - or - call = - CachedCallContextSensitivity::viableImplCallContextReducedReverse(callable, innerCtx) - ) - } + private module Impl2 = Impl1::PrunedViableImpl; - pragma[nomagic] - private predicate argumentValueFlowsThrough( - ArgNode arg, ReadStepTypesOption read, Node out, string model, - CachedCallContextSensitivity::CcNoCall ctx - ) { - exists(DataFlowCall call, ReturnKind kind | - argumentValueFlowsThrough0(call, arg, kind, read, model, ctx) and - out = getAnOutNode(call, kind) - | - // normal flow through - read = TReadStepTypesNone() and - compatibleTypesFilter(getNodeDataFlowType(arg), getNodeDataFlowType(out)) - or - // getter - compatibleTypesFilter(getNodeDataFlowType(arg), read.getContainerType()) and - compatibleTypesFilter(read.getContentType(), getNodeDataFlowType(out)) - ) - } + import Impl2 - /** - * Holds if `arg` flows to `out` through a call using only - * value-preserving steps and possibly a single read step, not taking - * call contexts into account. - * - * If a read step was taken, then `read` captures the `Content`, the - * container type, and the content type. - */ - cached - predicate argumentValueFlowsThrough( - ArgNode arg, ReadStepTypesOption read, Node out, string model - ) { - argumentValueFlowsThrough(arg, read, out, model, _) - } + cached + predicate instanceofCc(Cc cc) { any() } - /** - * Holds if `arg` flows to `out` through a call using only - * value-preserving steps and a single read step, not taking call - * contexts into account, thus representing a getter-step. - * - * This predicate is exposed for testing only. - */ - predicate getterStep(ArgNode arg, ContentSet c, Node out) { - argumentValueFlowsThrough(arg, TReadStepTypesSome(_, c, _), out, _) - } + cached + predicate instanceofCcCall(CcCall cc) { any() } - /** - * Holds if `p` can flow to a return node of kind `kind` in the same - * callable using only value-preserving steps and possibly a single read - * step. - * - * If a read step was taken, then `read` captures the `Content`, the - * container type, and the content type. - */ - private predicate parameterValueFlowReturn( - ParamNode p, ReturnKind kind, ReadStepTypesOption read, string model, - CachedCallContextSensitivity::CcNoCall ctx - ) { - exists(ReturnNode ret | - parameterValueFlow(p, ret, read, model, ctx) and - kind = ret.getKind() - ) - } + cached + predicate instanceofCcNoCall(CcNoCall cc) { any() } + + cached + DataFlowCallable viableImplCallContextReduced(DataFlowCall call, CcCall ctx) { + result = Impl2::viableImplCallContextReduced(call, ctx) } - import Final + cached + DataFlowCall viableImplCallContextReducedReverse(DataFlowCallable callable, CcNoCall ctx) { + result = Impl2::viableImplCallContextReducedReverse(callable, ctx) + } + } + + /** + * Holds if `p` is the parameter of a viable dispatch target of `call`, + * and `p` has position `ppos`. + */ + pragma[nomagic] + private predicate viableParam(DataFlowCall call, ParameterPosition ppos, ParamNode p) { + p.isParameterOf(viableCallableExt(call), ppos) } - import FlowThrough + /** + * Holds if `p` is the parameter of a viable dispatch target of `call`, + * and `p` has position `ppos`. + */ + pragma[nomagic] + private predicate viableParamEx(DataFlowCall call, ParameterPositionEx ppos, ParamNodeEx p) { + p.isParameterOf(viableCallableExt(call), ppos) + } /** - * 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. + * Holds if `arg` is a possible argument to `p` in `call`, taking virtual + * dispatch into account. */ - private predicate parameterValueFlowsToPreUpdate(ParamNode p, PostUpdateNode n) { - parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone(), _, _) + cached + predicate viableParamArg(DataFlowCall call, ParamNode p, ArgNode arg) { + exists(ParameterPosition ppos | + viableParam(call, ppos, p) and + argumentPositionMatch(call, arg, ppos) and + compatibleTypesFilter(getNodeDataFlowType(arg), getNodeDataFlowType(p)) and + golangSpecificParamArgFilter(call, p, arg) + ) + } + + bindingset[call, p, arg] + private predicate golangSpecificParamArgFilterEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + golangSpecificParamArgFilter(call, p.asNode(), arg.asNode()) + or + not p.asNode() instanceof ParameterNode + or + not arg.asNode() instanceof ArgumentNode + } + + /** + * Holds if `arg` is a possible argument to `p` in `call`, taking virtual + * dispatch into account. + */ + cached + predicate viableParamArgEx(DataFlowCallEx call, ParamNodeEx p, ArgNodeEx arg) { + // viableParamArg(call, p.asNode(), arg.asNode()) + // or + exists(ParameterPositionEx ppos, DataFlowCall underlyingCall | + underlyingCall = call.projectCall() and + viableParamEx(underlyingCall, ppos, p) and + argumentPositionMatchEx(call, arg, ppos) and + compatibleTypesFilter(arg.getDataFlowType(), p.getDataFlowType()) and + golangSpecificParamArgFilterEx(underlyingCall, p, arg) + ) + } + + pragma[nomagic] + private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { + viableCallableExt(call) = result.getCallable() and + kind = result.getKind() + } + + /** + * Holds if a value at return position `pos` can be returned to `out` via `call`, + * taking virtual dispatch into account. + */ + cached + predicate viableReturnPosOut(DataFlowCall call, ReturnPosition pos, OutNodeExt out) { + exists(ReturnKindExt kind | + pos = viableReturnPos(call, kind) and + out = getAnOutNodeExt(call, kind) + ) + } + + /** + * Holds if a value at return position `pos` can be returned to `out` via `call`, + * taking virtual dispatch into account. + */ + cached + predicate viableReturnPosOutEx(DataFlowCallEx call, ReturnPosition pos, OutNodeEx out) { + exists(ReturnKindExt kind | + pos = viableReturnPos(call.projectCall(), kind) and + out = kind.getAnOutNodeEx(call) //and + // call.toString().matches("%GetBox1%") + ) } cached @@ -2066,14 +2089,6 @@ module MakeImplCommon Lang> { kind = TValueReturn(ret.getKind()) } - pragma[nomagic] - private predicate hasParamReturnKindIn( - PostUpdateNode n, ParamNode p, ReturnKindExt kind, DataFlowCallable c - ) { - c = getNodeEnclosingCallable(n) and - paramReturnNode(n, p, _, kind) - } - pragma[nomagic] private predicate hasParamReturnKindIn(ParamNode p, ReturnKindExt kind, DataFlowCallable c) { c = getNodeEnclosingCallable(p) and @@ -2096,14 +2111,6 @@ module MakeImplCommon Lang> { ) } - cached - ReturnPosition getParamReturnPosition(PostUpdateNode n, ParamNode p) { - exists(ReturnKindExt kind, DataFlowCallable c | - hasParamReturnKindIn(n, p, kind, c) and - result = TReturnPosition0(c, kind) - ) - } - cached ReturnPosition getParamReturnPosition(ParamNode p) { exists(ReturnKindExt kind, DataFlowCallable c |