From 28afda1726189c656545aa5dffc89f36993b6065 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 16 Jun 2026 14:44:47 +0100 Subject: [PATCH] Shared CFG: add callableExitStep hook for routing epilogue tails to exit nodes --- .../codeql/controlflow/ControlFlowGraph.qll | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll index 7f10f3d6436..146e5f77399 100644 --- a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll +++ b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll @@ -1189,6 +1189,23 @@ module Make0 Ast> { none() } + /** + * Holds if `n` steps directly to the normal exit node (`normal = true`) + * or the exceptional exit node (`normal = false`) of callable `c`. + * + * By default the only node that reaches a callable's normal exit is the + * "after" node of its body. This predicate lets a language route the tail + * of a function epilogue (such as Go's result-read or deferred-call nodes) + * to the appropriate exit node, which is useful when the body cannot + * terminate normally (e.g. it always ends in a `return`) and therefore has + * no "after" node to anchor the epilogue on. + * + * The default implementation adds no such steps. + */ + default predicate callableExitStep(PreControlFlowNode n, Callable c, boolean normal) { + none() + } + /** * Holds if there is a local non-abrupt step from `n1` to `n2`. * @@ -1464,6 +1481,12 @@ module Make0 Ast> { n1.isAfter(getBodyExit(c)) and n2.(NormalExitNodeImpl).getEnclosingCallable() = c or + Input2::callableExitStep(n1, c, true) and + n2.(NormalExitNodeImpl).getEnclosingCallable() = c + or + Input2::callableExitStep(n1, c, false) and + n2.(ExceptionalExitNodeImpl).getEnclosingCallable() = c + or n1.(AnnotatedExitNodeImpl).getEnclosingCallable() = c and n2.(ExitNodeImpl).getEnclosingCallable() = c )