@@ -980,6 +980,19 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
980980 */
981981 final class PreControlFlowNode = NodeImpl ;
982982
983+ /**
984+ * Holds if `n` is the in-order or post-order control flow node for `ast`.
985+ *
986+ * Unlike the `PreControlFlowNode.isIn` member predicate, this is computed
987+ * structurally (directly from the underlying node representation) and so
988+ * does not give rise to a dependency on node reachability. It is intended
989+ * for languages implementing `Input2::deferExitStep`, whose definition must
990+ * not depend on `reachable` (see `succIgnoringDeferExit`); such languages
991+ * can use this to identify nodes inside a negation without introducing a
992+ * non-monotonic cycle.
993+ */
994+ predicate isInOrderNode ( PreControlFlowNode n , AstNode ast ) { n = TAstNode ( ast ) }
995+
983996 private class BeforeNode extends NodeImpl , TBeforeNode {
984997 private AstNode n ;
985998
@@ -1051,7 +1064,7 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
10511064 }
10521065
10531066 /** The `PreControlFlowNode` at the entry point of a callable. */
1054- final private class EntryNodeImpl extends NodeImpl , TEntryNode {
1067+ final class EntryNodeImpl extends NodeImpl , TEntryNode {
10551068 private Callable c ;
10561069
10571070 EntryNodeImpl ( ) { this = TEntryNode ( c ) }
@@ -1206,6 +1219,44 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
12061219 none ( )
12071220 }
12081221
1222+ /**
1223+ * Holds if the language-specific implementation takes over the routing of
1224+ * the normal fall-through from callable `c`'s body to its normal exit
1225+ * node.
1226+ *
1227+ * When this holds, the library's default edge from the "after" node of
1228+ * `c`'s body to the normal exit node is suppressed, and the language is
1229+ * responsible for routing the fall-through to the normal exit itself (for
1230+ * example, to interpose a function-exit epilogue such as Go's deferred
1231+ * calls). This complements `callableExitStep`, which the language can use
1232+ * to add the replacement edge into the normal exit node.
1233+ *
1234+ * The default implementation does not override any fall-through edges.
1235+ */
1236+ default predicate overridesCallableBodyExit ( Callable c ) { none ( ) }
1237+
1238+ /**
1239+ * Holds if there is a local non-abrupt step from `n1` to `n2` that forms
1240+ * part of a function-exit epilogue whose placement depends on
1241+ * reachability (such as Go's deferred calls, which run at function exit in
1242+ * last-in-first-out order, gated by whether their registration is
1243+ * reachable on the path to a given exit).
1244+ *
1245+ * Edges added here are included in the final control flow graph exactly
1246+ * like ordinary `step` edges, but they are *excluded* when the library
1247+ * computes the defer-free reachability exposed through
1248+ * `getASuccessorIgnoringDeferredExit`. This lets a language compute the
1249+ * reachability gate without observing the epilogue edges it is in the
1250+ * process of defining, avoiding a circularity.
1251+ *
1252+ * Each `deferExitStep` edge must be disjoint from every other `step` edge
1253+ * (i.e. a pair `(n1, n2)` that is a `deferExitStep` must not also arise
1254+ * from `step`), so that the defer-free reachability is well defined.
1255+ *
1256+ * The default implementation adds no such steps.
1257+ */
1258+ default predicate deferExitStep ( PreControlFlowNode n1 , PreControlFlowNode n2 ) { none ( ) }
1259+
12091260 /**
12101261 * Holds if there is a local non-abrupt step from `n1` to `n2`.
12111262 *
@@ -1455,6 +1506,22 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
14551506
14561507 /** Holds if there is a local non-abrupt step from `n1` to `n2`. */
14571508 private predicate explicitStep ( PreControlFlowNode n1 , PreControlFlowNode n2 ) {
1509+ explicitStepCommon ( n1 , n2 )
1510+ or
1511+ Input2:: deferExitStep ( n1 , n2 )
1512+ }
1513+
1514+ /**
1515+ * Holds if there is a local non-abrupt step from `n1` to `n2`, excluding
1516+ * the reachability-dependent function-exit epilogue edges contributed by
1517+ * `Input2::deferExitStep`.
1518+ *
1519+ * This is the basis for the defer-free reachability exposed through
1520+ * `getASuccessorIgnoringDeferredExit`, and it must not depend on
1521+ * `deferExitStep` (so that a language can compute the reachability gate
1522+ * for its `deferExitStep` edges without circularity).
1523+ */
1524+ private predicate explicitStepCommon ( PreControlFlowNode n1 , PreControlFlowNode n2 ) {
14581525 Input2:: step ( n1 , n2 )
14591526 or
14601527 exists ( Callable c |
@@ -1479,7 +1546,8 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
14791546 )
14801547 or
14811548 n1 .isAfter ( getBodyExit ( c ) ) and
1482- n2 .( NormalExitNodeImpl ) .getEnclosingCallable ( ) = c
1549+ n2 .( NormalExitNodeImpl ) .getEnclosingCallable ( ) = c and
1550+ not Input2:: overridesCallableBodyExit ( c )
14831551 or
14841552 Input2:: callableExitStep ( n1 , c , true ) and
14851553 n2 .( NormalExitNodeImpl ) .getEnclosingCallable ( ) = c
@@ -1870,10 +1938,15 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
18701938 /**
18711939 * Holds if `ast` does not have explicitly defined control flow steps
18721940 * and therefore should use default left-to-right evaluation.
1941+ *
1942+ * This uses `explicitStepCommon` rather than `explicitStep` so that it
1943+ * does not depend on `Input2::deferExitStep` (whose edges never originate
1944+ * from a "before" node, so the two agree on `before` nodes anyway). This
1945+ * keeps the defer-free reachability independent of `deferExitStep`.
18731946 */
18741947 private predicate defaultCfg ( AstNode ast ) {
18751948 hasCfg ( ast ) and
1876- not explicitStep ( any ( PreControlFlowNode n | n .isBefore ( ast ) ) , _)
1949+ not explicitStepCommon ( any ( PreControlFlowNode n | n .isBefore ( ast ) ) , _)
18771950 }
18781951
18791952 private module ChildDenseRankInput implements DenseRankInputSig1 {
@@ -1927,6 +2000,14 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
19272000 explicitStep ( n1 , n2 ) or defaultStep ( n1 , n2 )
19282001 }
19292002
2003+ /**
2004+ * Holds if there is a local non-abrupt step from `n1` to `n2`, excluding
2005+ * the function-exit epilogue edges contributed by `Input2::deferExitStep`.
2006+ */
2007+ private predicate stepIgnoringDeferExit ( PreControlFlowNode n1 , PreControlFlowNode n2 ) {
2008+ explicitStepCommon ( n1 , n2 ) or defaultStep ( n1 , n2 )
2009+ }
2010+
19302011 /**
19312012 * Holds if the execution of `ast` may result in an abrupt completion
19322013 * `c` originating at `last`.
@@ -1988,6 +2069,45 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
19882069 preSucc ( n1 , n2 , t )
19892070 }
19902071
2072+ /**
2073+ * Holds if `n2` is a normal successor of `n1` of type `t`, computed over
2074+ * the defer-free step relation `stepIgnoringDeferExit` (i.e. ignoring the
2075+ * epilogue edges added through `Input2::deferExitStep`).
2076+ *
2077+ * Abrupt-completion edges are deliberately omitted: this relation only
2078+ * needs to expose the normal control flow used to compute a language's
2079+ * reachability gate for its `deferExitStep` edges.
2080+ */
2081+ private predicate preSuccIgnoringDeferExit (
2082+ PreControlFlowNode n1 , PreControlFlowNode n2 , SuccessorType t
2083+ ) {
2084+ stepIgnoringDeferExit ( n1 , n2 ) and n2 = TAfterValueNode ( _, t )
2085+ or
2086+ stepIgnoringDeferExit ( n1 , n2 ) and n2 .( AdditionalNode ) .getSuccessorType ( ) = t
2087+ or
2088+ stepIgnoringDeferExit ( n1 , n2 ) and
2089+ not n2 instanceof AfterValueNode and
2090+ not n2 instanceof AdditionalNode and
2091+ t instanceof DirectSuccessor
2092+ }
2093+
2094+ /**
2095+ * Holds if `n2` is a successor of `n1` of type `t`, ignoring the
2096+ * epilogue edges added through `Input2::deferExitStep`.
2097+ *
2098+ * This exposes the defer-free reachability that a language needs in order
2099+ * to compute the reachability gate for its `deferExitStep` edges without
2100+ * observing those edges. It is typed over `PreControlFlowNode` (rather
2101+ * than the reachability-restricted `ControlFlowNode`) so that a language
2102+ * can use it to compute `deferExitStep` without a non-monotonic cycle
2103+ * through `reachable`.
2104+ */
2105+ cached
2106+ predicate succIgnoringDeferExit ( PreControlFlowNode n1 , PreControlFlowNode n2 , SuccessorType t ) {
2107+ Input1:: cfgCachedStageRef ( ) and
2108+ preSuccIgnoringDeferExit ( n1 , n2 , t )
2109+ }
2110+
19912111 /** The cached stage of the control flow graph. */
19922112 cached
19932113 module CfgCachedStage {
@@ -2002,7 +2122,8 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
20022122 ( simpleLeafNode ( _) implies any ( ) ) and
20032123 ( exists ( TBeforeNode ( _) ) implies any ( ) ) and
20042124 ( reachable ( _) implies any ( ) ) and
2005- ( succ ( _, _, _) implies any ( ) )
2125+ ( succ ( _, _, _) implies any ( ) ) and
2126+ ( succIgnoringDeferExit ( _, _, _) implies any ( ) )
20062127 }
20072128 }
20082129
@@ -2026,6 +2147,19 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
20262147 /** Gets an immediate successor of this node, if this is not an `ExitNode`. */
20272148 ControlFlowNode getASuccessor ( ) { result = this .getASuccessor ( _) }
20282149
2150+ /**
2151+ * Gets an immediate successor of this node, ignoring the
2152+ * reachability-dependent function-exit epilogue edges added through
2153+ * `deferExitStep` (such as Go's deferred calls).
2154+ *
2155+ * This is intended for languages that need to compute reachability
2156+ * before those epilogue edges are added; it should not be used as a
2157+ * general successor relation.
2158+ */
2159+ ControlFlowNode getASuccessorIgnoringDeferredExit ( ) {
2160+ succIgnoringDeferExit ( this , result , _)
2161+ }
2162+
20292163 /** Gets an immediate predecessor of this node, if this is not an `EntryNode`. */
20302164 ControlFlowNode getAPredecessor ( ) { result .getASuccessor ( ) = this }
20312165
0 commit comments