Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Python: deprecate Function.getAReturnValueFlowNode() and rewrite inte…
…rnal callers

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>
  • Loading branch information
yoff and Copilot committed Jun 22, 2026
commit 1a9bb2416a63942da89a3104a975ba4815f4815b
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 10 additions & 2 deletions python/ql/lib/semmle/python/Function.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion python/ql/lib/semmle/python/frameworks/Bottle.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
5 changes: 4 additions & 1 deletion python/ql/lib/semmle/python/frameworks/Django.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
5 changes: 4 additions & 1 deletion python/ql/lib/semmle/python/frameworks/FastApi.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
5 changes: 4 additions & 1 deletion python/ql/lib/semmle/python/frameworks/Pyramid.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
5 changes: 3 additions & 2 deletions python/ql/lib/semmle/python/frameworks/Stdlib.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
}

Expand Down
5 changes: 4 additions & 1 deletion python/ql/lib/semmle/python/frameworks/Twisted.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
5 changes: 3 additions & 2 deletions python/ql/lib/semmle/python/objects/Callables.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
Expand Down
7 changes: 6 additions & 1 deletion python/ql/lib/semmle/python/objects/ObjectAPI.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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()) }

Expand Down
5 changes: 4 additions & 1 deletion python/ql/lib/semmle/python/types/FunctionObject.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading