From edfe91832bb5ab6dc37c7cdf1fdb5ce2b89ecd55 Mon Sep 17 00:00:00 2001 From: Copilot Date: Tue, 5 May 2026 14:25:43 +0000 Subject: [PATCH] Python: compact-renumber FunctionExpr/Lambda defaults `Args.getDefault(int)` and `Args.getKwDefault(int)` are indexed by argument position (with gaps for args without defaults), not by default position. The CFG `getChild` predicate for FunctionDefExpr and LambdaExpr therefore had gaps at low indices and collisions where defaults and kwdefaults overlapped, producing parallel edges before the FunctionExpr. Use `rank` to compact-renumber `getDefault(n)` and `getKwDefault(n)` in source order. Verified on a CPython database: removes ~536 `multipleSuccessors` consistency results (1340 -> 804); the rest are `for/else` and `while/else`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../controlflow/internal/AstNodeImpl.qll | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll index 8a86ffa0874..d6c63c6027c 100644 --- a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll +++ b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll @@ -835,9 +835,22 @@ module Ast implements AstSig { FunctionDefExpr() { funcExpr = this.asExpr() } - Expr getDefault(int n) { result = TExpr(funcExpr.getArgs().getDefault(n)) } + /** + * Gets the `n`th default for a positional argument, in evaluation + * order. Note that `Args.getDefault(int)` is indexed by argument + * position (with gaps for arguments without defaults), so we must + * renumber here to obtain contiguous indices. + */ + Expr getDefault(int n) { + result = + TExpr(rank[n + 1](Py::Expr d, int i | d = funcExpr.getArgs().getDefault(i) | d order by i)) + } - Expr getKwDefault(int n) { result = TExpr(funcExpr.getArgs().getKwDefault(n)) } + /** Gets the `n`th default for a keyword-only argument, in evaluation order. */ + Expr getKwDefault(int n) { + result = + TExpr(rank[n + 1](Py::Expr d, int i | d = funcExpr.getArgs().getKwDefault(i) | d order by i)) + } int getNumberOfDefaults() { result = count(funcExpr.getArgs().getADefault()) } } @@ -848,9 +861,17 @@ module Ast implements AstSig { LambdaExpr() { lambda = this.asExpr() } - Expr getDefault(int n) { result = TExpr(lambda.getArgs().getDefault(n)) } + /** Gets the `n`th default for a positional argument, in evaluation order. */ + Expr getDefault(int n) { + result = + TExpr(rank[n + 1](Py::Expr d, int i | d = lambda.getArgs().getDefault(i) | d order by i)) + } - Expr getKwDefault(int n) { result = TExpr(lambda.getArgs().getKwDefault(n)) } + /** Gets the `n`th default for a keyword-only argument, in evaluation order. */ + Expr getKwDefault(int n) { + result = + TExpr(rank[n + 1](Py::Expr d, int i | d = lambda.getArgs().getKwDefault(i) | d order by i)) + } int getNumberOfDefaults() { result = count(lambda.getArgs().getADefault()) } }