From 58c7a4bd6248fd79daabf49016bf74b878e87496 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 17 Aug 2026 10:29:30 +0200 Subject: [PATCH] Ruby: Test exception splitting removal. --- .../ruby/controlflow/internal/Splitting.qll | 449 ++++++++---------- 1 file changed, 210 insertions(+), 239 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll index 737f450b4f23..d63eaa7b0f67 100644 --- a/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll +++ b/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll @@ -13,16 +13,15 @@ private import codeql.ruby.controlflow.ControlFlowGraph cached private module Cached { cached - newtype TSplitKind = - TConditionalCompletionSplitKind() { forceCachingInSameStage() } or - TEnsureSplitKind(int nestLevel) { nestLevel = any(Trees::BodyStmtTree t).getNestLevel() } + newtype TSplitKind = TConditionalCompletionSplitKind() { forceCachingInSameStage() } + // TEnsureSplitKind(int nestLevel) { nestLevel = any(Trees::BodyStmtTree t).getNestLevel() } cached - newtype TSplit = - TConditionalCompletionSplit(ConditionalCompletion c) or - TEnsureSplit(EnsureSplitting::EnsureSplitType type, int nestLevel) { - nestLevel = any(Trees::BodyStmtTree t).getNestLevel() - } + newtype TSplit = TConditionalCompletionSplit(ConditionalCompletion c) + // or + // TEnsureSplit(EnsureSplitting::EnsureSplitType type, int nestLevel) { + // nestLevel = any(Trees::BodyStmtTree t).getNestLevel() + // } } import Cached @@ -114,235 +113,207 @@ module ConditionalCompletionSplitting { class ConditionalCompletionSplit = ConditionalCompletionSplitting::ConditionalCompletionSplit; module EnsureSplitting { - /** - * The type of a split `ensure` node. - * - * The type represents one of the possible ways of entering an `ensure` - * block. For example, if a block ends with a `return` statement, then - * the `ensure` block must end with a `return` as well (provided that - * the `ensure` block executes normally). - */ - class EnsureSplitType extends SuccessorType { - EnsureSplitType() { not this instanceof ConditionalSuccessor } - - /** Holds if this split type matches entry into an `ensure` block with completion `c`. */ - predicate isSplitForEntryCompletion(Completion c) { - if c instanceof NormalCompletion - then - // If the entry into the `ensure` block completes with any normal completion, - // it simply means normal execution after the `ensure` block - this instanceof DirectSuccessor - else this = c.getAMatchingSuccessorType() - } - } - - /** A node that belongs to an `ensure` block. */ - private class EnsureNode extends AstNode { - private Trees::BodyStmtTree block; - - EnsureNode() { this = block.getAnEnsureDescendant() } - - int getNestLevel() { result = block.getNestLevel() } - - /** Holds if this node is the entry node in the `ensure` block it belongs to. */ - predicate isEntryNode() { first(block.getEnsure(), this) } - - Ast::BodyStmt getBlock() { result = block } - - pragma[noinline] - predicate isEntered(AstNode pred, int nestLevel, Completion c) { - this.isEntryNode() and - nestLevel = this.getNestLevel() and - succ(pred, this, c) and - // the entry node may be reachable via a backwards loop edge; in this case - // the split has already been entered - not pred = block.getAnEnsureDescendant() - } - } - - /** - * A split for nodes belonging to an `ensure` block, which determines how to - * continue execution after leaving the `ensure` block. For example, in - * - * ```rb - * begin - * if x - * raise "Exception" - * end - * ensure - * puts "Ensure" - * end - * ``` - * - * all control flow nodes in the `ensure` block have two splits: one representing - * normal execution of the body (when `x` evaluates to `true`), and one representing - * exceptional execution of the body (when `x` evaluates to `false`). - */ - class EnsureSplit extends Split, TEnsureSplit { - private EnsureSplitType type; - private int nestLevel; - - EnsureSplit() { this = TEnsureSplit(type, nestLevel) } - - /** - * Gets the type of this `ensure` split, that is, how to continue execution after the - * `ensure` block. - */ - EnsureSplitType getType() { result = type } - - /** Gets the nesting level. */ - int getNestLevel() { result = nestLevel } - - override string toString() { - if type instanceof DirectSuccessor - then result = "" - else - if nestLevel > 0 - then result = "ensure(" + nestLevel + "): " + type.toString() - else result = "ensure: " + type.toString() - } - } - - private int getListOrder(EnsureSplitKind kind) { - result = ConditionalCompletionSplitting::getNextListOrder() + kind.getNestLevel() - } - - int getNextListOrder() { - result = max([getListOrder(_) + 1, ConditionalCompletionSplitting::getNextListOrder()]) - } - - private class EnsureSplitKind extends SplitKind, TEnsureSplitKind { - private int nestLevel; - - EnsureSplitKind() { this = TEnsureSplitKind(nestLevel) } - - /** Gets the nesting level. */ - int getNestLevel() { result = nestLevel } - - override int getListOrder() { result = getListOrder(this) } - - override string toString() { result = "ensure (" + nestLevel + ")" } - } - - private class EnsureSplitImpl extends SplitImpl instanceof EnsureSplit { - override EnsureSplitKind getKind() { result.getNestLevel() = super.getNestLevel() } - - override predicate hasEntry(AstNode pred, AstNode succ, Completion c) { - succ.(EnsureNode).isEntered(pred, super.getNestLevel(), c) and - super.getType().isSplitForEntryCompletion(c) - } - - override predicate hasEntryScope(CfgScope scope, AstNode first) { none() } - - /** - * Holds if this split applies to `pred`, where `pred` is a valid predecessor. - */ - private predicate appliesToPredecessor(AstNode pred) { - this.appliesTo(pred) and - (succ(pred, _, _) or succExit(_, pred, _)) - } - - pragma[noinline] - private predicate exit0(AstNode pred, Trees::BodyStmtTree block, int nestLevel, Completion c) { - this.appliesToPredecessor(pred) and - nestLevel = block.getNestLevel() and - block.last(pred, c) - } - - /** - * Holds if `pred` may exit this split with completion `c`. The Boolean - * `inherited` indicates whether `c` is an inherited completion from the - * body. - */ - private predicate exit(AstNode pred, Completion c, boolean inherited) { - exists(Trees::BodyStmtTree block, EnsureSplitType type | - this.exit0(pred, block, super.getNestLevel(), c) and - type = super.getType() - | - if last(block.getEnsure(), pred, c) - then - // `ensure` block can itself exit with completion `c`: either `c` must - // match this split, `c` must be an abnormal completion, or this split - // does not require another completion to be recovered - inherited = false and - ( - type = c.getAMatchingSuccessorType() - or - not c instanceof NormalCompletion - or - type instanceof DirectSuccessor - ) - else ( - // `ensure` block can exit with inherited completion `c`, which must - // match this split - inherited = true and - type = c.getAMatchingSuccessorType() and - not type instanceof DirectSuccessor - ) - ) - or - // If this split is normal, and an outer split can exit based on an inherited - // completion, we need to exit this split as well. For example, in - // - // ```rb - // def m(b1, b2) - // if b1 - // return - // end - // ensure - // begin - // if b2 - // raise "Exception" - // end - // ensure - // puts "inner ensure" - // end - // end - // ``` - // - // if the outer split for `puts "inner ensure"` is `return` and the inner split - // is "normal" (corresponding to `b1 = true` and `b2 = false`), then the inner - // split must be able to exit with a `return` completion. - this.appliesToPredecessor(pred) and - exists(EnsureSplitImpl outer | - outer.(EnsureSplit).getNestLevel() = super.getNestLevel() - 1 and - outer.exit(pred, c, inherited) and - super.getType() instanceof DirectSuccessor and - inherited = true - ) - } - - override predicate hasExit(AstNode pred, AstNode succ, Completion c) { - succ(pred, succ, c) and - ( - this.exit(pred, c, _) - or - this.exit(pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) - ) - } - - override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) { - succExit(scope, last, c) and - ( - this.exit(last, c, _) - or - this.exit(last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) - ) - } - - override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) { - this.appliesToPredecessor(pred) and - succ(pred, succ, c) and - succ = - any(EnsureNode en | - if en.isEntryNode() and en.getBlock() != pred.(EnsureNode).getBlock() - then - // entering a nested `ensure` block - en.getNestLevel() > super.getNestLevel() - else - // staying in the same (possibly nested) `ensure` block as `pred` - en.getNestLevel() >= super.getNestLevel() - ) - } - } + // /** + // * The type of a split `ensure` node. + // * + // * The type represents one of the possible ways of entering an `ensure` + // * block. For example, if a block ends with a `return` statement, then + // * the `ensure` block must end with a `return` as well (provided that + // * the `ensure` block executes normally). + // */ + // class EnsureSplitType extends SuccessorType { + // EnsureSplitType() { not this instanceof ConditionalSuccessor } + // /** Holds if this split type matches entry into an `ensure` block with completion `c`. */ + // predicate isSplitForEntryCompletion(Completion c) { + // if c instanceof NormalCompletion + // then + // // If the entry into the `ensure` block completes with any normal completion, + // // it simply means normal execution after the `ensure` block + // this instanceof DirectSuccessor + // else this = c.getAMatchingSuccessorType() + // } + // } + // /** A node that belongs to an `ensure` block. */ + // private class EnsureNode extends AstNode { + // private Trees::BodyStmtTree block; + // EnsureNode() { this = block.getAnEnsureDescendant() } + // int getNestLevel() { result = block.getNestLevel() } + // /** Holds if this node is the entry node in the `ensure` block it belongs to. */ + // predicate isEntryNode() { first(block.getEnsure(), this) } + // Ast::BodyStmt getBlock() { result = block } + // pragma[noinline] + // predicate isEntered(AstNode pred, int nestLevel, Completion c) { + // this.isEntryNode() and + // nestLevel = this.getNestLevel() and + // succ(pred, this, c) and + // // the entry node may be reachable via a backwards loop edge; in this case + // // the split has already been entered + // not pred = block.getAnEnsureDescendant() + // } + // } + // /** + // * A split for nodes belonging to an `ensure` block, which determines how to + // * continue execution after leaving the `ensure` block. For example, in + // * + // * ```rb + // * begin + // * if x + // * raise "Exception" + // * end + // * ensure + // * puts "Ensure" + // * end + // * ``` + // * + // * all control flow nodes in the `ensure` block have two splits: one representing + // * normal execution of the body (when `x` evaluates to `true`), and one representing + // * exceptional execution of the body (when `x` evaluates to `false`). + // */ + // class EnsureSplit extends Split, TEnsureSplit { + // private EnsureSplitType type; + // private int nestLevel; + // EnsureSplit() { this = TEnsureSplit(type, nestLevel) } + // /** + // * Gets the type of this `ensure` split, that is, how to continue execution after the + // * `ensure` block. + // */ + // EnsureSplitType getType() { result = type } + // /** Gets the nesting level. */ + // int getNestLevel() { result = nestLevel } + // override string toString() { + // if type instanceof DirectSuccessor + // then result = "" + // else + // if nestLevel > 0 + // then result = "ensure(" + nestLevel + "): " + type.toString() + // else result = "ensure: " + type.toString() + // } + // } + // private int getListOrder(EnsureSplitKind kind) { + // result = ConditionalCompletionSplitting::getNextListOrder() + kind.getNestLevel() + // } + // int getNextListOrder() { + // result = max([getListOrder(_) + 1, ConditionalCompletionSplitting::getNextListOrder()]) + // } + // private class EnsureSplitKind extends SplitKind, TEnsureSplitKind { + // private int nestLevel; + // EnsureSplitKind() { this = TEnsureSplitKind(nestLevel) } + // /** Gets the nesting level. */ + // int getNestLevel() { result = nestLevel } + // override int getListOrder() { result = getListOrder(this) } + // override string toString() { result = "ensure (" + nestLevel + ")" } + // } + // private class EnsureSplitImpl extends SplitImpl instanceof EnsureSplit { + // override EnsureSplitKind getKind() { result.getNestLevel() = super.getNestLevel() } + // override predicate hasEntry(AstNode pred, AstNode succ, Completion c) { + // succ.(EnsureNode).isEntered(pred, super.getNestLevel(), c) and + // super.getType().isSplitForEntryCompletion(c) + // } + // override predicate hasEntryScope(CfgScope scope, AstNode first) { none() } + // /** + // * Holds if this split applies to `pred`, where `pred` is a valid predecessor. + // */ + // private predicate appliesToPredecessor(AstNode pred) { + // this.appliesTo(pred) and + // (succ(pred, _, _) or succExit(_, pred, _)) + // } + // pragma[noinline] + // private predicate exit0(AstNode pred, Trees::BodyStmtTree block, int nestLevel, Completion c) { + // this.appliesToPredecessor(pred) and + // nestLevel = block.getNestLevel() and + // block.last(pred, c) + // } + // /** + // * Holds if `pred` may exit this split with completion `c`. The Boolean + // * `inherited` indicates whether `c` is an inherited completion from the + // * body. + // */ + // private predicate exit(AstNode pred, Completion c, boolean inherited) { + // exists(Trees::BodyStmtTree block, EnsureSplitType type | + // this.exit0(pred, block, super.getNestLevel(), c) and + // type = super.getType() + // | + // if last(block.getEnsure(), pred, c) + // then + // // `ensure` block can itself exit with completion `c`: either `c` must + // // match this split, `c` must be an abnormal completion, or this split + // // does not require another completion to be recovered + // inherited = false and + // ( + // type = c.getAMatchingSuccessorType() + // or + // not c instanceof NormalCompletion + // or + // type instanceof DirectSuccessor + // ) + // else ( + // // `ensure` block can exit with inherited completion `c`, which must + // // match this split + // inherited = true and + // type = c.getAMatchingSuccessorType() and + // not type instanceof DirectSuccessor + // ) + // ) + // or + // // If this split is normal, and an outer split can exit based on an inherited + // // completion, we need to exit this split as well. For example, in + // // + // // ```rb + // // def m(b1, b2) + // // if b1 + // // return + // // end + // // ensure + // // begin + // // if b2 + // // raise "Exception" + // // end + // // ensure + // // puts "inner ensure" + // // end + // // end + // // ``` + // // + // // if the outer split for `puts "inner ensure"` is `return` and the inner split + // // is "normal" (corresponding to `b1 = true` and `b2 = false`), then the inner + // // split must be able to exit with a `return` completion. + // this.appliesToPredecessor(pred) and + // exists(EnsureSplitImpl outer | + // outer.(EnsureSplit).getNestLevel() = super.getNestLevel() - 1 and + // outer.exit(pred, c, inherited) and + // super.getType() instanceof DirectSuccessor and + // inherited = true + // ) + // } + // override predicate hasExit(AstNode pred, AstNode succ, Completion c) { + // succ(pred, succ, c) and + // ( + // this.exit(pred, c, _) + // or + // this.exit(pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) + // ) + // } + // override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) { + // succExit(scope, last, c) and + // ( + // this.exit(last, c, _) + // or + // this.exit(last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) + // ) + // } + // override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) { + // this.appliesToPredecessor(pred) and + // succ(pred, succ, c) and + // succ = + // any(EnsureNode en | + // if en.isEntryNode() and en.getBlock() != pred.(EnsureNode).getBlock() + // then + // // entering a nested `ensure` block + // en.getNestLevel() > super.getNestLevel() + // else + // // staying in the same (possibly nested) `ensure` block as `pred` + // en.getNestLevel() >= super.getNestLevel() + // ) + // } + // } }