private import DataFlowImplSpecific::Private private import DataFlowImplSpecific::Public import Cached module DataFlowImplCommonPublic { /** Provides `FlowState = string`. */ module FlowStateString { /** A state value to track during data flow. */ class FlowState = string; /** * The default state, which is used when the state is unspecified for a source * or a sink. */ class FlowStateEmpty extends FlowState { FlowStateEmpty() { this = "" } } } private newtype TFlowFeature = TFeatureHasSourceCallContext() or TFeatureHasSinkCallContext() or TFeatureEqualSourceSinkCallContext() /** A flow configuration feature for use in `Configuration::getAFeature()`. */ class FlowFeature extends TFlowFeature { string toString() { none() } } /** * A flow configuration feature that implies that sources have some existing * call context. */ class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext { override string toString() { result = "FeatureHasSourceCallContext" } } /** * A flow configuration feature that implies that sinks have some existing * call context. */ class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext { override string toString() { result = "FeatureHasSinkCallContext" } } /** * A flow configuration feature that implies that source-sink pairs have some * shared existing call context. */ class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext { override string toString() { result = "FeatureEqualSourceSinkCallContext" } } } /** * The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion. * * `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the * estimated per-`AccessPathFront` tuple cost. Access paths exceeding both of * these limits are represented with lower precision during pruning. */ predicate accessPathApproxCostLimits(int apLimit, int tupleLimit) { apLimit = 10 and tupleLimit = 10000 } /** * The cost limits for the `AccessPathApprox` to `AccessPath` expansion. * * `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the * estimated per-`AccessPathApprox` tuple cost. Access paths exceeding both of * these limits are represented with lower precision. */ predicate accessPathCostLimits(int apLimit, int tupleLimit) { apLimit = 5 and tupleLimit = 1000 } /** * Holds if `arg` is an argument of `call` with an argument position that matches * parameter position `ppos`. */ pragma[noinline] predicate argumentPositionMatch(DataFlowCall call, ArgNode arg, ParameterPosition ppos) { exists(ArgumentPosition apos | arg.argumentOf(call, apos) and parameterMatch(ppos, apos) ) } /** * Provides a simple data-flow analysis for resolving lambda calls. The analysis * currently excludes read-steps, store-steps, and flow-through. * * The analysis uses non-linear recursion: When computing a flow path in or out * of a call, we use the results of the analysis recursively to resolve lambda * calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly. */ private module LambdaFlow { pragma[noinline] private predicate viableParamNonLambda(DataFlowCall call, ParameterPosition ppos, ParamNode p) { p.isParameterOf(viableCallable(call), ppos) } pragma[noinline] private predicate viableParamLambda(DataFlowCall call, ParameterPosition ppos, ParamNode p) { p.isParameterOf(viableCallableLambda(call, _), ppos) } private predicate viableParamArgNonLambda(DataFlowCall call, ParamNode p, ArgNode arg) { exists(ParameterPosition ppos | viableParamNonLambda(call, ppos, p) and argumentPositionMatch(call, arg, ppos) ) } private predicate viableParamArgLambda(DataFlowCall call, ParamNode p, ArgNode arg) { exists(ParameterPosition ppos | viableParamLambda(call, ppos, p) and argumentPositionMatch(call, arg, ppos) ) } private newtype TReturnPositionSimple = TReturnPositionSimple0(DataFlowCallable c, ReturnKind kind) { exists(ReturnNode ret | c = getNodeEnclosingCallable(ret) and kind = ret.getKind() ) } pragma[noinline] private TReturnPositionSimple getReturnPositionSimple(ReturnNode ret, ReturnKind kind) { result = TReturnPositionSimple0(getNodeEnclosingCallable(ret), kind) } pragma[nomagic] private TReturnPositionSimple viableReturnPosNonLambda(DataFlowCall call, ReturnKind kind) { result = TReturnPositionSimple0(viableCallable(call), kind) } pragma[nomagic] private TReturnPositionSimple viableReturnPosLambda(DataFlowCall call, ReturnKind kind) { result = TReturnPositionSimple0(viableCallableLambda(call, _), kind) } private predicate viableReturnPosOutNonLambda( DataFlowCall call, TReturnPositionSimple pos, OutNode out ) { exists(ReturnKind kind | pos = viableReturnPosNonLambda(call, kind) and out = getAnOutNode(call, kind) ) } pragma[nomagic] private predicate viableReturnPosOutLambda( DataFlowCall call, TReturnPositionSimple pos, OutNode out ) { exists(ReturnKind kind | pos = viableReturnPosLambda(call, kind) and out = getAnOutNode(call, kind) ) } /** * Holds if data can flow (inter-procedurally) from `node` (of type `t`) to * the lambda call `lambdaCall`. * * The parameter `toReturn` indicates whether the path from `node` to * `lambdaCall` goes through a return, and `toJump` whether the path goes * through a jump step. * * The call context `lastCall` records the last call on the path from `node` * to `lambdaCall`, if any. That is, `lastCall` is able to target the enclosing * callable of `lambdaCall`. */ pragma[nomagic] predicate revLambdaFlow( DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn, boolean toJump, DataFlowCallOption lastCall ) { revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and not expectsContent(node, _) and if castNode(node) or node instanceof ArgNode or node instanceof ReturnNode then compatibleTypes(t, getNodeDataFlowType(node)) else any() } pragma[nomagic] predicate revLambdaFlow0( DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn, boolean toJump, DataFlowCallOption lastCall ) { lambdaCall(lambdaCall, kind, node) and t = getNodeDataFlowType(node) and toReturn = false and toJump = false and lastCall = TDataFlowCallNone() or // local flow exists(Node mid, DataFlowType t0 | revLambdaFlow(lambdaCall, kind, mid, t0, toReturn, toJump, lastCall) | simpleLocalFlowStep(node, mid) and t = t0 or exists(boolean preservesValue | additionalLambdaFlowStep(node, mid, preservesValue) and getNodeEnclosingCallable(node) = getNodeEnclosingCallable(mid) | preservesValue = false and t = getNodeDataFlowType(node) or preservesValue = true and t = t0 ) ) or // jump step exists(Node mid, DataFlowType t0 | revLambdaFlow(lambdaCall, kind, mid, t0, _, _, lastCall) and toReturn = false and toJump = true | jumpStepCached(node, mid) and t = t0 or exists(boolean preservesValue | additionalLambdaFlowStep(node, mid, preservesValue) and getNodeEnclosingCallable(node) != getNodeEnclosingCallable(mid) | preservesValue = false and t = getNodeDataFlowType(node) or preservesValue = true and t = t0 ) ) or // flow into a callable exists(ParamNode p, DataFlowCallOption lastCall0, DataFlowCall call | revLambdaFlowIn(lambdaCall, kind, p, t, toJump, lastCall0) and ( if lastCall0 = TDataFlowCallNone() and toJump = false then lastCall = TDataFlowCallSome(call) else lastCall = lastCall0 ) and toReturn = false | viableParamArgNonLambda(call, p, node) or viableParamArgLambda(call, p, node) // non-linear recursion ) or // flow out of a callable exists(TReturnPositionSimple pos | revLambdaFlowOut(lambdaCall, kind, pos, t, toJump, lastCall) and getReturnPositionSimple(node, node.(ReturnNode).getKind()) = pos and toReturn = true ) } pragma[nomagic] predicate revLambdaFlowOutLambdaCall( DataFlowCall lambdaCall, LambdaCallKind kind, OutNode out, DataFlowType t, boolean toJump, DataFlowCall call, DataFlowCallOption lastCall ) { revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and exists(ReturnKindExt rk | out = rk.getAnOutNode(call) and lambdaCall(call, _, _) ) } pragma[nomagic] predicate revLambdaFlowOut( DataFlowCall lambdaCall, LambdaCallKind kind, TReturnPositionSimple pos, DataFlowType t, boolean toJump, DataFlowCallOption lastCall ) { exists(DataFlowCall call, OutNode out | revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and viableReturnPosOutNonLambda(call, pos, out) or // non-linear recursion revLambdaFlowOutLambdaCall(lambdaCall, kind, out, t, toJump, call, lastCall) and viableReturnPosOutLambda(call, pos, out) ) } pragma[nomagic] predicate revLambdaFlowIn( DataFlowCall lambdaCall, LambdaCallKind kind, ParamNode p, DataFlowType t, boolean toJump, DataFlowCallOption lastCall ) { revLambdaFlow(lambdaCall, kind, p, t, false, toJump, lastCall) } } private DataFlowCallable viableCallableExt(DataFlowCall call) { result = viableCallable(call) or result = viableCallableLambda(call, _) } cached private module Cached { /** * If needed, call this predicate from `DataFlowImplSpecific.qll` in order to * force a stage-dependency on the `DataFlowImplCommon.qll` stage and thereby * collapsing the two stages. */ cached predicate forceCachingInSameStage() { any() } cached predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = nodeGetEnclosingCallable(n) } cached predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) { c = call.getEnclosingCallable() } cached predicate nodeDataFlowType(Node n, DataFlowType t) { t = getNodeType(n) } cached predicate jumpStepCached(Node node1, Node node2) { jumpStep(node1, node2) } cached predicate clearsContentCached(Node n, ContentSet c) { clearsContent(n, c) } cached predicate expectsContentCached(Node n, ContentSet c) { expectsContent(n, c) } cached predicate isUnreachableInCallCached(Node n, DataFlowCall call) { isUnreachableInCall(n, call) } cached predicate outNodeExt(Node n) { n instanceof OutNode or n.(PostUpdateNode).getPreUpdateNode() instanceof ArgNode } cached predicate hiddenNode(Node n) { nodeIsHidden(n) } cached OutNodeExt getAnOutNodeExt(DataFlowCall call, ReturnKindExt k) { result = getAnOutNode(call, k.(ValueReturnKind).getKind()) or exists(ArgNode arg | result.(PostUpdateNode).getPreUpdateNode() = arg and arg.argumentOf(call, k.(ParamUpdateReturnKind).getAMatchingArgumentPosition()) ) } cached predicate returnNodeExt(Node n, ReturnKindExt k) { k = TValueReturn(n.(ReturnNode).getKind()) or exists(ParamNode p, ParameterPosition pos | parameterValueFlowsToPreUpdate(p, n) and p.isParameterOf(_, pos) and k = TParamUpdate(pos) ) } cached predicate castNode(Node n) { n instanceof CastNode } cached predicate castingNode(Node n) { castNode(n) or n instanceof ParamNode or n instanceof OutNodeExt or // For reads, `x.f`, we want to check that the tracked type after the read (which // is obtained by popping the head of the access path stack) is compatible with // the type of `x.f`. readSet(_, _, n) } cached predicate parameterNode(Node p, DataFlowCallable c, ParameterPosition pos) { isParameterNode(p, c, pos) } cached predicate argumentNode(Node n, DataFlowCall call, ArgumentPosition pos) { isArgumentNode(n, call, pos) } /** * Gets a viable target for the lambda call `call`. * * `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 `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 `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 compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(p)) and golangSpecificParamArgFilter(call, 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, Node out) { exists(ReturnKindExt kind | pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) } /** 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) ) 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) ) } 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. */ 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) { parameterValueFlow0(p, node, read) and if node instanceof CastingNode then // normal flow through read = TReadStepTypesNone() and compatibleTypes(getNodeDataFlowType(p), getNodeDataFlowType(node)) or // getter compatibleTypes(read.getContentType(), getNodeDataFlowType(node)) else any() } pragma[nomagic] private predicate parameterValueFlow0(ParamNode p, Node node, ReadStepTypesOption read) { p = node and Cand::cand(p, _) and read = TReadStepTypesNone() or // local flow exists(Node mid | parameterValueFlow(p, mid, read) and simpleLocalFlowStep(mid, node) ) or // read exists(Node mid | parameterValueFlow(p, mid, TReadStepTypesNone()) and readStepWithTypes(mid, read.getContainerType(), read.getContent(), node, read.getContentType()) and Cand::parameterValueFlowReturnCand(p, _, true) and compatibleTypes(getNodeDataFlowType(p), read.getContainerType()) ) or parameterValueFlow0_0(TReadStepTypesNone(), p, node, read) } pragma[nomagic] private predicate parameterValueFlow0_0( ReadStepTypesOption mustBeNone, ParamNode p, Node node, ReadStepTypesOption read ) { // flow through: no prior read exists(ArgNode arg | parameterValueFlowArg(p, arg, mustBeNone) and argumentValueFlowsThrough(arg, read, node) ) or // flow through: no read inside method exists(ArgNode arg | parameterValueFlowArg(p, arg, read) and argumentValueFlowsThrough(arg, mustBeNone, node) ) } pragma[nomagic] private predicate parameterValueFlowArg(ParamNode p, ArgNode arg, ReadStepTypesOption read) { parameterValueFlow(p, arg, read) and Cand::argumentValueFlowsThroughCand(arg, _, _) } pragma[nomagic] private predicate argumentValueFlowsThrough0( DataFlowCall call, ArgNode arg, ReturnKind kind, ReadStepTypesOption read ) { exists(ParamNode param | viableParamArg(call, param, arg) | parameterValueFlowReturn(param, kind, read) ) } /** * 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. */ pragma[nomagic] predicate argumentValueFlowsThrough(ArgNode arg, ReadStepTypesOption read, Node out) { exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThrough0(call, arg, kind, read) and out = getAnOutNode(call, kind) | // normal flow through read = TReadStepTypesNone() and compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(out)) or // getter compatibleTypes(getNodeDataFlowType(arg), read.getContainerType()) and compatibleTypes(read.getContentType(), getNodeDataFlowType(out)) ) } /** * 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 ) { exists(ReturnNode ret | parameterValueFlow(p, ret, read) and kind = ret.getKind() ) } } import Final } import FlowThrough cached private module DispatchWithCallContext { /** * Holds if the set of viable implementations that can be called by `call` * might be improved by knowing the call context. */ pragma[nomagic] private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) { mayBenefitFromCallContext(call, callable) or callEnclosingCallable(call, callable) and exists(viableCallableLambda(call, TDataFlowCallSome(_))) } /** * 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()) ) } /** * 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 | mayBenefitFromCallContextExt(call, c) and c = viableCallableExt(ctx) and ctxtgts = count(viableImplInCallContextExt(call, ctx)) and tgts = strictcount(viableCallableExt(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 = viableImplInCallContextExt(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 | mayBenefitFromCallContextExt(call, _) and c = viableCallableExt(call) and ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContextExt(call, ctx)) and tgts = strictcount(DataFlowCall ctx | callEnclosingCallable(call, viableCallableExt(ctx))) 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 = viableImplInCallContextExt(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. */ private predicate parameterValueFlowsToPreUpdate(ParamNode p, PostUpdateNode n) { parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone()) } cached predicate readSet(Node node1, ContentSet c, Node node2) { readStep(node1, c, node2) } cached predicate storeSet( Node node1, ContentSet c, Node node2, DataFlowType contentType, DataFlowType containerType ) { storeStep(node1, c, node2) and contentType = getNodeDataFlowType(node1) and containerType = getNodeDataFlowType(node2) or exists(Node n1, Node n2 | n1 = node1.(PostUpdateNode).getPreUpdateNode() and n2 = node2.(PostUpdateNode).getPreUpdateNode() | argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1) or readSet(n2, c, n1) and contentType = getNodeDataFlowType(n1) and containerType = getNodeDataFlowType(n2) ) } /** * Holds if data can flow from `node1` to `node2` via a direct assignment to * `c`. * * This includes reverse steps through reads when the result of the read has * been stored into, in order to handle cases like `x.f1.f2 = y`. */ cached predicate store( Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType ) { exists(ContentSet cs | c = cs.getAStoreContent() and storeSet(node1, cs, node2, contentType, containerType) ) } /** * Holds if data can flow from `fromNode` to `toNode` because they are the post-update * nodes of some function output and input respectively, where the output and input * are aliases. A typical example is a function returning `this`, implementing a fluent * interface. */ private predicate reverseStepThroughInputOutputAlias( PostUpdateNode fromNode, PostUpdateNode toNode ) { exists(Node fromPre, Node toPre | fromPre = fromNode.getPreUpdateNode() and toPre = toNode.getPreUpdateNode() | exists(DataFlowCall c | // Does the language-specific simpleLocalFlowStep already model flow // from function input to output? fromPre = getAnOutNode(c, _) and toPre.(ArgNode).argumentOf(c, _) and simpleLocalFlowStep(toPre.(ArgNode), fromPre) ) or argumentValueFlowsThrough(toPre, TReadStepTypesNone(), fromPre) ) } cached predicate simpleLocalFlowStepExt(Node node1, Node node2) { simpleLocalFlowStep(node1, node2) or reverseStepThroughInputOutputAlias(node1, node2) } /** * Holds if the call context `call` improves virtual dispatch in `callable`. */ cached predicate recordDataFlowCallSiteDispatch(DataFlowCall call, DataFlowCallable callable) { reducedViableImplInCallContext(_, callable, call) } /** * Holds if the call context `call` allows us to prune unreachable nodes in `callable`. */ cached predicate recordDataFlowCallSiteUnreachable(DataFlowCall call, DataFlowCallable callable) { exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call)) } cached predicate allowParameterReturnInSelfCached(ParamNode p) { allowParameterReturnInSelf(p) } cached newtype TCallContext = TAnyCallContext() or TSpecificCall(DataFlowCall call) { recordDataFlowCallSite(call, _) } or TSomeCall() or TReturn(DataFlowCallable c, DataFlowCall call) { reducedViableImplInReturn(c, call) } cached newtype TReturnPosition = TReturnPosition0(DataFlowCallable c, ReturnKindExt kind) { exists(ReturnNodeExt ret | c = returnNodeGetEnclosingCallable(ret) and kind = ret.getKind() ) } cached newtype TLocalFlowCallContext = TAnyLocalCall() or TSpecificLocalCall(DataFlowCall call) { isUnreachableInCallCached(_, call) } cached newtype TReturnKindExt = TValueReturn(ReturnKind kind) or TParamUpdate(ParameterPosition pos) { exists(ParamNode p | p.isParameterOf(_, pos)) } cached newtype TBooleanOption = TBooleanNone() or TBooleanSome(boolean b) { b = true or b = false } cached newtype TDataFlowCallOption = TDataFlowCallNone() or TDataFlowCallSome(DataFlowCall call) cached newtype TParamNodeOption = TParamNodeNone() or TParamNodeSome(ParamNode p) cached newtype TReturnCtx = TReturnCtxNone() or TReturnCtxNoFlowThrough() or TReturnCtxMaybeFlowThrough(ReturnPosition pos) cached newtype TAccessPathFront = TFrontNil() or TFrontHead(Content c) cached newtype TApproxAccessPathFront = TApproxFrontNil() or TApproxFrontHead(ContentApprox c) cached newtype TAccessPathFrontOption = TAccessPathFrontNone() or TAccessPathFrontSome(AccessPathFront apf) cached newtype TApproxAccessPathFrontOption = TApproxAccessPathFrontNone() or TApproxAccessPathFrontSome(ApproxAccessPathFront apf) } /** * Holds if the call context `call` either improves virtual dispatch in * `callable` or if it allows us to prune unreachable nodes in `callable`. */ predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) { recordDataFlowCallSiteDispatch(call, callable) or recordDataFlowCallSiteUnreachable(call, callable) } /** * A `Node` at which a cast can occur such that the type should be checked. */ class CastingNode instanceof Node { CastingNode() { castingNode(this) } string toString() { result = super.toString() } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } } private predicate readStepWithTypes( Node n1, DataFlowType container, ContentSet c, Node n2, DataFlowType content ) { readSet(n1, c, n2) and container = getNodeDataFlowType(n1) and content = getNodeDataFlowType(n2) } private newtype TReadStepTypesOption = TReadStepTypesNone() or TReadStepTypesSome(DataFlowType container, ContentSet c, DataFlowType content) { readStepWithTypes(_, container, c, _, content) } private class ReadStepTypesOption extends TReadStepTypesOption { predicate isSome() { this instanceof TReadStepTypesSome } DataFlowType getContainerType() { this = TReadStepTypesSome(result, _, _) } ContentSet getContent() { this = TReadStepTypesSome(_, result, _) } DataFlowType getContentType() { this = TReadStepTypesSome(_, _, result) } string toString() { if this.isSome() then result = "Some(..)" else result = "None()" } } /** * A call context to restrict the targets of virtual dispatch, prune local flow, * and match the call sites of flow into a method with flow out of a method. * * There are four cases: * - `TAnyCallContext()` : No restrictions on method flow. * - `TSpecificCall(DataFlowCall call)` : Flow entered through the * given `call`. This call improves the set of viable * dispatch targets for at least one method call in the current callable * or helps prune unreachable nodes in the current callable. * - `TSomeCall()` : Flow entered through a parameter. The * originating call does not improve the set of dispatch targets for any * method call in the current callable and was therefore not recorded. * - `TReturn(Callable c, DataFlowCall call)` : Flow reached `call` from `c` and * this dispatch target of `call` implies a reduced set of dispatch origins * to which data may flow if it should reach a `return` statement. */ abstract class CallContext extends TCallContext { abstract string toString(); /** Holds if this call context is relevant for `callable`. */ abstract predicate relevantFor(DataFlowCallable callable); } abstract class CallContextNoCall extends CallContext { } class CallContextAny extends CallContextNoCall, TAnyCallContext { override string toString() { result = "CcAny" } override predicate relevantFor(DataFlowCallable callable) { any() } } abstract class CallContextCall extends CallContext { /** Holds if this call context may be `call`. */ bindingset[call] abstract predicate matchesCall(DataFlowCall call); } class CallContextSpecificCall extends CallContextCall, TSpecificCall { override string toString() { exists(DataFlowCall call | this = TSpecificCall(call) | result = "CcCall(" + call + ")") } override predicate relevantFor(DataFlowCallable callable) { recordDataFlowCallSite(this.getCall(), callable) } override predicate matchesCall(DataFlowCall call) { call = this.getCall() } DataFlowCall getCall() { this = TSpecificCall(result) } } class CallContextSomeCall extends CallContextCall, TSomeCall { override string toString() { result = "CcSomeCall" } override predicate relevantFor(DataFlowCallable callable) { exists(ParamNode p | getNodeEnclosingCallable(p) = callable) } override predicate matchesCall(DataFlowCall call) { any() } } class CallContextReturn extends CallContextNoCall, TReturn { override string toString() { exists(DataFlowCall call | this = TReturn(_, call) | result = "CcReturn(" + call + ")") } override predicate relevantFor(DataFlowCallable callable) { exists(DataFlowCall call | this = TReturn(_, call) and callEnclosingCallable(call, callable)) } } /** * A call context that is relevant for pruning local flow. */ abstract class LocalCallContext extends TLocalFlowCallContext { abstract string toString(); /** Holds if this call context is relevant for `callable`. */ abstract predicate relevantFor(DataFlowCallable callable); } class LocalCallContextAny extends LocalCallContext, TAnyLocalCall { override string toString() { result = "LocalCcAny" } override predicate relevantFor(DataFlowCallable callable) { any() } } class LocalCallContextSpecificCall extends LocalCallContext, TSpecificLocalCall { LocalCallContextSpecificCall() { this = TSpecificLocalCall(call) } DataFlowCall call; DataFlowCall getCall() { result = call } override string toString() { result = "LocalCcCall(" + call + ")" } override predicate relevantFor(DataFlowCallable callable) { relevantLocalCCtx(call, callable) } } private predicate relevantLocalCCtx(DataFlowCall call, DataFlowCallable callable) { exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCallCached(n, call)) } /** * Gets the local call context given the call context and the callable that * the contexts apply to. */ LocalCallContext getLocalCallContext(CallContext ctx, DataFlowCallable callable) { ctx.relevantFor(callable) and if relevantLocalCCtx(ctx.(CallContextSpecificCall).getCall(), callable) then result.(LocalCallContextSpecificCall).getCall() = ctx.(CallContextSpecificCall).getCall() else result instanceof LocalCallContextAny } /** * The value of a parameter at function entry, viewed as a node in a data * flow graph. */ class ParamNode instanceof Node { ParamNode() { parameterNode(this, _, _) } string toString() { result = super.toString() } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } /** * Holds if this node is the parameter of callable `c` at the specified * position. */ predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { parameterNode(this, c, pos) } } /** A data-flow node that represents a call argument. */ class ArgNode instanceof Node { ArgNode() { argumentNode(this, _, _) } string toString() { result = super.toString() } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } /** Holds if this argument occurs at the given position in the given call. */ final predicate argumentOf(DataFlowCall call, ArgumentPosition pos) { argumentNode(this, call, pos) } } /** * A node from which flow can return to the caller. This is either a regular * `ReturnNode` or a `PostUpdateNode` corresponding to the value of a parameter. */ class ReturnNodeExt instanceof Node { ReturnNodeExt() { returnNodeExt(this, _) } string toString() { result = super.toString() } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } /** Gets the kind of this returned value. */ ReturnKindExt getKind() { returnNodeExt(this, result) } } /** * A node to which data can flow from a call. Either an ordinary out node * or a post-update node associated with a call argument. */ class OutNodeExt instanceof Node { OutNodeExt() { outNodeExt(this) } string toString() { result = super.toString() } predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } } /** * An extended return kind. A return kind describes how data can be returned * from a callable. This can either be through a returned value or an updated * parameter. */ abstract class ReturnKindExt extends TReturnKindExt { /** Gets a textual representation of this return kind. */ abstract string toString(); /** Gets a node corresponding to data flow out of `call`. */ final OutNodeExt getAnOutNode(DataFlowCall call) { result = getAnOutNodeExt(call, this) } } class ValueReturnKind extends ReturnKindExt, TValueReturn { private ReturnKind kind; ValueReturnKind() { this = TValueReturn(kind) } ReturnKind getKind() { result = kind } override string toString() { result = kind.toString() } } class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { private ParameterPosition pos; ParamUpdateReturnKind() { this = TParamUpdate(pos) } ParameterPosition getPosition() { result = pos } pragma[nomagic] ArgumentPosition getAMatchingArgumentPosition() { parameterMatch(pos, result) } override string toString() { result = "param update " + pos } } /** A callable tagged with a relevant return kind. */ class ReturnPosition extends TReturnPosition0 { private DataFlowCallable c; private ReturnKindExt kind; ReturnPosition() { this = TReturnPosition0(c, kind) } /** Gets the callable. */ DataFlowCallable getCallable() { result = c } /** Gets the return kind. */ ReturnKindExt getKind() { result = kind } /** Gets a textual representation of this return position. */ string toString() { result = "[" + kind + "] " + c } } /** * Gets the enclosing callable of `n`. Unlike `n.getEnclosingCallable()`, this * predicate ensures that joins go from `n` to the result instead of the other * way around. */ pragma[inline] DataFlowCallable getNodeEnclosingCallable(Node n) { nodeEnclosingCallable(pragma[only_bind_out](n), pragma[only_bind_into](result)) } /** Gets the type of `n` used for type pruning. */ pragma[inline] DataFlowType getNodeDataFlowType(Node n) { nodeDataFlowType(pragma[only_bind_out](n), pragma[only_bind_into](result)) } pragma[noinline] private DataFlowCallable returnNodeGetEnclosingCallable(ReturnNodeExt ret) { result = getNodeEnclosingCallable(ret) } pragma[noinline] private ReturnPosition getReturnPosition0(ReturnNodeExt ret, ReturnKindExt kind) { result.getCallable() = returnNodeGetEnclosingCallable(ret) and kind = result.getKind() } pragma[noinline] ReturnPosition getReturnPosition(ReturnNodeExt ret) { result = getReturnPosition0(ret, ret.getKind()) } /** * Checks whether `inner` can return to `call` in the call context `innercc`. * Assumes a context of `inner = viableCallableExt(call)`. */ bindingset[innercc, inner, call] predicate checkCallContextReturn(CallContext innercc, DataFlowCallable inner, DataFlowCall call) { innercc instanceof CallContextAny or exists(DataFlowCallable c0, DataFlowCall call0 | callEnclosingCallable(call0, inner) and innercc = TReturn(c0, call0) and c0 = prunedViableImplInCallContextReverse(call0, call) ) } /** * Checks whether `call` can resolve to `calltarget` in the call context `cc`. * Assumes a context of `calltarget = viableCallableExt(call)`. */ bindingset[cc, call, calltarget] predicate checkCallContextCall(CallContext cc, DataFlowCall call, DataFlowCallable calltarget) { exists(DataFlowCall ctx | cc = TSpecificCall(ctx) | if reducedViableImplInCallContext(call, _, ctx) then calltarget = prunedViableImplInCallContext(call, ctx) else any() ) or cc instanceof CallContextSomeCall or cc instanceof CallContextAny or cc instanceof CallContextReturn } /** * Resolves a return from `callable` in `cc` to `call`. This is equivalent to * `callable = viableCallableExt(call) and checkCallContextReturn(cc, callable, call)`. */ bindingset[cc, callable] predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) { cc instanceof CallContextAny and callable = viableCallableExt(call) or exists(DataFlowCallable c0, DataFlowCall call0 | callEnclosingCallable(call0, callable) and cc = TReturn(c0, call0) and c0 = prunedViableImplInCallContextReverse(call0, call) ) } /** * Resolves a call from `call` in `cc` to `result`. This is equivalent to * `result = viableCallableExt(call) and checkCallContextCall(cc, call, result)`. */ bindingset[call, cc] DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) { exists(DataFlowCall ctx | cc = TSpecificCall(ctx) | if reducedViableImplInCallContext(call, _, ctx) then result = prunedViableImplInCallContext(call, ctx) else result = viableCallableExt(call) ) or result = viableCallableExt(call) and cc instanceof CallContextSomeCall or result = viableCallableExt(call) and cc instanceof CallContextAny or result = viableCallableExt(call) and cc instanceof CallContextReturn } /** An optional Boolean value. */ class BooleanOption extends TBooleanOption { string toString() { this = TBooleanNone() and result = "" or this = TBooleanSome(any(boolean b | result = b.toString())) } } /** An optional `DataFlowCall`. */ class DataFlowCallOption extends TDataFlowCallOption { string toString() { this = TDataFlowCallNone() and result = "(none)" or exists(DataFlowCall call | this = TDataFlowCallSome(call) and result = call.toString() ) } } /** An optional `ParamNode`. */ class ParamNodeOption extends TParamNodeOption { string toString() { this = TParamNodeNone() and result = "(none)" or exists(ParamNode p | this = TParamNodeSome(p) and result = p.toString() ) } } /** * A return context used to calculate flow summaries in reverse flow. * * The possible values are: * * - `TReturnCtxNone()`: no return flow. * - `TReturnCtxNoFlowThrough()`: return flow, but flow through is not possible. * - `TReturnCtxMaybeFlowThrough(ReturnPosition pos)`: return flow, of kind `pos`, and * flow through may be possible. */ class ReturnCtx extends TReturnCtx { string toString() { this = TReturnCtxNone() and result = "(none)" or this = TReturnCtxNoFlowThrough() and result = "(no flow through)" or exists(ReturnPosition pos | this = TReturnCtxMaybeFlowThrough(pos) and result = pos.toString() ) } } /** * The front of an approximated access path. This is either a head or a nil. */ abstract class ApproxAccessPathFront extends TApproxAccessPathFront { abstract string toString(); abstract boolean toBoolNonEmpty(); ContentApprox getHead() { this = TApproxFrontHead(result) } pragma[nomagic] Content getAHead() { exists(ContentApprox cont | this = TApproxFrontHead(cont) and cont = getContentApprox(result) ) } } class ApproxAccessPathFrontNil extends ApproxAccessPathFront, TApproxFrontNil { override string toString() { result = "nil" } override boolean toBoolNonEmpty() { result = false } } class ApproxAccessPathFrontHead extends ApproxAccessPathFront, TApproxFrontHead { private ContentApprox c; ApproxAccessPathFrontHead() { this = TApproxFrontHead(c) } override string toString() { result = c.toString() } override boolean toBoolNonEmpty() { result = true } } /** An optional approximated access path front. */ class ApproxAccessPathFrontOption extends TApproxAccessPathFrontOption { string toString() { this = TApproxAccessPathFrontNone() and result = "" or this = TApproxAccessPathFrontSome(any(ApproxAccessPathFront apf | result = apf.toString())) } } /** * The front of an access path. This is either a head or a nil. */ abstract class AccessPathFront extends TAccessPathFront { abstract string toString(); abstract ApproxAccessPathFront toApprox(); Content getHead() { this = TFrontHead(result) } } class AccessPathFrontNil extends AccessPathFront, TFrontNil { override string toString() { result = "nil" } override ApproxAccessPathFront toApprox() { result = TApproxFrontNil() } } class AccessPathFrontHead extends AccessPathFront, TFrontHead { private Content c; AccessPathFrontHead() { this = TFrontHead(c) } override string toString() { result = c.toString() } override ApproxAccessPathFront toApprox() { result.getAHead() = c } } /** An optional access path front. */ class AccessPathFrontOption extends TAccessPathFrontOption { string toString() { this = TAccessPathFrontNone() and result = "" or this = TAccessPathFrontSome(any(AccessPathFront apf | result = apf.toString())) } }