From dc5aa8e0f5fad31959ba38182f5f405810c03dc2 Mon Sep 17 00:00:00 2001 From: yoff Date: Mon, 1 Jun 2026 13:27:08 +0000 Subject: [PATCH] Python: deprecate Function.getAReturnValueFlowNode() and rewrite internal callers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the getAFlowNode deprecation in the same PR: same AST→legacy-CFG bridge pattern. The 11 internal call sites (across objects/, types/, frameworks/, and TypeTrackingImpl) are rewritten to bind a `Return ret` explicitly, then constrain via `ret.getScope() = f and n.getNode() = ret.getValue()`. The predicate itself is preserved with a deprecation note so external users do not experience churn. Semantic noop. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../2026-06-01-deprecate-getAReturnValueFlowNode.md | 4 ++++ python/ql/lib/semmle/python/Function.qll | 12 ++++++++++-- .../dataflow/new/internal/TypeTrackingImpl.qll | 6 ++++-- python/ql/lib/semmle/python/frameworks/Bottle.qll | 5 ++++- python/ql/lib/semmle/python/frameworks/Django.qll | 5 ++++- python/ql/lib/semmle/python/frameworks/FastApi.qll | 5 ++++- python/ql/lib/semmle/python/frameworks/Pyramid.qll | 5 ++++- python/ql/lib/semmle/python/frameworks/Stdlib.qll | 5 +++-- python/ql/lib/semmle/python/frameworks/Twisted.qll | 5 ++++- python/ql/lib/semmle/python/objects/Callables.qll | 5 +++-- python/ql/lib/semmle/python/objects/ObjectAPI.qll | 7 ++++++- python/ql/lib/semmle/python/types/FunctionObject.qll | 5 ++++- 12 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 python/ql/lib/change-notes/2026-06-01-deprecate-getAReturnValueFlowNode.md diff --git a/python/ql/lib/change-notes/2026-06-01-deprecate-getAReturnValueFlowNode.md b/python/ql/lib/change-notes/2026-06-01-deprecate-getAReturnValueFlowNode.md new file mode 100644 index 00000000000..42c6cc60cea --- /dev/null +++ b/python/ql/lib/change-notes/2026-06-01-deprecate-getAReturnValueFlowNode.md @@ -0,0 +1,4 @@ +--- +category: deprecated +--- +* The `Function.getAReturnValueFlowNode()` predicate has been deprecated. Bind a `Return` node explicitly instead — `exists(Return ret | ret.getScope() = f and n.getNode() = ret.getValue())`. This is a preparatory step towards migrating the dataflow library off the legacy CFG; it has no semantic effect. diff --git a/python/ql/lib/semmle/python/Function.qll b/python/ql/lib/semmle/python/Function.qll index c133275b8b7..2ef0366aa3b 100644 --- a/python/ql/lib/semmle/python/Function.qll +++ b/python/ql/lib/semmle/python/Function.qll @@ -153,8 +153,16 @@ class Function extends Function_, Scope, AstNode { override predicate contains(AstNode inner) { Scope.super.contains(inner) } - /** Gets a control flow node for a return value of this function */ - ControlFlowNode getAReturnValueFlowNode() { + /** + * DEPRECATED: bind a `Return` node explicitly instead, e.g. + * `exists(Return ret | ret.getScope() = this and n.getNode() = ret.getValue())`. + * This API is being phased out together with `AstNode.getAFlowNode()` to + * untangle the AST and CFG hierarchies in preparation for migrating the + * dataflow library off the legacy CFG. + * + * Gets a control flow node for a return value of this function. + */ + deprecated ControlFlowNode getAReturnValueFlowNode() { exists(Return ret | ret.getScope() = this and ret.getValue() = result.getNode() diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll index 95434b05451..b2a5f127ea3 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackingImpl.qll @@ -94,8 +94,10 @@ private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input { Node returnOf(Node callable, SummaryComponent return) { return = FlowSummaryImpl::Private::SummaryComponent::return() and // `result` should be the return value of a callable expression (lambda or function) referenced by `callable` - result.asCfgNode() = - callable.getALocalSource().asExpr().(CallableExpr).getInnerScope().getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = callable.getALocalSource().asExpr().(CallableExpr).getInnerScope() and + result.asCfgNode().getNode() = ret.getValue() + ) } // Relating callables to nodes diff --git a/python/ql/lib/semmle/python/frameworks/Bottle.qll b/python/ql/lib/semmle/python/frameworks/Bottle.qll index aa2c906948d..d4fc54f9568 100644 --- a/python/ql/lib/semmle/python/frameworks/Bottle.qll +++ b/python/ql/lib/semmle/python/frameworks/Bottle.qll @@ -73,7 +73,10 @@ module Bottle { /** A response returned by a view callable. */ class BottleReturnResponse extends Http::Server::HttpResponse::Range { BottleReturnResponse() { - this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = any(View::ViewCallable vc) and + this.asCfgNode().getNode() = ret.getValue() + ) } override DataFlow::Node getBody() { result = this } diff --git a/python/ql/lib/semmle/python/frameworks/Django.qll b/python/ql/lib/semmle/python/frameworks/Django.qll index ee0ed4a84dd..4f3fdbff01a 100644 --- a/python/ql/lib/semmle/python/frameworks/Django.qll +++ b/python/ql/lib/semmle/python/frameworks/Django.qll @@ -2872,7 +2872,10 @@ module PrivateDjango { DataFlow::CfgNode { DjangoRedirectViewGetRedirectUrlReturn() { - node = any(GetRedirectUrlFunction f).getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = any(GetRedirectUrlFunction f) and + node.getNode() = ret.getValue() + ) } override DataFlow::Node getRedirectLocation() { result = this } diff --git a/python/ql/lib/semmle/python/frameworks/FastApi.qll b/python/ql/lib/semmle/python/frameworks/FastApi.qll index 9d52e9b4f57..db8cd8e7896 100644 --- a/python/ql/lib/semmle/python/frameworks/FastApi.qll +++ b/python/ql/lib/semmle/python/frameworks/FastApi.qll @@ -309,7 +309,10 @@ module FastApi { FastApiRouteSetup routeSetup; FastApiRequestHandlerReturn() { - node = routeSetup.getARequestHandler().getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = routeSetup.getARequestHandler() and + node.getNode() = ret.getValue() + ) } override DataFlow::Node getBody() { result = this } diff --git a/python/ql/lib/semmle/python/frameworks/Pyramid.qll b/python/ql/lib/semmle/python/frameworks/Pyramid.qll index 63e19363fe8..dbfd684f02c 100644 --- a/python/ql/lib/semmle/python/frameworks/Pyramid.qll +++ b/python/ql/lib/semmle/python/frameworks/Pyramid.qll @@ -166,7 +166,10 @@ module Pyramid { /** A response returned by a view callable. */ private class PyramidReturnResponse extends Http::Server::HttpResponse::Range { PyramidReturnResponse() { - this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() and + exists(Return ret | + ret.getScope() = any(View::ViewCallable vc) and + this.asCfgNode().getNode() = ret.getValue() + ) and not this = instance() } diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index 5d3b994880a..41857457dd1 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -2254,8 +2254,9 @@ module StdlibPrivate { DataFlow::CfgNode { WsgirefSimpleServerApplicationReturn() { - exists(WsgirefSimpleServerApplication requestHandler | - node = requestHandler.getAReturnValueFlowNode() + exists(WsgirefSimpleServerApplication requestHandler, Return ret | + ret.getScope() = requestHandler and + node.getNode() = ret.getValue() ) } diff --git a/python/ql/lib/semmle/python/frameworks/Twisted.qll b/python/ql/lib/semmle/python/frameworks/Twisted.qll index 60aedd8fb58..510c7738a9d 100644 --- a/python/ql/lib/semmle/python/frameworks/Twisted.qll +++ b/python/ql/lib/semmle/python/frameworks/Twisted.qll @@ -182,7 +182,10 @@ private module Twisted { DataFlow::CfgNode { TwistedResourceRenderMethodReturn() { - this.asCfgNode() = any(TwistedResourceRenderMethod meth).getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = any(TwistedResourceRenderMethod meth) and + this.asCfgNode().getNode() = ret.getValue() + ) } override DataFlow::Node getBody() { result = this } diff --git a/python/ql/lib/semmle/python/objects/Callables.qll b/python/ql/lib/semmle/python/objects/Callables.qll index c42393cc3c6..4aba4298c94 100644 --- a/python/ql/lib/semmle/python/objects/Callables.qll +++ b/python/ql/lib/semmle/python/objects/Callables.qll @@ -81,11 +81,12 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti pragma[nomagic] override predicate callResult(PointsToContext callee, ObjectInternal obj, CfgOrigin origin) { - exists(Function func, ControlFlowNode rval, ControlFlowNode forigin | + exists(Function func, Return ret, ControlFlowNode rval, ControlFlowNode forigin | func = this.getScope() and callee.appliesToScope(func) | - rval = func.getAReturnValueFlowNode() and + ret.getScope() = func and + rval.getNode() = ret.getValue() and PointsToInternal::pointsTo(rval, callee, obj, forigin) and origin = CfgOrigin::fromCfgNode(forigin) ) diff --git a/python/ql/lib/semmle/python/objects/ObjectAPI.qll b/python/ql/lib/semmle/python/objects/ObjectAPI.qll index a5d4d91cc7a..d6f3b5f1fca 100644 --- a/python/ql/lib/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/lib/semmle/python/objects/ObjectAPI.qll @@ -745,7 +745,12 @@ class PythonFunctionValue extends FunctionValue { override int maxParameters() { result = this.getScope().getMaxPositionalArguments() } /** Gets a control flow node corresponding to a return statement in this function */ - ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() } + ControlFlowNode getAReturnedNode() { + exists(Return ret | + ret.getScope() = this.getScope() and + result.getNode() = ret.getValue() + ) + } override ClassValue getARaisedType() { scope_raises(result, this.getScope()) } diff --git a/python/ql/lib/semmle/python/types/FunctionObject.qll b/python/ql/lib/semmle/python/types/FunctionObject.qll index d03f0862300..55ac1b25538 100644 --- a/python/ql/lib/semmle/python/types/FunctionObject.qll +++ b/python/ql/lib/semmle/python/types/FunctionObject.qll @@ -137,7 +137,10 @@ class PyFunctionObject extends FunctionObject { /** Gets a control flow node corresponding to the value of a return statement */ ControlFlowNodeWithPointsTo getAReturnedNode() { - result = this.getFunction().getAReturnValueFlowNode() + exists(Return ret | + ret.getScope() = this.getFunction() and + result.getNode() = ret.getValue() + ) } override string descriptiveString() {