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/Exprs.qll b/python/ql/lib/semmle/python/Exprs.qll index 9ce6f9e6680..72fd0434d64 100644 --- a/python/ql/lib/semmle/python/Exprs.qll +++ b/python/ql/lib/semmle/python/Exprs.qll @@ -69,7 +69,6 @@ class AssignExpr extends AssignExpr_ { class Attribute extends Attribute_ { /* syntax: Expr.name */ override Expr getASubExpression() { result = this.getObject() } - /** Gets the name of this attribute. That is the `name` in `obj.name` */ string getName() { result = Attribute_.super.getAttr() } @@ -96,6 +95,7 @@ class Subscript extends Subscript_ { } Expr getObject() { result = Subscript_.super.getValue() } + } /** A call expression, such as `func(...)` */ @@ -110,7 +110,6 @@ class Call extends Call_ { override predicate hasSideEffects() { any() } override string toString() { result = this.getFunc().toString() + "()" } - /** Gets a tuple (*) argument of this call. */ Expr getStarargs() { result = this.getAPositionalArg().(Starred).getValue() } @@ -196,6 +195,7 @@ class IfExp extends IfExp_ { override Expr getASubExpression() { result = this.getTest() or result = this.getBody() or result = this.getOrelse() } + } /** A starred expression, such as the `*rest` in the assignment `first, *rest = seq` */ @@ -404,6 +404,7 @@ class PlaceHolder extends PlaceHolder_ { override Expr getASubExpression() { none() } override string toString() { result = "$" + this.getId() } + } /** A tuple expression such as `( 1, 3, 5, 7, 9 )` */ @@ -469,7 +470,6 @@ class Name extends Name_ { override Expr getASubExpression() { none() } override string toString() { result = this.getId() } - override predicate isArtificial() { /* Artificial variable names in comprehensions all start with "." */ this.getId().charAt(0) = "." @@ -574,7 +574,6 @@ abstract class NameConstant extends Name, ImmutableLiteral { override string toString() { name_consts(this, result) } override predicate isConstant() { any() } - override predicate isArtificial() { none() } } 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/Import.qll b/python/ql/lib/semmle/python/Import.qll index d4f5109ed47..84158ab19d5 100644 --- a/python/ql/lib/semmle/python/Import.qll +++ b/python/ql/lib/semmle/python/Import.qll @@ -148,7 +148,6 @@ class ImportExpr extends ImportExpr_ { /** A `from ... import ...` expression */ class ImportMember extends ImportMember_ { override Expr getASubExpression() { result = this.getModule() } - override predicate hasSideEffects() { /* Strictly this only has side-effects if the module is a package */ any() 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() {