diff --git a/csharp/ql/test/utils/modelgenerator/dataflow/Summaries.cs b/csharp/ql/test/utils/modelgenerator/dataflow/Summaries.cs index b59513504d9d..4c85b397ac1f 100644 --- a/csharp/ql/test/utils/modelgenerator/dataflow/Summaries.cs +++ b/csharp/ql/test/utils/modelgenerator/dataflow/Summaries.cs @@ -536,6 +536,12 @@ public void Apply(Action a, object o) { a(o); } + + private void CallApply() + { + // Test that this call to `Apply` does not interfere with the flow summaries generated for `Apply` + Apply(x => x, null); + } } public static class HigherOrderExtensionMethods diff --git a/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll b/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll index 506774857d8e..489caa33a21e 100644 --- a/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll +++ b/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll @@ -413,7 +413,7 @@ module MakeImpl Lang> { Cc ccNone(); - CcCall ccSomeCall(); + CcCall ccSomeCall(boolean isSource); /* * The following `instanceof` predicates are necessary for proper @@ -566,7 +566,7 @@ module MakeImpl Lang> { Nd node, Cc cc, SummaryCtx summaryCtx, Typ t, Ap ap, ApApprox apa, TypOption stored ) { sourceNode(node) and - (if hasSourceCallCtx() then cc = ccSomeCall() else cc = ccNone()) and + (if hasSourceCallCtx() then cc = ccSomeCall(true) else cc = ccNone()) and summaryCtx.isSourceCtx() and t = getNodeTyp(node) and ap instanceof ApNil and @@ -2080,7 +2080,7 @@ module MakeImpl Lang> { override predicate isSource() { sourceNode(node) and - (if hasSourceCallCtx() then cc = ccSomeCall() else cc = ccNone()) and + (if hasSourceCallCtx() then cc = ccSomeCall(true) else cc = ccNone()) and summaryCtx.isSourceCtx() and t = getNodeTyp(node) and ap instanceof ApNil @@ -2716,7 +2716,7 @@ module MakeImpl Lang> { Cc ccNone() { result = false } - CcCall ccSomeCall() { result = true } + CcCall ccSomeCall(boolean isSource) { result = true and isSource = [false, true] } predicate instanceofCc(Cc cc) { any() } diff --git a/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll b/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll index 51ebb3f8a730..3731808ccc85 100644 --- a/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll +++ b/shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll @@ -494,6 +494,10 @@ module MakeImplCommon Lang> { tgts = strictcount(Callable tgt | relevantCallEdgeIn(call, tgt)) and ctxtgts < tgts ) + or + // If only a single lambda can reach `call`, we still want to check for the call + // context, since lambdas outside the codebase may reach as well + exists(viableCallableLambda(call, TCallSome(ctx))) } /** @@ -619,7 +623,7 @@ module MakeImplCommon Lang> { TSpecificCall(CallSet calls, DispatchSet tgts, UnreachableSetOption unreachable) { hasCtx(_, calls, tgts, unreachable) } or - TSomeCall() or + TSomeCall(Boolean isSource) or TReturn(Callable c, Call call) { reducedViableImplInReturn(c, call) } /** @@ -632,9 +636,10 @@ module MakeImplCommon Lang> { * dispatch targets for all of `calls` to the set of dispatch targets in * `tgts`, and/or the specific call prunes unreachable nodes in the * current callable as given by `unreachable`. - * - `TSomeCall()` : Flow entered through a parameter. The + * - `TSomeCall(boolean isSource)` : 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. + * method call in the current callable and was therefore not recorded. `isSource` + * indicates whether the call context is for a flow source when using `FlowFeature`s. * - `TReturn(Callable c, Call 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. @@ -656,7 +661,13 @@ module MakeImplCommon Lang> { } private class CallContextSomeCall extends CallContextCall, TSomeCall { - override string toString() { result = "CcSomeCall" } + private boolean isSource; + + CallContextSomeCall() { this = TSomeCall(isSource) } + + override string toString() { + if isSource = true then result = "CcSomeCallSource" else result = "CcSomeCall" + } } private class CallContextReturn extends CallContextNoCall, TReturn { @@ -707,7 +718,7 @@ module MakeImplCommon Lang> { pragma[inline] predicate matchesCall(CcCall cc, Call call) { cc = Input2::getSpecificCallContextCall(call, _) or - cc = ccSomeCall() + cc = ccSomeCall(false) } class CcNoCall = CallContextNoCall; @@ -716,7 +727,7 @@ module MakeImplCommon Lang> { Cc ccNone() { result instanceof CallContextAny } - CcCall ccSomeCall() { result instanceof CallContextSomeCall } + CcCall ccSomeCall(boolean isSource) { result = TSomeCall(isSource) } predicate instanceofCc(Cc cc) { any() } @@ -739,7 +750,13 @@ module MakeImplCommon Lang> { /** Holds if `call` does not have a reduced set of dispatch targets in call context `ctx`. */ bindingset[call, ctx] predicate viableImplNotCallContextReduced(Call call, CallContext ctx) { - not Input2::callContextAffectsDispatch(call, ctx) + not Input2::callContextAffectsDispatch(call, ctx) and + // When sources have call contexts (using `FlowFeature`s), we check that `call` can + // dispatch in all possible call contexts + not ( + ctx = TSomeCall(true) and + Input2::callContextAffectsDispatch(call, _) + ) } /** @@ -822,7 +839,7 @@ module MakeImplCommon Lang> { CallContextCall getCallContextCall(Call call, Callable c) { if recordCallSiteDispatch(call, c) then result = Input2::getSpecificCallContextCall(call, c) - else result = TSomeCall() + else result = TSomeCall(false) } } @@ -850,7 +867,7 @@ module MakeImplCommon Lang> { CallContextCall getCallContextCall(Call call, Callable c) { if recordCallSite(call, c) then result = Input2::getSpecificCallContextCall(call, c) - else result = TSomeCall() + else result = TSomeCall(false) } } }