From 01a9bec7df4a75d64cb589850f6b1f8f82958922 Mon Sep 17 00:00:00 2001 From: Taus Date: Mon, 13 Apr 2026 21:19:07 +0000 Subject: [PATCH] Python: Exclude sources in functions with unclear returns A common source of FPs is when the flow inside a function depends on some argument to the function. In this case, if a non-container class is being returned in _some_ branch, we behave as if it _always_ is returned, leading to false positives where the code is actually safe because the argument to the function prevents the bad return from being executed. --- .../new/internal/ClassInstanceFlow.qll | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/ClassInstanceFlow.qll b/python/ql/lib/semmle/python/dataflow/new/internal/ClassInstanceFlow.qll index 5558175d04b..2b907c243d2 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/ClassInstanceFlow.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/ClassInstanceFlow.qll @@ -97,6 +97,22 @@ module ClassInstanceFlow { node = DataFlow::BarrierGuard::getABarrierNode() } + /** + * Holds if `call` is inside a branch that is guarded by a condition + * depending on a parameter of the enclosing function. In such cases, + * the instantiation is contextual — it only happens for certain argument + * values — and we cannot determine from the call site whether it will + * actually execute. + */ + private predicate parameterGuardedCall(CallNode call) { + exists(ConditionBlock guard, DataFlow::ParameterNode param, DataFlow::Node guardSubExpr | + guard.controls(call.getBasicBlock(), _) and + param.getScope() = call.getScope() and + guardSubExpr.asCfgNode() = guard.getLastNode().getAChild*() and + DataFlow::localFlow(param, guardSubExpr) + ) + } + predicate isAdditionalFlowStep( DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo ) { @@ -109,7 +125,11 @@ module ClassInstanceFlow { nodeTo.asCfgNode() = call and // Exclude decorator applications, where the result is a proxy // rather than a typical instance. - not call.getNode() = any(FunctionExpr fe).getADecoratorCall() + not call.getNode() = any(FunctionExpr fe).getADecoratorCall() and + // Exclude instantiations guarded by parameter-dependent conditions, + // since we cannot determine from the call site whether the guard + // will be satisfied. + not parameterGuardedCall(call) ) } }