diff --git a/ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll b/ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll index d69a65243b8..8df84ffd371 100644 --- a/ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll +++ b/ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll @@ -186,12 +186,17 @@ module SuccessorTypes { /** * A conditional control flow successor. Either a Boolean successor (`BooleanSuccessor`), - * or an emptiness successor (`EmptinessSuccessor`). + * an emptiness successor (`EmptinessSuccessor`), or a matching successor + * (`MatchingSuccessor`) */ class ConditionalSuccessor extends SuccessorType { boolean value; - ConditionalSuccessor() { this = TBooleanSuccessor(value) or this = TEmptinessSuccessor(value) } + ConditionalSuccessor() { + this = TBooleanSuccessor(value) or + this = TEmptinessSuccessor(value) or + this = TMatchingSuccessor(value) + } /** Gets the Boolean value of this successor. */ final boolean getValue() { result = value } @@ -248,12 +253,38 @@ module SuccessorTypes { * ``` */ class EmptinessSuccessor extends ConditionalSuccessor, TEmptinessSuccessor { - /** Holds if this is an empty successor. */ - predicate isEmpty() { value = true } + override string toString() { if value = true then result = "empty" else result = "non-empty" } + } - final override string toString() { - if this.isEmpty() then result = "empty" else result = "non-empty" - } + /** + * A matching control flow successor. + * + * For example, this program fragment: + * + * ```rb + * case x + * when 1 then puts "one" + * else puts "not one" + * end + * ``` + * + * has a control flow graph containing matching successors: + * + * ``` + * x + * | + * 1 + * / \ + * / \ + * / \ + * / \ + * match non-match + * | | + * puts "one" puts "not one" + * ``` + */ + class MatchingSuccessor extends ConditionalSuccessor, TMatchingSuccessor { + override string toString() { if value = true then result = "match" else result = "no-match" } } /** diff --git a/ql/src/codeql_ruby/controlflow/internal/Completion.qll b/ql/src/codeql_ruby/controlflow/internal/Completion.qll index 71d09d6e7db..4245bc7990a 100644 --- a/ql/src/codeql_ruby/controlflow/internal/Completion.qll +++ b/ql/src/codeql_ruby/controlflow/internal/Completion.qll @@ -7,13 +7,15 @@ private import codeql_ruby.ast.internal.TreeSitter::Generated private import codeql_ruby.controlflow.ControlFlowGraph private import AstNodes +private import ControlFlowGraphImpl private import NonReturning private import SuccessorTypes private newtype TCompletion = TSimpleCompletion() or - TBooleanCompletion(boolean b) { b = true or b = false } or - TEmptinessCompletion(boolean isEmpty) { isEmpty = true or isEmpty = false } or + TBooleanCompletion(boolean b) { b in [false, true] } or + TEmptinessCompletion(boolean isEmpty) { isEmpty in [false, true] } or + TMatchingCompletion(boolean isMatch) { isMatch in [false, true] } or TReturnCompletion() or TBreakCompletion() or TNextCompletion() or @@ -21,11 +23,35 @@ private newtype TCompletion = TRetryCompletion() or TRaiseCompletion() or // TODO: Add exception type? TExitCompletion() or - TNestedCompletion(Completion inner, Completion outer) { - outer = TSimpleCompletion() and - inner = TBreakCompletion() + TNestedCompletion(Completion inner, Completion outer, int nestLevel) { + inner = TBreakCompletion() and + outer instanceof NonNestedNormalCompletion and + nestLevel = 0 + or + inner instanceof NormalCompletion and + nestedEnsureCompletion(outer, nestLevel) } +pragma[noinline] +private predicate nestedEnsureCompletion(Completion outer, int nestLevel) { + ( + outer = TReturnCompletion() + or + outer = TBreakCompletion() + or + outer = TNextCompletion() + or + outer = TRedoCompletion() + or + outer = TRetryCompletion() + or + outer = TRaiseCompletion() + or + outer = TExitCompletion() + ) and + nestLevel = any(Trees::RescueEnsureBlockTree t).nestLevel() +} + pragma[noinline] private predicate completionIsValidForStmt(AstNode n, Completion c) { n instanceof Break and @@ -57,11 +83,15 @@ abstract class Completion extends TCompletion { this = TBooleanCompletion(_) ) or + mustHaveMatchingCompletion(n) and + this = TMatchingCompletion(_) + or n = any(RescueModifier parent).getBody() and this = TRaiseCompletion() or not n instanceof NonReturningCall and not completionIsValidForStmt(n, _) and not mustHaveBooleanCompletion(n) and + not mustHaveMatchingCompletion(n) and this = TSimpleCompletion() } @@ -143,14 +173,30 @@ private predicate inBooleanContext(AstNode n) { n instanceof Pattern } +/** + * Holds if a normal completion of `n` must be a matching completion. + */ +private predicate mustHaveMatchingCompletion(AstNode n) { + inMatchingContext(n) and + not n instanceof NonReturningCall +} + +/** + * Holds if `n` is used in a matching context. That is, whether or + * not the value of `n` matches, determines the successor. + */ +private predicate inMatchingContext(AstNode n) { n = any(Rescue r).getExceptions().getChild(_) } + /** * A completion that represents normal evaluation of a statement or an * expression. */ abstract class NormalCompletion extends Completion { } +abstract private class NonNestedNormalCompletion extends NormalCompletion { } + /** A simple (normal) completion. */ -class SimpleCompletion extends NormalCompletion, TSimpleCompletion { +class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion { override NormalSuccessor getAMatchingSuccessorType() { any() } override string toString() { result = "simple" } @@ -158,10 +204,13 @@ class SimpleCompletion extends NormalCompletion, TSimpleCompletion { /** * A completion that represents evaluation of an expression, whose value determines - * the successor. Either a Boolean completion (`BooleanCompletion`) - * or an emptiness completion (`EmptinessCompletion`). + * the successor. Either a Boolean completion (`BooleanCompletion`), an emptiness + * completion (`EmptinessCompletion`), or a matching completion (`MatchingCompletion`). */ -abstract class ConditionalCompletion extends NormalCompletion { } +abstract class ConditionalCompletion extends NonNestedNormalCompletion { + /** Gets the Boolean value of this conditional completion. */ + abstract boolean getValue(); +} /** * A completion that represents evaluation of an expression @@ -172,8 +221,7 @@ class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion { BooleanCompletion() { this = TBooleanCompletion(value) } - /** Gets the Boolean value of this completion. */ - boolean getValue() { result = value } + override boolean getValue() { result = value } /** Gets the dual Boolean completion. */ BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) } @@ -198,84 +246,157 @@ class FalseCompletion extends BooleanCompletion { * a test in a `for in` statement. */ class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion { - /** Holds if the emptiness test evaluates to `true`. */ - predicate isEmpty() { this = TEmptinessCompletion(true) } + private boolean value; - override EmptinessSuccessor getAMatchingSuccessorType() { - if isEmpty() then result.getValue() = true else result.getValue() = false - } + EmptinessCompletion() { this = TEmptinessCompletion(value) } - override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" } + override boolean getValue() { result = value } + + override EmptinessSuccessor getAMatchingSuccessorType() { result.getValue() = value } + + override string toString() { if value = true then result = "empty" else result = "non-empty" } +} + +/** + * A completion that represents evaluation of a matching test, for example + * a test in a `rescue` statement. + */ +class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion { + private boolean value; + + MatchingCompletion() { this = TMatchingCompletion(value) } + + override boolean getValue() { result = value } + + override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value } + + override string toString() { if value = true then result = "match" else result = "no-match" } } /** * A completion that represents evaluation of a statement or an * expression resulting in a return. */ -class ReturnCompletion extends Completion, TReturnCompletion { +class ReturnCompletion extends Completion { + ReturnCompletion() { + this = TReturnCompletion() or + this = TNestedCompletion(_, TReturnCompletion(), _) + } + override ReturnSuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "return" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TReturnCompletion() and result = "return" + } } /** * A completion that represents evaluation of a statement or an * expression resulting in a break from a loop. */ -class BreakCompletion extends Completion, TBreakCompletion { +class BreakCompletion extends Completion { + BreakCompletion() { + this = TBreakCompletion() or + this = TNestedCompletion(_, TBreakCompletion(), _) + } + override BreakSuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "break" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TBreakCompletion() and result = "break" + } } /** * A completion that represents evaluation of a statement or an * expression resulting in a continuation of a loop. */ -class NextCompletion extends Completion, TNextCompletion { +class NextCompletion extends Completion { + NextCompletion() { + this = TNextCompletion() or + this = TNestedCompletion(_, TNextCompletion(), _) + } + override NextSuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "next" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TNextCompletion() and result = "next" + } } /** * A completion that represents evaluation of a statement or an * expression resulting in a redo of a loop iteration. */ -class RedoCompletion extends Completion, TRedoCompletion { +class RedoCompletion extends Completion { + RedoCompletion() { + this = TRedoCompletion() or + this = TNestedCompletion(_, TRedoCompletion(), _) + } + override RedoSuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "redo" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TRedoCompletion() and result = "redo" + } } /** * A completion that represents evaluation of a statement or an * expression resulting in a retry. */ -class RetryCompletion extends Completion, TRetryCompletion { +class RetryCompletion extends Completion { + RetryCompletion() { + this = TRetryCompletion() or + this = TNestedCompletion(_, TRetryCompletion(), _) + } + override RetrySuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "retry" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TRetryCompletion() and result = "retry" + } } /** * A completion that represents evaluation of a statement or an * expression resulting in a thrown exception. */ -class RaiseCompletion extends Completion, TRaiseCompletion { +class RaiseCompletion extends Completion { + RaiseCompletion() { + this = TRaiseCompletion() or + this = TNestedCompletion(_, TRaiseCompletion(), _) + } + override RaiseSuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "raise" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TRaiseCompletion() and result = "raise" + } } /** * A completion that represents evaluation of a statement or an * expression resulting in an abort/exit. */ -class ExitCompletion extends Completion, TExitCompletion { +class ExitCompletion extends Completion { + ExitCompletion() { + this = TExitCompletion() or + this = TNestedCompletion(_, TExitCompletion(), _) + } + override ExitSuccessor getAMatchingSuccessorType() { any() } - override string toString() { result = "exit" } + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TExitCompletion() and result = "exit" + } } /** @@ -296,25 +417,60 @@ class ExitCompletion extends Completion, TExitCompletion { * the `while` loop can have a nested completion where the inner completion * is a `break` and the outer completion is a simple successor. */ -class NestedCompletion extends Completion, TNestedCompletion { +abstract class NestedCompletion extends Completion, TNestedCompletion { Completion inner; Completion outer; + int nestLevel; - NestedCompletion() { this = TNestedCompletion(inner, outer) } + NestedCompletion() { this = TNestedCompletion(inner, outer, nestLevel) } - override Completion getInnerCompletion() { result = inner } + /** Gets a completion that is compatible with the inner completion. */ + abstract Completion getAnInnerCompatibleCompletion(); + + /** Gets the level of this nested completion. */ + final int getNestLevel() { result = nestLevel } + + override string toString() { result = outer + " [" + inner + "] (" + nestLevel + ")" } +} + +class NestedBreakCompletion extends NormalCompletion, NestedCompletion { + NestedBreakCompletion() { + inner = TBreakCompletion() and + outer instanceof NonNestedNormalCompletion + } + + override BreakCompletion getInnerCompletion() { result = inner } + + override NonNestedNormalCompletion getOuterCompletion() { result = outer } + + override Completion getAnInnerCompatibleCompletion() { + result = inner and + outer = TSimpleCompletion() + or + result = TNestedCompletion(outer, inner, _) + } + + override SuccessorType getAMatchingSuccessorType() { + outer instanceof SimpleCompletion and + result instanceof BreakSuccessor + or + result = outer.(ConditionalCompletion).getAMatchingSuccessorType() + } +} + +class NestedEnsureCompletion extends NestedCompletion { + NestedEnsureCompletion() { + inner instanceof NormalCompletion and + nestedEnsureCompletion(outer, nestLevel) + } + + override NormalCompletion getInnerCompletion() { result = inner } override Completion getOuterCompletion() { result = outer } - override SuccessorType getAMatchingSuccessorType() { - inner = TBreakCompletion() and - outer = TSimpleCompletion() and - result instanceof BreakSuccessor + override Completion getAnInnerCompatibleCompletion() { + result.getOuterCompletion() = this.getInnerCompletion() } - override string toString() { result = outer + " [" + inner + "]" } -} - -private class NestedNormalCompletion extends NormalCompletion, NestedCompletion { - NestedNormalCompletion() { outer instanceof NormalCompletion } + override SuccessorType getAMatchingSuccessorType() { none() } } diff --git a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll index 6d9d4f7b6a3..d0df3768d98 100644 --- a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll +++ b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll @@ -38,6 +38,14 @@ private import Completion private import SuccessorTypes private import Splitting +private AstNode parent(AstNode n) { + result.getAFieldOrChild() = n and + not n instanceof CfgScope +} + +/** Gets the CFG scope of node `n`. */ +CfgScope getScope(AstNode n) { result = parent+(n) } + abstract private class ControlFlowTree extends AstNode { /** * Holds if `first` is the first element executed within this AST node. @@ -227,7 +235,7 @@ abstract private class LeafTree extends PreOrderTree, PostOrderTree { } /** Defines the CFG by dispatch on the various AST types. */ -private module Trees { +module Trees { private class AliasTree extends StandardPreOrderTree, Alias { final override AstNode getChildNode(int i) { i = 0 and result = this.getName() @@ -262,12 +270,9 @@ private module Trees { final override Interpolation getChildNode(int i) { result = this.getChild(i) } } - private class BeginTree extends StandardPreOrderTree, Begin { - final override AstNode getChildNode(int i) { - result = this.getChild(i) and - not result instanceof Rescue and - not result instanceof Else and - not result instanceof Ensure + private class BeginTree extends RescueEnsureBlockTree, Begin { + final override AstNode getChildNode(int i, boolean rescuable) { + result = this.getChild(i) and rescuable = true } override predicate isHidden() { any() } @@ -370,11 +375,14 @@ private module Trees { private class CharacterTree extends LeafTree, Character { } - private class ClassTree extends StandardPreOrderTree, Class { - final override AstNode getChildNode(int i) { - result = this.getName() and i = 0 - or - result = this.getChild(i - 1) + private class ClassTree extends RescueEnsureBlockTree, Class { + final override AstNode getChildNode(int i, boolean rescuable) { + rescuable = true and + ( + result = this.getName() and i = 0 + or + result = this.getChild(i - 1) + ) } } @@ -399,11 +407,11 @@ private module Trees { override predicate isHidden() { any() } } - private class DoBlockTree extends StandardPreOrderTree, DoBlock { - final override AstNode getChildNode(int i) { - result = this.getParameters() and i = 0 + private class DoBlockTree extends RescueEnsureBlockTree, DoBlock { + final override AstNode getChildNode(int i, boolean rescuable) { + result = this.getParameters() and i = 0 and rescuable = false or - result = this.getChild(i - 1) + result = this.getChild(i - 1) and rescuable = true } override predicate isHidden() { any() } @@ -431,8 +439,6 @@ private module Trees { private class EnsureTree extends StandardPreOrderTree, Ensure { final override AstNode getChildNode(int i) { result = this.getChild(i) } - - override predicate isHidden() { any() } } private class EscapeSequenceTree extends LeafTree, EscapeSequence { } @@ -443,8 +449,58 @@ private module Trees { override predicate isHidden() { any() } } - private class ExceptionsTree extends StandardPostOrderTree, Exceptions { - final override AstNode getChildNode(int i) { result = this.getChild(i) } + private class ExceptionsTree extends PreOrderTree, Exceptions { + final override predicate propagatesAbnormal(AstNode child) { none() } + + /** Holds if this exception filter belongs to a final `rescue` block. */ + pragma[noinline] + private predicate inLastRescue() { + exists(RescueEnsureBlockTree block, Rescue rescue, int i | + rescue = block.getRescue(i) and + this = rescue.getExceptions() and + not exists(block.getRescue(i + 1)) + ) + } + + final override predicate last(AstNode last, Completion c) { + last(this.getChild(_), last, c) and + c.(MatchingCompletion).getValue() = true + or + exists(int lst, Completion c0 | + last(this.getChild(lst), last, c0) and + not exists(this.getChild(lst + 1)) + | + // The last exception filter in a last `rescue` block propagates + // the exception when it doesn't match + this.inLastRescue() and + c = + any(NestedEnsureCompletion nec | + nec.getOuterCompletion() instanceof RaiseCompletion and + nec.getInnerCompletion() = c0 and + c0.(MatchingCompletion).getValue() = false and + nec.getNestLevel() = 0 + ) + or + c = c0 and + ( + not this.inLastRescue() + or + not c0.(MatchingCompletion).getValue() = false + ) + ) + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + pred = this and + first(this.getChild(0), succ) and + c instanceof SimpleCompletion + or + exists(int i | + last(this.getChild(i), pred, c) and + c.(MatchingCompletion).getValue() = false and + first(this.getChild(i + 1), succ) + ) + } override predicate isHidden() { any() } } @@ -493,18 +549,14 @@ private module Trees { final override predicate last(AstNode last, Completion c) { last = this and - c.(EmptinessCompletion).isEmpty() + c.(EmptinessCompletion).getValue() = true or last(this.getBody(), last, c) and not c.continuesLoop() and not c instanceof BreakCompletion and not c instanceof RedoCompletion or - c = - any(NestedCompletion nc | - last(this.getBody(), last, nc.getInnerCompletion().(BreakCompletion)) and - nc.getOuterCompletion() instanceof SimpleCompletion - ) + last(this.getBody(), last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion()) } /** @@ -521,8 +573,7 @@ private module Trees { or pred = this and first(this.getPattern(0), succ) and - c instanceof EmptinessCompletion and - not c.(EmptinessCompletion).isEmpty() + c.(EmptinessCompletion).getValue() = false or exists(int i, ControlFlowTree next | last(this.getPattern(i), pred, c) and @@ -719,11 +770,11 @@ private module Trees { } } - private class MethodTree extends StandardPreOrderTree, Method { - final override AstNode getChildNode(int i) { - result = this.getParameters() and i = 0 + private class MethodTree extends RescueEnsureBlockTree, Method { + final override AstNode getChildNode(int i, boolean rescuable) { + result = this.getParameters() and i = 0 and rescuable = false or - result = this.getChild(i - 1) + result = this.getChild(i - 1) and rescuable = true } override predicate isHidden() { any() } @@ -744,11 +795,14 @@ private module Trees { override predicate isHidden() { any() } } - private class ModuleTree extends StandardPreOrderTree, Module { - final override AstNode getChildNode(int i) { - result = this.getName() and i = 0 - or - result = this.getChild(i - 1) + private class ModuleTree extends RescueEnsureBlockTree, Module { + final override AstNode getChildNode(int i, boolean rescuable) { + rescuable = true and + ( + result = this.getName() and i = 0 + or + result = this.getChild(i - 1) + ) } } @@ -809,13 +863,272 @@ private module Trees { final override Interpolation getChildNode(int i) { result = this.getChild(i) } } - private class RescueTree extends StandardPreOrderTree, Rescue { - final override AstNode getChildNode(int i) { - result = this.getExceptions() and i = 0 + private class RescueTree extends PreOrderTree, Rescue { + final override predicate propagatesAbnormal(AstNode child) { child = this.getExceptions() } + + final override predicate last(AstNode last, Completion c) { + last(this.getExceptions(), last, c) and + c.(MatchingCompletion).getValue() = false or - result = this.getVariable() and i = 1 + last(this.getBody(), last, c) or - result = this.getBody() and i = 2 + not exists(this.getExceptions()) and + not exists(this.getBody()) and + ( + last(this.getVariable(), last, c) + or + not exists(this.getVariable()) and + last = this and + c.isValidFor(this) + ) + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + exists(AstNode next | + pred = this and + first(next, succ) and + c instanceof SimpleCompletion + | + next = this.getExceptions() + or + not exists(this.getExceptions()) and + ( + next = this.getVariable() + or + not exists(this.getVariable()) and + next = this.getBody() + ) + ) + or + exists(AstNode next | + last(this.getExceptions(), pred, c) and + first(next, succ) and + c.(MatchingCompletion).getValue() = true + | + next = this.getVariable() + or + not exists(this.getVariable()) and + next = this.getBody() + ) + or + last(this.getVariable(), pred, c) and + first(this.getBody(), succ) and + c instanceof NormalCompletion + } + } + + /** Gets a child of `n` that is in CFG scope `scope`. */ + pragma[noinline] + private AstNode getAChildInScope(AstNode n, CfgScope scope) { + result.getParent() = n and + scope = getScope(result) + } + + /** A block that may contain `rescue`/`ensure`. */ + abstract class RescueEnsureBlockTree extends PreOrderTree { + /** + * Gets the `i`th child of this block. `rescuable` indicates whether exceptional + * execution of the child can be caught by `rescue`/`ensure`. + */ + abstract AstNode getChildNode(int i, boolean rescuable); + + /** Gets the `i`th child in the body of this block. */ + final private AstNode getBodyChild(int i, boolean rescuable) { + result = this.getChildNode(_, rescuable) and + result = + rank[i + 1](AstNode child, int j | + child = this.getChildNode(j, _) and + not result instanceof Rescue and + not result instanceof Ensure and + not result instanceof Else and + not child instanceof CfgScope + | + child order by j + ) + } + + /** Gets the `i`th `rescue` block in this block. */ + final Rescue getRescue(int i) { + result = rank[i + 1](Rescue s | s = this.getAFieldOrChild() | s order by s.getParentIndex()) + } + + /** Gets the `else` block in this block, if any. */ + final private Else getElse() { result = unique(Else s | s = this.getAFieldOrChild()) } + + /** Gets the `ensure` block in this block, if any. */ + final Ensure getEnsure() { result = unique(Ensure s | s = this.getAFieldOrChild()) } + + final private predicate hasEnsure() { exists(this.getEnsure()) } + + final override predicate propagatesAbnormal(AstNode child) { child = this.getEnsure() } + + /** + * Gets a descendant that belongs to the `ensure` block of this block, if any. + * Nested `ensure` blocks are not included. + */ + AstNode getAnEnsureDescendant() { + result = this.getEnsure() + or + exists(AstNode mid | + mid = this.getAnEnsureDescendant() and + result = getAChildInScope(mid, getScope(mid)) and + not exists(RescueEnsureBlockTree nestedBlock | + result = nestedBlock.getEnsure() and + nestedBlock != this + ) + ) + } + + /** + * Holds if `innerBlock` has an `ensure` block and is immediately nested inside the + * `ensure` block of this block. + */ + private predicate nestedEnsure(RescueEnsureBlockTree innerBlock) { + exists(Ensure innerEnsure | + innerEnsure = getAChildInScope(this.getAnEnsureDescendant(), getScope(this)) and + innerEnsure = innerBlock.getEnsure() + ) + } + + /** + * Gets the `ensure`-nesting level of this block. That is, the number of `ensure` + * blocks that this block is nested under. + */ + int nestLevel() { result = count(RescueEnsureBlockTree outer | outer.nestedEnsure+(this)) } + + /** + * Holds if `last` is a last element in the body of this block. `ensurable` + * indicates whether `last` may be a predecessor of an `ensure` block. + */ + pragma[nomagic] + private predicate lastBody(AstNode last, Completion c, boolean ensurable) { + exists(boolean rescuable | + if c instanceof RaiseCompletion then ensurable = rescuable else ensurable = true + | + last(this.getBodyChild(_, rescuable), last, c) and + not c instanceof NormalCompletion + or + exists(int lst | + last(this.getBodyChild(lst, rescuable), last, c) and + not exists(this.getBodyChild(lst + 1, _)) + ) + ) + } + + /** + * Gets a last element from this block that may finish with completion `c`, such + * that control may be transferred to the `ensure` block (if it exists), but only + * if `ensurable = true`. + */ + pragma[nomagic] + private AstNode getAnEnsurePredecessor(Completion c, boolean ensurable) { + exists(boolean ensurable0 | + // Exit completions ignore the `ensure` block (TODO: CHECK THIS) + if c instanceof ExitCompletion then ensurable = false else ensurable = ensurable0 + | + this.lastBody(result, c, ensurable0) and + ( + // Any non-throw completion will always continue directly to the `ensure` block, + // unless there is an `else` block + not c instanceof RaiseCompletion and + not exists(this.getElse()) + or + // Any completion will continue to the `ensure` block when there are no `rescue` + // blocks + not exists(this.getRescue(_)) + ) + or + // Last element from any of the `rescue` blocks continues to the `ensure` block + last(this.getRescue(_).getBody(), result, c) and + ensurable0 = true + or + // Last element of last `rescue` block continues to the `ensure` block + exists(int lst | + last(this.getRescue(lst), result, c) and + not exists(this.getRescue(lst + 1)) and + ensurable0 = true + ) + or + // Last element of `else` block continues to the `ensure` block + last(this.getElse(), result, c) and + ensurable0 = true + ) + } + + pragma[nomagic] + private predicate lastEnsure0(AstNode last, Completion c) { last(this.getEnsure(), last, c) } + + pragma[nomagic] + private predicate lastEnsure( + AstNode last, NormalCompletion ensure, Completion outer, int nestLevel + ) { + this.lastEnsure0(last, ensure) and + exists( + this.getAnEnsurePredecessor(any(Completion c0 | outer = c0.getOuterCompletion()), true) + ) and + nestLevel = this.nestLevel() + } + + override predicate last(AstNode last, Completion c) { + exists(boolean ensurable | last = this.getAnEnsurePredecessor(c, ensurable) | + not this.hasEnsure() + or + ensurable = false + ) + or + // If the body completes normally, take the completion from the `ensure` block + this.lastEnsure(last, c, any(NormalCompletion nc), _) + or + // If the `ensure` block completes normally, it inherits any non-normal + // completion from the body + c = + any(NestedEnsureCompletion nec | + this + .lastEnsure(last, nec.getAnInnerCompatibleCompletion(), nec.getOuterCompletion(), + nec.getNestLevel()) + ) + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + pred = this and + first(this.getBodyChild(0, _), succ) and + c instanceof SimpleCompletion + or + // Normal left-to-right evaluation in the body + exists(int i | + last(this.getBodyChild(i, _), pred, c) and + first(this.getBodyChild(i + 1, _), succ) and + c instanceof NormalCompletion + ) + or + // Exceptional flow from body to first `rescue` + this.lastBody(pred, c, true) and + first(this.getRescue(0), succ) and + c instanceof RaiseCompletion + or + exists(Rescue rescue, int i | rescue = this.getRescue(i) | + pred = rescue and + last(this.getRescue(i), rescue, c) and + ( + // Flow from one `rescue` clause to the next when there is no match + first(this.getRescue(i + 1), succ) and + c.(MatchingCompletion).getValue() = false + or + // Flow from last `rescue` clause to `ensure` block + not exists(this.getRescue(i + 1)) and + first(this.getEnsure(), succ) and + c.getInnerCompletion().(MatchingCompletion).getValue() = false + ) + ) + or + // Flow from body to `else` block when no exception + this.lastBody(pred, c, _) and + first(this.getElse(), succ) and + c instanceof NormalCompletion + or + // Flow into `ensure` block + pred = getAnEnsurePredecessor(c, true) and + first(this.getEnsure(), succ) } } @@ -870,23 +1183,31 @@ private module Trees { private class SetterTree extends LeafTree, Setter { } - private class SingletonClassTree extends StandardPreOrderTree, SingletonClass { - final override AstNode getChildNode(int i) { - result = this.getValue() and i = 0 - or - result = this.getChild(i - 1) + private class SingletonClassTree extends RescueEnsureBlockTree, SingletonClass { + final override AstNode getChildNode(int i, boolean rescuable) { + rescuable = true and + ( + result = this.getValue() and i = 0 + or + result = this.getChild(i - 1) + ) } override predicate isHidden() { any() } } - private class SingletonMethodTree extends StandardPreOrderTree, SingletonMethod { - final override AstNode getChildNode(int i) { - result = this.getObject() and i = 0 + private class SingletonMethodTree extends RescueEnsureBlockTree, SingletonMethod { + final override AstNode getChildNode(int i, boolean rescuable) { + result = this.getObject() and + i = 0 and + rescuable = true // TODO: CHECK THIS or - result = this.getParameters() and i = 1 + result = this.getParameters() and + i = 1 and + rescuable = false or - result = this.getChild(i - 2) + result = this.getChild(i - 2) and + rescuable = true } override predicate isHidden() { any() } @@ -990,11 +1311,7 @@ private module Trees { not c instanceof BreakCompletion and not c instanceof RedoCompletion or - c = - any(NestedCompletion nc | - last(this.getBodyNode(), last, nc.getInnerCompletion().(BreakCompletion)) and - nc.getOuterCompletion() instanceof SimpleCompletion - ) + last(this.getBodyNode(), last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion()) } final override predicate succ(AstNode pred, AstNode succ, Completion c) { @@ -1048,8 +1365,9 @@ private module Cached { cached newtype TSuccessorType = TSuccessorSuccessor() or - TBooleanSuccessor(boolean b) { b = true or b = false } or - TEmptinessSuccessor(boolean isEmpty) { isEmpty = true or isEmpty = false } or + TBooleanSuccessor(boolean b) { b in [false, true] } or + TEmptinessSuccessor(boolean isEmpty) { isEmpty in [false, true] } or + TMatchingSuccessor(boolean isMatch) { isMatch in [false, true] } or TReturnSuccessor() or TBreakSuccessor() or TNextSuccessor() or diff --git a/ql/src/codeql_ruby/controlflow/internal/NonReturning.qll b/ql/src/codeql_ruby/controlflow/internal/NonReturning.qll index 29fcfb7fd6a..dc065750c5f 100644 --- a/ql/src/codeql_ruby/controlflow/internal/NonReturning.qll +++ b/ql/src/codeql_ruby/controlflow/internal/NonReturning.qll @@ -12,7 +12,7 @@ abstract class NonReturningCall extends AstNode { private class RaiseCall extends NonReturningCall, MethodCall { RaiseCall() { this.getMethod().toString() = "raise" } - override RaiseCompletion getACompletion() { any() } + override RaiseCompletion getACompletion() { not result instanceof NestedCompletion } } private class ExitCall extends NonReturningCall, MethodCall { diff --git a/ql/src/codeql_ruby/controlflow/internal/Splitting.qll b/ql/src/codeql_ruby/controlflow/internal/Splitting.qll index 6f8498b3f24..0e525beef9f 100644 --- a/ql/src/codeql_ruby/controlflow/internal/Splitting.qll +++ b/ql/src/codeql_ruby/controlflow/internal/Splitting.qll @@ -15,10 +15,16 @@ private int maxSplits() { result = 5 } cached private module Cached { cached - newtype TSplitKind = TConditionalCompletionSplitKind() + newtype TSplitKind = + TConditionalCompletionSplitKind() or + TEnsureSplitKind(int nestLevel) { nestLevel = any(Trees::RescueEnsureBlockTree t).nestLevel() } cached - newtype TSplit = TConditionalCompletionSplit(BooleanCompletion c) + newtype TSplit = + TConditionalCompletionSplit(ConditionalCompletion c) or + TEnsureSplit(EnsureSplitting::EnsureSplitType type, int nestLevel) { + nestLevel = any(Trees::RescueEnsureBlockTree t).nestLevel() + } cached newtype TSplits = @@ -60,13 +66,6 @@ class Split extends TSplit { string toString() { none() } } -private AstNode parent(AstNode n) { - result.getAFieldOrChild() = n and - not n instanceof CfgScope -} - -private CfgScope getScope(AstNode n) { result = parent+(n) } - /** * Holds if split kinds `sk1` and `sk2` may overlap. That is, they may apply * to at least one common AST node inside `scope`. @@ -195,7 +194,7 @@ private module ConditionalCompletionSplitting { * restrict the edges out of `x < 2 and x > 0` accordingly. */ class ConditionalCompletionSplit extends Split, TConditionalCompletionSplit { - BooleanCompletion completion; + ConditionalCompletion completion; ConditionalCompletionSplit() { this = TConditionalCompletionSplit(completion) } @@ -238,19 +237,258 @@ private module ConditionalCompletionSplitting { override predicate hasExit(AstNode pred, AstNode succ, Completion c) { this.appliesTo(pred) and succ(pred, succ, c) and - if c instanceof BooleanCompletion then completion = c else any() + if c instanceof ConditionalCompletion then completion = c else any() } override predicate hasExitScope(AstNode last, CfgScope scope, Completion c) { this.appliesTo(last) and succExit(last, scope, c) and - if c instanceof BooleanCompletion then completion = c else any() + if c instanceof ConditionalCompletion then completion = c else any() } override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) { none() } } } +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 NormalSuccessor + else this = c.getAMatchingSuccessorType() + } + } + + /** A node that belongs to an `ensure` block. */ + private class EnsureNode extends AstNode { + private Trees::RescueEnsureBlockTree block; + + EnsureNode() { this = block.getAnEnsureDescendant() } + + /** Gets the immediate block that this node belongs to. */ + Trees::RescueEnsureBlockTree getBlock() { result = block } + + /** Holds if this node is the entry node in the `ensure` block it belongs to. */ + predicate isEntryNode() { first(block.getEnsure(), this) } + } + + /** A node that does not belong to an `ensure` block. */ + private class NonEnsureNode extends EnsureNode { + NonEnsureNode() { not this = any(Trees::RescueEnsureBlockTree t).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 NormalSuccessor + 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 + ")" } + } + + pragma[noinline] + private predicate hasEntry0(AstNode pred, EnsureNode succ, int nestLevel, Completion c) { + succ.isEntryNode() and + nestLevel = succ.getBlock().nestLevel() and + succ(pred, succ, c) + } + + private class EnsureSplitImpl extends SplitImpl, EnsureSplit { + override EnsureSplitKind getKind() { result.getNestLevel() = this.getNestLevel() } + + override predicate hasEntry(AstNode pred, AstNode succ, Completion c) { + hasEntry0(pred, succ, this.getNestLevel(), c) and + this.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::RescueEnsureBlockTree block, int nestLevel, Completion c + ) { + this.appliesToPredecessor(pred) and + nestLevel = block.nestLevel() and + last(block, 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( + Trees::RescueEnsureBlockTree block, AstNode pred, Completion c, boolean inherited + ) { + exists(EnsureSplitType type | + exit0(pred, block, this.getNestLevel(), c) and + type = this.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 NormalSuccessor + ) + else ( + // `ensure` block can exit with inherited completion `c`, which must + // match this split + inherited = true and + type = c.getAMatchingSuccessorType() and + not type instanceof NormalSuccessor + ) + ) + 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.getNestLevel() = this.getNestLevel() - 1 and + outer.exit(_, pred, c, inherited) and + this.getType() instanceof NormalSuccessor and + inherited = true + ) + } + + override predicate hasExit(AstNode pred, AstNode succ, Completion c) { + succ(pred, succ, c) and + ( + exit(_, pred, c, _) + or + exit(_, pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) + ) + } + + override predicate hasExitScope(AstNode last, CfgScope scope, Completion c) { + succExit(last, scope, c) and + ( + exit(_, last, c, _) + or + 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() + then + // entering a nested `ensure` block + en.getBlock().nestLevel() > this.getNestLevel() + else + // staying in the same (possibly nested) `ensure` block as `pred` + en.getBlock().nestLevel() >= this.getNestLevel() + ) + } + } +} + /** * A set of control flow node splits. The set is represented by a list of splits, * ordered by ascending rank. diff --git a/ql/test/library-tests/controlflow/graph/Cfg.expected b/ql/test/library-tests/controlflow/graph/Cfg.expected index 1ff670286ec..2d1ad8f449c 100644 --- a/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -11,6 +11,7 @@ nodes | break_ensure.rb:3:8:3:12 | Binary | | break_ensure.rb:3:12:3:12 | 0 | | break_ensure.rb:4:7:4:11 | Break | +| break_ensure.rb:7:1:10:5 | Ensure | | break_ensure.rb:8:3:10:5 | If | | break_ensure.rb:8:6:8:13 | elements | | break_ensure.rb:8:6:8:18 | Call | @@ -30,6 +31,22 @@ nodes | break_ensure.rb:16:10:16:14 | Binary | | break_ensure.rb:16:14:16:14 | 0 | | break_ensure.rb:17:9:17:13 | Break | +| break_ensure.rb:19:5:22:9 | Ensure | +| break_ensure.rb:19:5:22:9 | [ensure: break] Ensure | +| break_ensure.rb:20:7:22:9 | If | +| break_ensure.rb:20:7:22:9 | [ensure: break] If | +| break_ensure.rb:20:10:20:17 | [ensure: break] elements | +| break_ensure.rb:20:10:20:17 | elements | +| break_ensure.rb:20:10:20:22 | Call | +| break_ensure.rb:20:10:20:22 | [ensure: break] Call | +| break_ensure.rb:20:19:20:22 | [ensure: break] nil? | +| break_ensure.rb:20:19:20:22 | nil? | +| break_ensure.rb:21:9:21:12 | [ensure: break] puts | +| break_ensure.rb:21:9:21:12 | puts | +| break_ensure.rb:21:9:21:27 | MethodCall | +| break_ensure.rb:21:9:21:27 | [ensure: break] MethodCall | +| break_ensure.rb:21:14:21:27 | String | +| break_ensure.rb:21:14:21:27 | [ensure: break] String | | break_ensure.rb:27:1:42:3 | enter m3 | | break_ensure.rb:27:1:42:3 | exit m3 | | break_ensure.rb:27:1:42:3 | exit m3 (normal) | @@ -39,6 +56,24 @@ nodes | break_ensure.rb:29:8:29:20 | Call | | break_ensure.rb:29:17:29:20 | nil? | | break_ensure.rb:30:7:30:12 | Return | +| break_ensure.rb:32:3:39:7 | Ensure | +| break_ensure.rb:32:3:39:7 | [ensure: return] Ensure | +| break_ensure.rb:33:5:39:7 | For | +| break_ensure.rb:33:5:39:7 | [ensure: return] For | +| break_ensure.rb:33:9:33:15 | [ensure: return] element | +| break_ensure.rb:33:9:33:15 | element | +| break_ensure.rb:33:20:33:27 | [ensure: return] elements | +| break_ensure.rb:33:20:33:27 | elements | +| break_ensure.rb:35:9:37:11 | If | +| break_ensure.rb:35:9:37:11 | [ensure: return] If | +| break_ensure.rb:35:12:35:12 | [ensure: return] x | +| break_ensure.rb:35:12:35:12 | x | +| break_ensure.rb:35:12:35:16 | Binary | +| break_ensure.rb:35:12:35:16 | [ensure: return] Binary | +| break_ensure.rb:35:16:35:16 | 0 | +| break_ensure.rb:35:16:35:16 | [ensure: return] 0 | +| break_ensure.rb:36:11:36:15 | Break | +| break_ensure.rb:36:11:36:15 | [ensure: return] Break | | break_ensure.rb:41:3:41:6 | puts | | break_ensure.rb:41:3:41:13 | MethodCall | | break_ensure.rb:41:8:41:13 | String | @@ -263,6 +298,13 @@ nodes | cfg.rb:75:35:75:36 | 10 | | cfg.rb:75:43:75:43 | x | | cfg.rb:78:3:78:3 | ; | +| cfg.rb:83:3:83:6 | puts | +| cfg.rb:83:3:83:11 | MethodCall | +| cfg.rb:83:8:83:11 | String | +| cfg.rb:84:1:85:12 | Ensure | +| cfg.rb:85:3:85:6 | puts | +| cfg.rb:85:3:85:12 | MethodCall | +| cfg.rb:85:8:85:12 | String | | cfg.rb:88:1:88:6 | escape | | cfg.rb:88:1:88:23 | Assignment | | cfg.rb:88:10:88:23 | String | @@ -782,12 +824,16 @@ nodes | raise.rb:17:7:17:11 | raise | | raise.rb:17:7:17:22 | MethodCall | | raise.rb:17:13:17:22 | ExceptionA | +| raise.rb:19:3:20:18 | Rescue | +| raise.rb:19:10:19:19 | ExceptionA | +| raise.rb:20:5:20:8 | puts | +| raise.rb:20:5:20:18 | MethodCall | +| raise.rb:20:10:20:18 | String | | raise.rb:22:3:22:6 | puts | | raise.rb:22:3:22:15 | MethodCall | | raise.rb:22:8:22:15 | String | | raise.rb:25:1:34:3 | enter m3 | | raise.rb:25:1:34:3 | exit m3 | -| raise.rb:25:1:34:3 | exit m3 (abnormal) | | raise.rb:25:1:34:3 | exit m3 (normal) | | raise.rb:25:8:25:8 | b | | raise.rb:27:5:29:7 | If | @@ -795,12 +841,15 @@ nodes | raise.rb:28:7:28:11 | raise | | raise.rb:28:7:28:22 | MethodCall | | raise.rb:28:13:28:22 | ExceptionA | +| raise.rb:30:3:31:18 | Rescue | +| raise.rb:31:5:31:8 | puts | +| raise.rb:31:5:31:18 | MethodCall | +| raise.rb:31:10:31:18 | String | | raise.rb:33:3:33:6 | puts | | raise.rb:33:3:33:15 | MethodCall | | raise.rb:33:8:33:15 | String | | raise.rb:36:1:45:3 | enter m4 | | raise.rb:36:1:45:3 | exit m4 | -| raise.rb:36:1:45:3 | exit m4 (abnormal) | | raise.rb:36:1:45:3 | exit m4 (normal) | | raise.rb:36:8:36:8 | b | | raise.rb:38:5:40:7 | If | @@ -808,12 +857,16 @@ nodes | raise.rb:39:7:39:11 | raise | | raise.rb:39:7:39:22 | MethodCall | | raise.rb:39:13:39:22 | ExceptionA | +| raise.rb:41:3:42:22 | Rescue | +| raise.rb:41:13:41:13 | e | +| raise.rb:42:5:42:8 | puts | +| raise.rb:42:5:42:22 | MethodCall | +| raise.rb:42:10:42:22 | String | | raise.rb:44:3:44:6 | puts | | raise.rb:44:3:44:15 | MethodCall | | raise.rb:44:8:44:15 | String | | raise.rb:47:1:55:3 | enter m5 | | raise.rb:47:1:55:3 | exit m5 | -| raise.rb:47:1:55:3 | exit m5 (abnormal) | | raise.rb:47:1:55:3 | exit m5 (normal) | | raise.rb:47:8:47:8 | b | | raise.rb:49:5:51:7 | If | @@ -821,6 +874,8 @@ nodes | raise.rb:50:7:50:11 | raise | | raise.rb:50:7:50:22 | MethodCall | | raise.rb:50:13:50:22 | ExceptionA | +| raise.rb:52:3:52:13 | Rescue | +| raise.rb:52:13:52:13 | e | | raise.rb:54:3:54:6 | puts | | raise.rb:54:3:54:15 | MethodCall | | raise.rb:54:8:54:15 | String | @@ -834,6 +889,13 @@ nodes | raise.rb:60:7:60:11 | raise | | raise.rb:60:7:60:22 | MethodCall | | raise.rb:60:13:60:22 | ExceptionA | +| raise.rb:62:3:63:22 | Rescue | +| raise.rb:62:10:62:19 | ExceptionA | +| raise.rb:62:22:62:31 | ExceptionB | +| raise.rb:62:36:62:36 | e | +| raise.rb:63:5:63:8 | puts | +| raise.rb:63:5:63:22 | MethodCall | +| raise.rb:63:10:63:22 | String | | raise.rb:65:3:65:6 | puts | | raise.rb:65:3:65:15 | MethodCall | | raise.rb:65:8:65:15 | String | @@ -858,9 +920,18 @@ nodes | raise.rb:74:3:74:6 | puts | | raise.rb:74:3:74:20 | MethodCall | | raise.rb:74:8:74:20 | String | +| raise.rb:75:1:76:15 | Ensure | +| raise.rb:75:1:76:15 | [ensure: raise] Ensure | +| raise.rb:75:1:76:15 | [ensure: return] Ensure | +| raise.rb:76:3:76:6 | [ensure: raise] puts | +| raise.rb:76:3:76:6 | [ensure: return] puts | | raise.rb:76:3:76:6 | puts | | raise.rb:76:3:76:15 | MethodCall | +| raise.rb:76:3:76:15 | [ensure: raise] MethodCall | +| raise.rb:76:3:76:15 | [ensure: return] MethodCall | | raise.rb:76:8:76:15 | String | +| raise.rb:76:8:76:15 | [ensure: raise] String | +| raise.rb:76:8:76:15 | [ensure: return] String | | raise.rb:79:1:92:3 | enter m8 | | raise.rb:79:1:92:3 | exit m8 | | raise.rb:79:1:92:3 | exit m8 (abnormal) | @@ -885,6 +956,18 @@ nodes | raise.rb:87:5:87:8 | puts | | raise.rb:87:5:87:22 | MethodCall | | raise.rb:87:10:87:22 | String | +| raise.rb:88:3:89:17 | Ensure | +| raise.rb:88:3:89:17 | [ensure: raise] Ensure | +| raise.rb:88:3:89:17 | [ensure: return] Ensure | +| raise.rb:89:5:89:8 | [ensure: raise] puts | +| raise.rb:89:5:89:8 | [ensure: return] puts | +| raise.rb:89:5:89:8 | puts | +| raise.rb:89:5:89:17 | MethodCall | +| raise.rb:89:5:89:17 | [ensure: raise] MethodCall | +| raise.rb:89:5:89:17 | [ensure: return] MethodCall | +| raise.rb:89:10:89:17 | String | +| raise.rb:89:10:89:17 | [ensure: raise] String | +| raise.rb:89:10:89:17 | [ensure: return] String | | raise.rb:91:3:91:6 | puts | | raise.rb:91:3:91:15 | MethodCall | | raise.rb:91:8:91:15 | String | @@ -914,17 +997,87 @@ nodes | raise.rb:102:5:102:8 | puts | | raise.rb:102:5:102:22 | MethodCall | | raise.rb:102:10:102:22 | String | +| raise.rb:103:3:111:7 | Ensure | +| raise.rb:103:3:111:7 | [ensure: raise] Ensure | +| raise.rb:103:3:111:7 | [ensure: return] Ensure | +| raise.rb:104:5:104:8 | [ensure: raise] puts | +| raise.rb:104:5:104:8 | [ensure: return] puts | +| raise.rb:104:5:104:8 | puts | +| raise.rb:104:5:104:23 | MethodCall | +| raise.rb:104:5:104:23 | [ensure: raise] MethodCall | +| raise.rb:104:5:104:23 | [ensure: return] MethodCall | +| raise.rb:104:10:104:23 | String | +| raise.rb:104:10:104:23 | [ensure: raise] String | +| raise.rb:104:10:104:23 | [ensure: return] String | +| raise.rb:106:7:108:9 | If | +| raise.rb:106:7:108:9 | [ensure: raise] If | +| raise.rb:106:7:108:9 | [ensure: return] If | +| raise.rb:106:10:106:11 | [ensure: raise] b1 | +| raise.rb:106:10:106:11 | [ensure: return] b1 | +| raise.rb:106:10:106:11 | b1 | +| raise.rb:107:9:107:13 | [ensure: raise] raise | +| raise.rb:107:9:107:13 | [ensure: return] raise | +| raise.rb:107:9:107:13 | raise | +| raise.rb:107:9:107:26 | MethodCall | +| raise.rb:107:9:107:26 | [ensure: raise] MethodCall | +| raise.rb:107:9:107:26 | [ensure: return] MethodCall | +| raise.rb:107:15:107:26 | String | +| raise.rb:107:15:107:26 | [ensure: raise] String | +| raise.rb:107:15:107:26 | [ensure: return] String | +| raise.rb:109:5:110:25 | Ensure | +| raise.rb:109:5:110:25 | [ensure(1): raise] Ensure | +| raise.rb:109:5:110:25 | [ensure: raise, ensure(1): raise] Ensure | +| raise.rb:109:5:110:25 | [ensure: raise] Ensure | +| raise.rb:109:5:110:25 | [ensure: return, ensure(1): raise] Ensure | +| raise.rb:109:5:110:25 | [ensure: return] Ensure | +| raise.rb:110:7:110:10 | [ensure(1): raise] puts | +| raise.rb:110:7:110:10 | [ensure: raise, ensure(1): raise] puts | +| raise.rb:110:7:110:10 | [ensure: raise] puts | +| raise.rb:110:7:110:10 | [ensure: return, ensure(1): raise] puts | +| raise.rb:110:7:110:10 | [ensure: return] puts | +| raise.rb:110:7:110:10 | puts | +| raise.rb:110:7:110:25 | MethodCall | +| raise.rb:110:7:110:25 | [ensure(1): raise] MethodCall | +| raise.rb:110:7:110:25 | [ensure: raise, ensure(1): raise] MethodCall | +| raise.rb:110:7:110:25 | [ensure: raise] MethodCall | +| raise.rb:110:7:110:25 | [ensure: return, ensure(1): raise] MethodCall | +| raise.rb:110:7:110:25 | [ensure: return] MethodCall | +| raise.rb:110:12:110:25 | String | +| raise.rb:110:12:110:25 | [ensure(1): raise] String | +| raise.rb:110:12:110:25 | [ensure: raise, ensure(1): raise] String | +| raise.rb:110:12:110:25 | [ensure: raise] String | +| raise.rb:110:12:110:25 | [ensure: return, ensure(1): raise] String | +| raise.rb:110:12:110:25 | [ensure: return] String | | raise.rb:113:3:113:6 | puts | | raise.rb:113:3:113:15 | MethodCall | | raise.rb:113:8:113:15 | String | +| raise.rb:114:1:118:5 | Ensure | +| raise.rb:114:1:118:5 | [ensure: raise] Ensure | +| raise.rb:114:1:118:5 | [ensure: return] Ensure | +| raise.rb:115:3:115:6 | [ensure: raise] puts | +| raise.rb:115:3:115:6 | [ensure: return] puts | | raise.rb:115:3:115:6 | puts | | raise.rb:115:3:115:22 | MethodCall | +| raise.rb:115:3:115:22 | [ensure: raise] MethodCall | +| raise.rb:115:3:115:22 | [ensure: return] MethodCall | | raise.rb:115:8:115:22 | String | +| raise.rb:115:8:115:22 | [ensure: raise] String | +| raise.rb:115:8:115:22 | [ensure: return] String | | raise.rb:116:3:118:5 | If | +| raise.rb:116:3:118:5 | [ensure: raise] If | +| raise.rb:116:3:118:5 | [ensure: return] If | +| raise.rb:116:6:116:7 | [ensure: raise] b2 | +| raise.rb:116:6:116:7 | [ensure: return] b2 | | raise.rb:116:6:116:7 | b2 | +| raise.rb:117:5:117:9 | [ensure: raise] raise | +| raise.rb:117:5:117:9 | [ensure: return] raise | | raise.rb:117:5:117:9 | raise | | raise.rb:117:5:117:22 | MethodCall | +| raise.rb:117:5:117:22 | [ensure: raise] MethodCall | +| raise.rb:117:5:117:22 | [ensure: return] MethodCall | | raise.rb:117:11:117:22 | String | +| raise.rb:117:11:117:22 | [ensure: raise] String | +| raise.rb:117:11:117:22 | [ensure: return] String | | raise.rb:121:1:124:3 | enter m10 | | raise.rb:121:1:124:3 | exit m10 | | raise.rb:121:1:124:3 | exit m10 (abnormal) | @@ -936,7 +1089,7 @@ edges | break_ensure.rb:1:1:11:3 | exit m1 (normal) | break_ensure.rb:1:1:11:3 | exit m1 | semmle.label | successor | | break_ensure.rb:1:8:1:15 | elements | break_ensure.rb:2:18:2:25 | elements | semmle.label | successor | | break_ensure.rb:2:3:6:5 | For | break_ensure.rb:2:7:2:13 | element | semmle.label | non-empty | -| break_ensure.rb:2:3:6:5 | For | break_ensure.rb:8:3:10:5 | If | semmle.label | empty | +| break_ensure.rb:2:3:6:5 | For | break_ensure.rb:7:1:10:5 | Ensure | semmle.label | empty | | break_ensure.rb:2:7:2:13 | element | break_ensure.rb:3:5:5:7 | If | semmle.label | successor | | break_ensure.rb:2:18:2:25 | elements | break_ensure.rb:2:3:6:5 | For | semmle.label | successor | | break_ensure.rb:3:5:5:7 | If | break_ensure.rb:3:8:3:8 | x | semmle.label | successor | @@ -944,7 +1097,8 @@ edges | break_ensure.rb:3:8:3:12 | Binary | break_ensure.rb:2:3:6:5 | For | semmle.label | false | | break_ensure.rb:3:8:3:12 | Binary | break_ensure.rb:4:7:4:11 | Break | semmle.label | true | | break_ensure.rb:3:12:3:12 | 0 | break_ensure.rb:3:8:3:12 | Binary | semmle.label | successor | -| break_ensure.rb:4:7:4:11 | Break | break_ensure.rb:8:3:10:5 | If | semmle.label | break | +| break_ensure.rb:4:7:4:11 | Break | break_ensure.rb:7:1:10:5 | Ensure | semmle.label | break | +| break_ensure.rb:7:1:10:5 | Ensure | break_ensure.rb:8:3:10:5 | If | semmle.label | successor | | break_ensure.rb:8:3:10:5 | If | break_ensure.rb:8:6:8:13 | elements | semmle.label | successor | | break_ensure.rb:8:6:8:13 | elements | break_ensure.rb:8:15:8:18 | nil? | semmle.label | successor | | break_ensure.rb:8:6:8:18 | Call | break_ensure.rb:1:1:11:3 | exit m1 (normal) | semmle.label | false | @@ -962,19 +1116,59 @@ edges | break_ensure.rb:14:18:14:25 | elements | break_ensure.rb:14:3:24:5 | For | semmle.label | successor | | break_ensure.rb:16:7:18:9 | If | break_ensure.rb:16:10:16:10 | x | semmle.label | successor | | break_ensure.rb:16:10:16:10 | x | break_ensure.rb:16:14:16:14 | 0 | semmle.label | successor | -| break_ensure.rb:16:10:16:14 | Binary | break_ensure.rb:14:3:24:5 | For | semmle.label | false | | break_ensure.rb:16:10:16:14 | Binary | break_ensure.rb:17:9:17:13 | Break | semmle.label | true | +| break_ensure.rb:16:10:16:14 | Binary | break_ensure.rb:19:5:22:9 | Ensure | semmle.label | false | | break_ensure.rb:16:14:16:14 | 0 | break_ensure.rb:16:10:16:14 | Binary | semmle.label | successor | -| break_ensure.rb:17:9:17:13 | Break | break_ensure.rb:13:1:25:3 | exit m2 (normal) | semmle.label | break | +| break_ensure.rb:17:9:17:13 | Break | break_ensure.rb:19:5:22:9 | [ensure: break] Ensure | semmle.label | break | +| break_ensure.rb:19:5:22:9 | Ensure | break_ensure.rb:20:7:22:9 | If | semmle.label | successor | +| break_ensure.rb:19:5:22:9 | [ensure: break] Ensure | break_ensure.rb:20:7:22:9 | [ensure: break] If | semmle.label | successor | +| break_ensure.rb:20:7:22:9 | If | break_ensure.rb:20:10:20:17 | elements | semmle.label | successor | +| break_ensure.rb:20:7:22:9 | [ensure: break] If | break_ensure.rb:20:10:20:17 | [ensure: break] elements | semmle.label | successor | +| break_ensure.rb:20:10:20:17 | [ensure: break] elements | break_ensure.rb:20:19:20:22 | [ensure: break] nil? | semmle.label | successor | +| break_ensure.rb:20:10:20:17 | elements | break_ensure.rb:20:19:20:22 | nil? | semmle.label | successor | +| break_ensure.rb:20:10:20:22 | Call | break_ensure.rb:14:3:24:5 | For | semmle.label | false | +| break_ensure.rb:20:10:20:22 | Call | break_ensure.rb:21:14:21:27 | String | semmle.label | true | +| break_ensure.rb:20:10:20:22 | [ensure: break] Call | break_ensure.rb:13:1:25:3 | exit m2 (normal) | semmle.label | false | +| break_ensure.rb:20:10:20:22 | [ensure: break] Call | break_ensure.rb:21:14:21:27 | [ensure: break] String | semmle.label | true | +| break_ensure.rb:20:19:20:22 | [ensure: break] nil? | break_ensure.rb:20:10:20:22 | [ensure: break] Call | semmle.label | successor | +| break_ensure.rb:20:19:20:22 | nil? | break_ensure.rb:20:10:20:22 | Call | semmle.label | successor | +| break_ensure.rb:21:9:21:12 | [ensure: break] puts | break_ensure.rb:21:9:21:27 | [ensure: break] MethodCall | semmle.label | successor | +| break_ensure.rb:21:9:21:12 | puts | break_ensure.rb:21:9:21:27 | MethodCall | semmle.label | successor | +| break_ensure.rb:21:9:21:27 | MethodCall | break_ensure.rb:14:3:24:5 | For | semmle.label | successor | +| break_ensure.rb:21:9:21:27 | [ensure: break] MethodCall | break_ensure.rb:13:1:25:3 | exit m2 (normal) | semmle.label | break | +| break_ensure.rb:21:14:21:27 | String | break_ensure.rb:21:9:21:12 | puts | semmle.label | successor | +| break_ensure.rb:21:14:21:27 | [ensure: break] String | break_ensure.rb:21:9:21:12 | [ensure: break] puts | semmle.label | successor | | break_ensure.rb:27:1:42:3 | enter m3 | break_ensure.rb:27:8:27:15 | elements | semmle.label | successor | | break_ensure.rb:27:1:42:3 | exit m3 (normal) | break_ensure.rb:27:1:42:3 | exit m3 | semmle.label | successor | | break_ensure.rb:27:8:27:15 | elements | break_ensure.rb:29:5:31:7 | If | semmle.label | successor | | break_ensure.rb:29:5:31:7 | If | break_ensure.rb:29:8:29:15 | elements | semmle.label | successor | | break_ensure.rb:29:8:29:15 | elements | break_ensure.rb:29:17:29:20 | nil? | semmle.label | successor | | break_ensure.rb:29:8:29:20 | Call | break_ensure.rb:30:7:30:12 | Return | semmle.label | true | -| break_ensure.rb:29:8:29:20 | Call | break_ensure.rb:41:8:41:13 | String | semmle.label | false | +| break_ensure.rb:29:8:29:20 | Call | break_ensure.rb:32:3:39:7 | Ensure | semmle.label | false | | break_ensure.rb:29:17:29:20 | nil? | break_ensure.rb:29:8:29:20 | Call | semmle.label | successor | -| break_ensure.rb:30:7:30:12 | Return | break_ensure.rb:27:1:42:3 | exit m3 (normal) | semmle.label | return | +| break_ensure.rb:30:7:30:12 | Return | break_ensure.rb:32:3:39:7 | [ensure: return] Ensure | semmle.label | return | +| break_ensure.rb:32:3:39:7 | Ensure | break_ensure.rb:33:20:33:27 | elements | semmle.label | successor | +| break_ensure.rb:32:3:39:7 | [ensure: return] Ensure | break_ensure.rb:33:20:33:27 | [ensure: return] elements | semmle.label | successor | +| break_ensure.rb:33:5:39:7 | For | break_ensure.rb:33:9:33:15 | element | semmle.label | non-empty | +| break_ensure.rb:33:5:39:7 | For | break_ensure.rb:41:8:41:13 | String | semmle.label | empty | +| break_ensure.rb:33:5:39:7 | [ensure: return] For | break_ensure.rb:27:1:42:3 | exit m3 (normal) | semmle.label | return | +| break_ensure.rb:33:5:39:7 | [ensure: return] For | break_ensure.rb:33:9:33:15 | [ensure: return] element | semmle.label | non-empty | +| break_ensure.rb:33:9:33:15 | [ensure: return] element | break_ensure.rb:35:9:37:11 | [ensure: return] If | semmle.label | successor | +| break_ensure.rb:33:9:33:15 | element | break_ensure.rb:35:9:37:11 | If | semmle.label | successor | +| break_ensure.rb:33:20:33:27 | [ensure: return] elements | break_ensure.rb:33:5:39:7 | [ensure: return] For | semmle.label | successor | +| break_ensure.rb:33:20:33:27 | elements | break_ensure.rb:33:5:39:7 | For | semmle.label | successor | +| break_ensure.rb:35:9:37:11 | If | break_ensure.rb:35:12:35:12 | x | semmle.label | successor | +| break_ensure.rb:35:9:37:11 | [ensure: return] If | break_ensure.rb:35:12:35:12 | [ensure: return] x | semmle.label | successor | +| break_ensure.rb:35:12:35:12 | [ensure: return] x | break_ensure.rb:35:16:35:16 | [ensure: return] 0 | semmle.label | successor | +| break_ensure.rb:35:12:35:12 | x | break_ensure.rb:35:16:35:16 | 0 | semmle.label | successor | +| break_ensure.rb:35:12:35:16 | Binary | break_ensure.rb:33:5:39:7 | For | semmle.label | false | +| break_ensure.rb:35:12:35:16 | Binary | break_ensure.rb:36:11:36:15 | Break | semmle.label | true | +| break_ensure.rb:35:12:35:16 | [ensure: return] Binary | break_ensure.rb:33:5:39:7 | [ensure: return] For | semmle.label | false | +| break_ensure.rb:35:12:35:16 | [ensure: return] Binary | break_ensure.rb:36:11:36:15 | [ensure: return] Break | semmle.label | true | +| break_ensure.rb:35:16:35:16 | 0 | break_ensure.rb:35:12:35:16 | Binary | semmle.label | successor | +| break_ensure.rb:35:16:35:16 | [ensure: return] 0 | break_ensure.rb:35:12:35:16 | [ensure: return] Binary | semmle.label | successor | +| break_ensure.rb:36:11:36:15 | Break | break_ensure.rb:41:8:41:13 | String | semmle.label | break | +| break_ensure.rb:36:11:36:15 | [ensure: return] Break | break_ensure.rb:27:1:42:3 | exit m3 (normal) | semmle.label | return | | break_ensure.rb:41:3:41:6 | puts | break_ensure.rb:41:3:41:13 | MethodCall | semmle.label | successor | | break_ensure.rb:41:3:41:13 | MethodCall | break_ensure.rb:27:1:42:3 | exit m3 (normal) | semmle.label | successor | | break_ensure.rb:41:8:41:13 | String | break_ensure.rb:41:3:41:6 | puts | semmle.label | successor | @@ -1203,7 +1397,14 @@ edges | cfg.rb:75:27:75:28 | 10 | cfg.rb:75:23:75:28 | Binary | semmle.label | successor | | cfg.rb:75:35:75:36 | 10 | cfg.rb:78:3:78:3 | ; | semmle.label | successor | | cfg.rb:75:43:75:43 | x | cfg.rb:78:3:78:3 | ; | semmle.label | successor | -| cfg.rb:78:3:78:3 | ; | cfg.rb:88:19:88:19 | x | semmle.label | successor | +| cfg.rb:78:3:78:3 | ; | cfg.rb:83:8:83:11 | String | semmle.label | successor | +| cfg.rb:83:3:83:6 | puts | cfg.rb:83:3:83:11 | MethodCall | semmle.label | successor | +| cfg.rb:83:3:83:11 | MethodCall | cfg.rb:84:1:85:12 | Ensure | semmle.label | successor | +| cfg.rb:83:8:83:11 | String | cfg.rb:83:3:83:6 | puts | semmle.label | successor | +| cfg.rb:84:1:85:12 | Ensure | cfg.rb:85:8:85:12 | String | semmle.label | successor | +| cfg.rb:85:3:85:6 | puts | cfg.rb:85:3:85:12 | MethodCall | semmle.label | successor | +| cfg.rb:85:3:85:12 | MethodCall | cfg.rb:88:19:88:19 | x | semmle.label | successor | +| cfg.rb:85:8:85:12 | String | cfg.rb:85:3:85:6 | puts | semmle.label | successor | | cfg.rb:88:1:88:6 | escape | cfg.rb:88:1:88:23 | Assignment | semmle.label | successor | | cfg.rb:88:1:88:23 | Assignment | cfg.rb:90:11:90:13 | 1.4 | semmle.label | successor | | cfg.rb:88:10:88:23 | String | cfg.rb:88:1:88:6 | escape | semmle.label | successor | @@ -1729,47 +1930,61 @@ edges | raise.rb:16:8:16:8 | b | raise.rb:17:13:17:22 | ExceptionA | semmle.label | true | | raise.rb:16:8:16:8 | b | raise.rb:22:8:22:15 | String | semmle.label | false | | raise.rb:17:7:17:11 | raise | raise.rb:17:7:17:22 | MethodCall | semmle.label | successor | -| raise.rb:17:7:17:22 | MethodCall | raise.rb:14:1:23:3 | exit m2 (abnormal) | semmle.label | raise | +| raise.rb:17:7:17:22 | MethodCall | raise.rb:19:3:20:18 | Rescue | semmle.label | raise | | raise.rb:17:13:17:22 | ExceptionA | raise.rb:17:7:17:11 | raise | semmle.label | successor | +| raise.rb:19:3:20:18 | Rescue | raise.rb:19:10:19:19 | ExceptionA | semmle.label | successor | +| raise.rb:19:10:19:19 | ExceptionA | raise.rb:14:1:23:3 | exit m2 (abnormal) | semmle.label | raise | +| raise.rb:19:10:19:19 | ExceptionA | raise.rb:20:10:20:18 | String | semmle.label | match | +| raise.rb:20:5:20:8 | puts | raise.rb:20:5:20:18 | MethodCall | semmle.label | successor | +| raise.rb:20:5:20:18 | MethodCall | raise.rb:22:8:22:15 | String | semmle.label | successor | +| raise.rb:20:10:20:18 | String | raise.rb:20:5:20:8 | puts | semmle.label | successor | | raise.rb:22:3:22:6 | puts | raise.rb:22:3:22:15 | MethodCall | semmle.label | successor | | raise.rb:22:3:22:15 | MethodCall | raise.rb:14:1:23:3 | exit m2 (normal) | semmle.label | successor | | raise.rb:22:8:22:15 | String | raise.rb:22:3:22:6 | puts | semmle.label | successor | | raise.rb:25:1:34:3 | enter m3 | raise.rb:25:8:25:8 | b | semmle.label | successor | -| raise.rb:25:1:34:3 | exit m3 (abnormal) | raise.rb:25:1:34:3 | exit m3 | semmle.label | successor | | raise.rb:25:1:34:3 | exit m3 (normal) | raise.rb:25:1:34:3 | exit m3 | semmle.label | successor | | raise.rb:25:8:25:8 | b | raise.rb:27:5:29:7 | If | semmle.label | successor | | raise.rb:27:5:29:7 | If | raise.rb:27:8:27:8 | b | semmle.label | successor | | raise.rb:27:8:27:8 | b | raise.rb:28:13:28:22 | ExceptionA | semmle.label | true | | raise.rb:27:8:27:8 | b | raise.rb:33:8:33:15 | String | semmle.label | false | | raise.rb:28:7:28:11 | raise | raise.rb:28:7:28:22 | MethodCall | semmle.label | successor | -| raise.rb:28:7:28:22 | MethodCall | raise.rb:25:1:34:3 | exit m3 (abnormal) | semmle.label | raise | +| raise.rb:28:7:28:22 | MethodCall | raise.rb:30:3:31:18 | Rescue | semmle.label | raise | | raise.rb:28:13:28:22 | ExceptionA | raise.rb:28:7:28:11 | raise | semmle.label | successor | +| raise.rb:30:3:31:18 | Rescue | raise.rb:31:10:31:18 | String | semmle.label | successor | +| raise.rb:31:5:31:8 | puts | raise.rb:31:5:31:18 | MethodCall | semmle.label | successor | +| raise.rb:31:5:31:18 | MethodCall | raise.rb:33:8:33:15 | String | semmle.label | successor | +| raise.rb:31:10:31:18 | String | raise.rb:31:5:31:8 | puts | semmle.label | successor | | raise.rb:33:3:33:6 | puts | raise.rb:33:3:33:15 | MethodCall | semmle.label | successor | | raise.rb:33:3:33:15 | MethodCall | raise.rb:25:1:34:3 | exit m3 (normal) | semmle.label | successor | | raise.rb:33:8:33:15 | String | raise.rb:33:3:33:6 | puts | semmle.label | successor | | raise.rb:36:1:45:3 | enter m4 | raise.rb:36:8:36:8 | b | semmle.label | successor | -| raise.rb:36:1:45:3 | exit m4 (abnormal) | raise.rb:36:1:45:3 | exit m4 | semmle.label | successor | | raise.rb:36:1:45:3 | exit m4 (normal) | raise.rb:36:1:45:3 | exit m4 | semmle.label | successor | | raise.rb:36:8:36:8 | b | raise.rb:38:5:40:7 | If | semmle.label | successor | | raise.rb:38:5:40:7 | If | raise.rb:38:8:38:8 | b | semmle.label | successor | | raise.rb:38:8:38:8 | b | raise.rb:39:13:39:22 | ExceptionA | semmle.label | true | | raise.rb:38:8:38:8 | b | raise.rb:44:8:44:15 | String | semmle.label | false | | raise.rb:39:7:39:11 | raise | raise.rb:39:7:39:22 | MethodCall | semmle.label | successor | -| raise.rb:39:7:39:22 | MethodCall | raise.rb:36:1:45:3 | exit m4 (abnormal) | semmle.label | raise | +| raise.rb:39:7:39:22 | MethodCall | raise.rb:41:3:42:22 | Rescue | semmle.label | raise | | raise.rb:39:13:39:22 | ExceptionA | raise.rb:39:7:39:11 | raise | semmle.label | successor | +| raise.rb:41:3:42:22 | Rescue | raise.rb:41:13:41:13 | e | semmle.label | successor | +| raise.rb:41:13:41:13 | e | raise.rb:42:10:42:22 | String | semmle.label | successor | +| raise.rb:42:5:42:8 | puts | raise.rb:42:5:42:22 | MethodCall | semmle.label | successor | +| raise.rb:42:5:42:22 | MethodCall | raise.rb:44:8:44:15 | String | semmle.label | successor | +| raise.rb:42:10:42:22 | String | raise.rb:42:5:42:8 | puts | semmle.label | successor | | raise.rb:44:3:44:6 | puts | raise.rb:44:3:44:15 | MethodCall | semmle.label | successor | | raise.rb:44:3:44:15 | MethodCall | raise.rb:36:1:45:3 | exit m4 (normal) | semmle.label | successor | | raise.rb:44:8:44:15 | String | raise.rb:44:3:44:6 | puts | semmle.label | successor | | raise.rb:47:1:55:3 | enter m5 | raise.rb:47:8:47:8 | b | semmle.label | successor | -| raise.rb:47:1:55:3 | exit m5 (abnormal) | raise.rb:47:1:55:3 | exit m5 | semmle.label | successor | | raise.rb:47:1:55:3 | exit m5 (normal) | raise.rb:47:1:55:3 | exit m5 | semmle.label | successor | | raise.rb:47:8:47:8 | b | raise.rb:49:5:51:7 | If | semmle.label | successor | | raise.rb:49:5:51:7 | If | raise.rb:49:8:49:8 | b | semmle.label | successor | | raise.rb:49:8:49:8 | b | raise.rb:50:13:50:22 | ExceptionA | semmle.label | true | | raise.rb:49:8:49:8 | b | raise.rb:54:8:54:15 | String | semmle.label | false | | raise.rb:50:7:50:11 | raise | raise.rb:50:7:50:22 | MethodCall | semmle.label | successor | -| raise.rb:50:7:50:22 | MethodCall | raise.rb:47:1:55:3 | exit m5 (abnormal) | semmle.label | raise | +| raise.rb:50:7:50:22 | MethodCall | raise.rb:52:3:52:13 | Rescue | semmle.label | raise | | raise.rb:50:13:50:22 | ExceptionA | raise.rb:50:7:50:11 | raise | semmle.label | successor | +| raise.rb:52:3:52:13 | Rescue | raise.rb:52:13:52:13 | e | semmle.label | successor | +| raise.rb:52:13:52:13 | e | raise.rb:54:8:54:15 | String | semmle.label | successor | | raise.rb:54:3:54:6 | puts | raise.rb:54:3:54:15 | MethodCall | semmle.label | successor | | raise.rb:54:3:54:15 | MethodCall | raise.rb:47:1:55:3 | exit m5 (normal) | semmle.label | successor | | raise.rb:54:8:54:15 | String | raise.rb:54:3:54:6 | puts | semmle.label | successor | @@ -1781,8 +1996,17 @@ edges | raise.rb:59:8:59:8 | b | raise.rb:60:13:60:22 | ExceptionA | semmle.label | true | | raise.rb:59:8:59:8 | b | raise.rb:65:8:65:15 | String | semmle.label | false | | raise.rb:60:7:60:11 | raise | raise.rb:60:7:60:22 | MethodCall | semmle.label | successor | -| raise.rb:60:7:60:22 | MethodCall | raise.rb:57:1:66:3 | exit m6 (abnormal) | semmle.label | raise | +| raise.rb:60:7:60:22 | MethodCall | raise.rb:62:3:63:22 | Rescue | semmle.label | raise | | raise.rb:60:13:60:22 | ExceptionA | raise.rb:60:7:60:11 | raise | semmle.label | successor | +| raise.rb:62:3:63:22 | Rescue | raise.rb:62:10:62:19 | ExceptionA | semmle.label | successor | +| raise.rb:62:10:62:19 | ExceptionA | raise.rb:62:22:62:31 | ExceptionB | semmle.label | no-match | +| raise.rb:62:10:62:19 | ExceptionA | raise.rb:62:36:62:36 | e | semmle.label | match | +| raise.rb:62:22:62:31 | ExceptionB | raise.rb:57:1:66:3 | exit m6 (abnormal) | semmle.label | raise | +| raise.rb:62:22:62:31 | ExceptionB | raise.rb:62:36:62:36 | e | semmle.label | match | +| raise.rb:62:36:62:36 | e | raise.rb:63:10:63:22 | String | semmle.label | successor | +| raise.rb:63:5:63:8 | puts | raise.rb:63:5:63:22 | MethodCall | semmle.label | successor | +| raise.rb:63:5:63:22 | MethodCall | raise.rb:65:8:65:15 | String | semmle.label | successor | +| raise.rb:63:10:63:22 | String | raise.rb:63:5:63:8 | puts | semmle.label | successor | | raise.rb:65:3:65:6 | puts | raise.rb:65:3:65:15 | MethodCall | semmle.label | successor | | raise.rb:65:3:65:15 | MethodCall | raise.rb:57:1:66:3 | exit m6 (normal) | semmle.label | successor | | raise.rb:65:8:65:15 | String | raise.rb:65:3:65:6 | puts | semmle.label | successor | @@ -1796,21 +2020,30 @@ edges | raise.rb:69:6:69:10 | Binary | raise.rb:71:3:72:18 | Elsif | semmle.label | false | | raise.rb:69:10:69:10 | 2 | raise.rb:69:6:69:10 | Binary | semmle.label | successor | | raise.rb:70:5:70:9 | raise | raise.rb:70:5:70:17 | MethodCall | semmle.label | successor | -| raise.rb:70:5:70:17 | MethodCall | raise.rb:68:1:77:3 | exit m7 (abnormal) | semmle.label | raise | +| raise.rb:70:5:70:17 | MethodCall | raise.rb:75:1:76:15 | [ensure: raise] Ensure | semmle.label | raise | | raise.rb:70:11:70:17 | String | raise.rb:70:5:70:9 | raise | semmle.label | successor | | raise.rb:71:3:72:18 | Elsif | raise.rb:71:9:71:9 | x | semmle.label | successor | | raise.rb:71:9:71:9 | x | raise.rb:71:13:71:13 | 0 | semmle.label | successor | | raise.rb:71:9:71:13 | Binary | raise.rb:72:12:72:18 | String | semmle.label | true | | raise.rb:71:9:71:13 | Binary | raise.rb:74:8:74:20 | String | semmle.label | false | | raise.rb:71:13:71:13 | 0 | raise.rb:71:9:71:13 | Binary | semmle.label | successor | -| raise.rb:72:5:72:18 | Return | raise.rb:68:1:77:3 | exit m7 (normal) | semmle.label | return | +| raise.rb:72:5:72:18 | Return | raise.rb:75:1:76:15 | [ensure: return] Ensure | semmle.label | return | | raise.rb:72:12:72:18 | String | raise.rb:72:5:72:18 | Return | semmle.label | successor | | raise.rb:74:3:74:6 | puts | raise.rb:74:3:74:20 | MethodCall | semmle.label | successor | -| raise.rb:74:3:74:20 | MethodCall | raise.rb:76:8:76:15 | String | semmle.label | successor | +| raise.rb:74:3:74:20 | MethodCall | raise.rb:75:1:76:15 | Ensure | semmle.label | successor | | raise.rb:74:8:74:20 | String | raise.rb:74:3:74:6 | puts | semmle.label | successor | +| raise.rb:75:1:76:15 | Ensure | raise.rb:76:8:76:15 | String | semmle.label | successor | +| raise.rb:75:1:76:15 | [ensure: raise] Ensure | raise.rb:76:8:76:15 | [ensure: raise] String | semmle.label | successor | +| raise.rb:75:1:76:15 | [ensure: return] Ensure | raise.rb:76:8:76:15 | [ensure: return] String | semmle.label | successor | +| raise.rb:76:3:76:6 | [ensure: raise] puts | raise.rb:76:3:76:15 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:76:3:76:6 | [ensure: return] puts | raise.rb:76:3:76:15 | [ensure: return] MethodCall | semmle.label | successor | | raise.rb:76:3:76:6 | puts | raise.rb:76:3:76:15 | MethodCall | semmle.label | successor | | raise.rb:76:3:76:15 | MethodCall | raise.rb:68:1:77:3 | exit m7 (normal) | semmle.label | successor | +| raise.rb:76:3:76:15 | [ensure: raise] MethodCall | raise.rb:68:1:77:3 | exit m7 (abnormal) | semmle.label | raise | +| raise.rb:76:3:76:15 | [ensure: return] MethodCall | raise.rb:68:1:77:3 | exit m7 (normal) | semmle.label | return | | raise.rb:76:8:76:15 | String | raise.rb:76:3:76:6 | puts | semmle.label | successor | +| raise.rb:76:8:76:15 | [ensure: raise] String | raise.rb:76:3:76:6 | [ensure: raise] puts | semmle.label | successor | +| raise.rb:76:8:76:15 | [ensure: return] String | raise.rb:76:3:76:6 | [ensure: return] puts | semmle.label | successor | | raise.rb:79:1:92:3 | enter m8 | raise.rb:79:8:79:8 | x | semmle.label | successor | | raise.rb:79:1:92:3 | exit m8 (abnormal) | raise.rb:79:1:92:3 | exit m8 | semmle.label | successor | | raise.rb:79:1:92:3 | exit m8 (normal) | raise.rb:79:1:92:3 | exit m8 | semmle.label | successor | @@ -1824,18 +2057,30 @@ edges | raise.rb:82:8:82:12 | Binary | raise.rb:84:5:85:20 | Elsif | semmle.label | false | | raise.rb:82:12:82:12 | 2 | raise.rb:82:8:82:12 | Binary | semmle.label | successor | | raise.rb:83:7:83:11 | raise | raise.rb:83:7:83:19 | MethodCall | semmle.label | successor | -| raise.rb:83:7:83:19 | MethodCall | raise.rb:79:1:92:3 | exit m8 (abnormal) | semmle.label | raise | +| raise.rb:83:7:83:19 | MethodCall | raise.rb:88:3:89:17 | [ensure: raise] Ensure | semmle.label | raise | | raise.rb:83:13:83:19 | String | raise.rb:83:7:83:11 | raise | semmle.label | successor | | raise.rb:84:5:85:20 | Elsif | raise.rb:84:11:84:11 | x | semmle.label | successor | | raise.rb:84:11:84:11 | x | raise.rb:84:15:84:15 | 0 | semmle.label | successor | | raise.rb:84:11:84:15 | Binary | raise.rb:85:14:85:20 | String | semmle.label | true | | raise.rb:84:11:84:15 | Binary | raise.rb:87:10:87:22 | String | semmle.label | false | | raise.rb:84:15:84:15 | 0 | raise.rb:84:11:84:15 | Binary | semmle.label | successor | -| raise.rb:85:7:85:20 | Return | raise.rb:79:1:92:3 | exit m8 (normal) | semmle.label | return | +| raise.rb:85:7:85:20 | Return | raise.rb:88:3:89:17 | [ensure: return] Ensure | semmle.label | return | | raise.rb:85:14:85:20 | String | raise.rb:85:7:85:20 | Return | semmle.label | successor | | raise.rb:87:5:87:8 | puts | raise.rb:87:5:87:22 | MethodCall | semmle.label | successor | -| raise.rb:87:5:87:22 | MethodCall | raise.rb:91:8:91:15 | String | semmle.label | successor | +| raise.rb:87:5:87:22 | MethodCall | raise.rb:88:3:89:17 | Ensure | semmle.label | successor | | raise.rb:87:10:87:22 | String | raise.rb:87:5:87:8 | puts | semmle.label | successor | +| raise.rb:88:3:89:17 | Ensure | raise.rb:89:10:89:17 | String | semmle.label | successor | +| raise.rb:88:3:89:17 | [ensure: raise] Ensure | raise.rb:89:10:89:17 | [ensure: raise] String | semmle.label | successor | +| raise.rb:88:3:89:17 | [ensure: return] Ensure | raise.rb:89:10:89:17 | [ensure: return] String | semmle.label | successor | +| raise.rb:89:5:89:8 | [ensure: raise] puts | raise.rb:89:5:89:17 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:89:5:89:8 | [ensure: return] puts | raise.rb:89:5:89:17 | [ensure: return] MethodCall | semmle.label | successor | +| raise.rb:89:5:89:8 | puts | raise.rb:89:5:89:17 | MethodCall | semmle.label | successor | +| raise.rb:89:5:89:17 | MethodCall | raise.rb:91:8:91:15 | String | semmle.label | successor | +| raise.rb:89:5:89:17 | [ensure: raise] MethodCall | raise.rb:79:1:92:3 | exit m8 (abnormal) | semmle.label | raise | +| raise.rb:89:5:89:17 | [ensure: return] MethodCall | raise.rb:79:1:92:3 | exit m8 (normal) | semmle.label | return | +| raise.rb:89:10:89:17 | String | raise.rb:89:5:89:8 | puts | semmle.label | successor | +| raise.rb:89:10:89:17 | [ensure: raise] String | raise.rb:89:5:89:8 | [ensure: raise] puts | semmle.label | successor | +| raise.rb:89:10:89:17 | [ensure: return] String | raise.rb:89:5:89:8 | [ensure: return] puts | semmle.label | successor | | raise.rb:91:3:91:6 | puts | raise.rb:91:3:91:15 | MethodCall | semmle.label | successor | | raise.rb:91:3:91:15 | MethodCall | raise.rb:79:1:92:3 | exit m8 (normal) | semmle.label | successor | | raise.rb:91:8:91:15 | String | raise.rb:91:3:91:6 | puts | semmle.label | successor | @@ -1854,30 +2099,105 @@ edges | raise.rb:97:8:97:12 | Binary | raise.rb:99:5:100:20 | Elsif | semmle.label | false | | raise.rb:97:12:97:12 | 2 | raise.rb:97:8:97:12 | Binary | semmle.label | successor | | raise.rb:98:7:98:11 | raise | raise.rb:98:7:98:19 | MethodCall | semmle.label | successor | -| raise.rb:98:7:98:19 | MethodCall | raise.rb:94:1:119:3 | exit m9 (abnormal) | semmle.label | raise | +| raise.rb:98:7:98:19 | MethodCall | raise.rb:103:3:111:7 | [ensure: raise] Ensure | semmle.label | raise | | raise.rb:98:13:98:19 | String | raise.rb:98:7:98:11 | raise | semmle.label | successor | | raise.rb:99:5:100:20 | Elsif | raise.rb:99:11:99:11 | x | semmle.label | successor | | raise.rb:99:11:99:11 | x | raise.rb:99:15:99:15 | 0 | semmle.label | successor | | raise.rb:99:11:99:15 | Binary | raise.rb:100:14:100:20 | String | semmle.label | true | | raise.rb:99:11:99:15 | Binary | raise.rb:102:10:102:22 | String | semmle.label | false | | raise.rb:99:15:99:15 | 0 | raise.rb:99:11:99:15 | Binary | semmle.label | successor | -| raise.rb:100:7:100:20 | Return | raise.rb:94:1:119:3 | exit m9 (normal) | semmle.label | return | +| raise.rb:100:7:100:20 | Return | raise.rb:103:3:111:7 | [ensure: return] Ensure | semmle.label | return | | raise.rb:100:14:100:20 | String | raise.rb:100:7:100:20 | Return | semmle.label | successor | | raise.rb:102:5:102:8 | puts | raise.rb:102:5:102:22 | MethodCall | semmle.label | successor | -| raise.rb:102:5:102:22 | MethodCall | raise.rb:113:8:113:15 | String | semmle.label | successor | +| raise.rb:102:5:102:22 | MethodCall | raise.rb:103:3:111:7 | Ensure | semmle.label | successor | | raise.rb:102:10:102:22 | String | raise.rb:102:5:102:8 | puts | semmle.label | successor | +| raise.rb:103:3:111:7 | Ensure | raise.rb:104:10:104:23 | String | semmle.label | successor | +| raise.rb:103:3:111:7 | [ensure: raise] Ensure | raise.rb:104:10:104:23 | [ensure: raise] String | semmle.label | successor | +| raise.rb:103:3:111:7 | [ensure: return] Ensure | raise.rb:104:10:104:23 | [ensure: return] String | semmle.label | successor | +| raise.rb:104:5:104:8 | [ensure: raise] puts | raise.rb:104:5:104:23 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:104:5:104:8 | [ensure: return] puts | raise.rb:104:5:104:23 | [ensure: return] MethodCall | semmle.label | successor | +| raise.rb:104:5:104:8 | puts | raise.rb:104:5:104:23 | MethodCall | semmle.label | successor | +| raise.rb:104:5:104:23 | MethodCall | raise.rb:106:7:108:9 | If | semmle.label | successor | +| raise.rb:104:5:104:23 | [ensure: raise] MethodCall | raise.rb:106:7:108:9 | [ensure: raise] If | semmle.label | successor | +| raise.rb:104:5:104:23 | [ensure: return] MethodCall | raise.rb:106:7:108:9 | [ensure: return] If | semmle.label | successor | +| raise.rb:104:10:104:23 | String | raise.rb:104:5:104:8 | puts | semmle.label | successor | +| raise.rb:104:10:104:23 | [ensure: raise] String | raise.rb:104:5:104:8 | [ensure: raise] puts | semmle.label | successor | +| raise.rb:104:10:104:23 | [ensure: return] String | raise.rb:104:5:104:8 | [ensure: return] puts | semmle.label | successor | +| raise.rb:106:7:108:9 | If | raise.rb:106:10:106:11 | b1 | semmle.label | successor | +| raise.rb:106:7:108:9 | [ensure: raise] If | raise.rb:106:10:106:11 | [ensure: raise] b1 | semmle.label | successor | +| raise.rb:106:7:108:9 | [ensure: return] If | raise.rb:106:10:106:11 | [ensure: return] b1 | semmle.label | successor | +| raise.rb:106:10:106:11 | [ensure: raise] b1 | raise.rb:107:15:107:26 | [ensure: raise] String | semmle.label | true | +| raise.rb:106:10:106:11 | [ensure: raise] b1 | raise.rb:109:5:110:25 | [ensure: raise] Ensure | semmle.label | false | +| raise.rb:106:10:106:11 | [ensure: return] b1 | raise.rb:107:15:107:26 | [ensure: return] String | semmle.label | true | +| raise.rb:106:10:106:11 | [ensure: return] b1 | raise.rb:109:5:110:25 | [ensure: return] Ensure | semmle.label | false | +| raise.rb:106:10:106:11 | b1 | raise.rb:107:15:107:26 | String | semmle.label | true | +| raise.rb:106:10:106:11 | b1 | raise.rb:109:5:110:25 | Ensure | semmle.label | false | +| raise.rb:107:9:107:13 | [ensure: raise] raise | raise.rb:107:9:107:26 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:107:9:107:13 | [ensure: return] raise | raise.rb:107:9:107:26 | [ensure: return] MethodCall | semmle.label | successor | +| raise.rb:107:9:107:13 | raise | raise.rb:107:9:107:26 | MethodCall | semmle.label | successor | +| raise.rb:107:9:107:26 | MethodCall | raise.rb:109:5:110:25 | [ensure(1): raise] Ensure | semmle.label | raise | +| raise.rb:107:9:107:26 | [ensure: raise] MethodCall | raise.rb:109:5:110:25 | [ensure: raise, ensure(1): raise] Ensure | semmle.label | raise | +| raise.rb:107:9:107:26 | [ensure: return] MethodCall | raise.rb:109:5:110:25 | [ensure: return, ensure(1): raise] Ensure | semmle.label | raise | +| raise.rb:107:15:107:26 | String | raise.rb:107:9:107:13 | raise | semmle.label | successor | +| raise.rb:107:15:107:26 | [ensure: raise] String | raise.rb:107:9:107:13 | [ensure: raise] raise | semmle.label | successor | +| raise.rb:107:15:107:26 | [ensure: return] String | raise.rb:107:9:107:13 | [ensure: return] raise | semmle.label | successor | +| raise.rb:109:5:110:25 | Ensure | raise.rb:110:12:110:25 | String | semmle.label | successor | +| raise.rb:109:5:110:25 | [ensure(1): raise] Ensure | raise.rb:110:12:110:25 | [ensure(1): raise] String | semmle.label | successor | +| raise.rb:109:5:110:25 | [ensure: raise, ensure(1): raise] Ensure | raise.rb:110:12:110:25 | [ensure: raise, ensure(1): raise] String | semmle.label | successor | +| raise.rb:109:5:110:25 | [ensure: raise] Ensure | raise.rb:110:12:110:25 | [ensure: raise] String | semmle.label | successor | +| raise.rb:109:5:110:25 | [ensure: return, ensure(1): raise] Ensure | raise.rb:110:12:110:25 | [ensure: return, ensure(1): raise] String | semmle.label | successor | +| raise.rb:109:5:110:25 | [ensure: return] Ensure | raise.rb:110:12:110:25 | [ensure: return] String | semmle.label | successor | +| raise.rb:110:7:110:10 | [ensure(1): raise] puts | raise.rb:110:7:110:25 | [ensure(1): raise] MethodCall | semmle.label | successor | +| raise.rb:110:7:110:10 | [ensure: raise, ensure(1): raise] puts | raise.rb:110:7:110:25 | [ensure: raise, ensure(1): raise] MethodCall | semmle.label | successor | +| raise.rb:110:7:110:10 | [ensure: raise] puts | raise.rb:110:7:110:25 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:110:7:110:10 | [ensure: return, ensure(1): raise] puts | raise.rb:110:7:110:25 | [ensure: return, ensure(1): raise] MethodCall | semmle.label | successor | +| raise.rb:110:7:110:10 | [ensure: return] puts | raise.rb:110:7:110:25 | [ensure: return] MethodCall | semmle.label | successor | +| raise.rb:110:7:110:10 | puts | raise.rb:110:7:110:25 | MethodCall | semmle.label | successor | +| raise.rb:110:7:110:25 | MethodCall | raise.rb:113:8:113:15 | String | semmle.label | successor | +| raise.rb:110:7:110:25 | [ensure(1): raise] MethodCall | raise.rb:114:1:118:5 | [ensure: raise] Ensure | semmle.label | raise | +| raise.rb:110:7:110:25 | [ensure: raise, ensure(1): raise] MethodCall | raise.rb:114:1:118:5 | [ensure: raise] Ensure | semmle.label | raise | +| raise.rb:110:7:110:25 | [ensure: raise] MethodCall | raise.rb:114:1:118:5 | [ensure: raise] Ensure | semmle.label | raise | +| raise.rb:110:7:110:25 | [ensure: return, ensure(1): raise] MethodCall | raise.rb:114:1:118:5 | [ensure: raise] Ensure | semmle.label | raise | +| raise.rb:110:7:110:25 | [ensure: return] MethodCall | raise.rb:114:1:118:5 | [ensure: return] Ensure | semmle.label | return | +| raise.rb:110:12:110:25 | String | raise.rb:110:7:110:10 | puts | semmle.label | successor | +| raise.rb:110:12:110:25 | [ensure(1): raise] String | raise.rb:110:7:110:10 | [ensure(1): raise] puts | semmle.label | successor | +| raise.rb:110:12:110:25 | [ensure: raise, ensure(1): raise] String | raise.rb:110:7:110:10 | [ensure: raise, ensure(1): raise] puts | semmle.label | successor | +| raise.rb:110:12:110:25 | [ensure: raise] String | raise.rb:110:7:110:10 | [ensure: raise] puts | semmle.label | successor | +| raise.rb:110:12:110:25 | [ensure: return, ensure(1): raise] String | raise.rb:110:7:110:10 | [ensure: return, ensure(1): raise] puts | semmle.label | successor | +| raise.rb:110:12:110:25 | [ensure: return] String | raise.rb:110:7:110:10 | [ensure: return] puts | semmle.label | successor | | raise.rb:113:3:113:6 | puts | raise.rb:113:3:113:15 | MethodCall | semmle.label | successor | -| raise.rb:113:3:113:15 | MethodCall | raise.rb:115:8:115:22 | String | semmle.label | successor | +| raise.rb:113:3:113:15 | MethodCall | raise.rb:114:1:118:5 | Ensure | semmle.label | successor | | raise.rb:113:8:113:15 | String | raise.rb:113:3:113:6 | puts | semmle.label | successor | +| raise.rb:114:1:118:5 | Ensure | raise.rb:115:8:115:22 | String | semmle.label | successor | +| raise.rb:114:1:118:5 | [ensure: raise] Ensure | raise.rb:115:8:115:22 | [ensure: raise] String | semmle.label | successor | +| raise.rb:114:1:118:5 | [ensure: return] Ensure | raise.rb:115:8:115:22 | [ensure: return] String | semmle.label | successor | +| raise.rb:115:3:115:6 | [ensure: raise] puts | raise.rb:115:3:115:22 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:115:3:115:6 | [ensure: return] puts | raise.rb:115:3:115:22 | [ensure: return] MethodCall | semmle.label | successor | | raise.rb:115:3:115:6 | puts | raise.rb:115:3:115:22 | MethodCall | semmle.label | successor | | raise.rb:115:3:115:22 | MethodCall | raise.rb:116:3:118:5 | If | semmle.label | successor | +| raise.rb:115:3:115:22 | [ensure: raise] MethodCall | raise.rb:116:3:118:5 | [ensure: raise] If | semmle.label | successor | +| raise.rb:115:3:115:22 | [ensure: return] MethodCall | raise.rb:116:3:118:5 | [ensure: return] If | semmle.label | successor | | raise.rb:115:8:115:22 | String | raise.rb:115:3:115:6 | puts | semmle.label | successor | +| raise.rb:115:8:115:22 | [ensure: raise] String | raise.rb:115:3:115:6 | [ensure: raise] puts | semmle.label | successor | +| raise.rb:115:8:115:22 | [ensure: return] String | raise.rb:115:3:115:6 | [ensure: return] puts | semmle.label | successor | | raise.rb:116:3:118:5 | If | raise.rb:116:6:116:7 | b2 | semmle.label | successor | +| raise.rb:116:3:118:5 | [ensure: raise] If | raise.rb:116:6:116:7 | [ensure: raise] b2 | semmle.label | successor | +| raise.rb:116:3:118:5 | [ensure: return] If | raise.rb:116:6:116:7 | [ensure: return] b2 | semmle.label | successor | +| raise.rb:116:6:116:7 | [ensure: raise] b2 | raise.rb:94:1:119:3 | exit m9 (abnormal) | semmle.label | raise | +| raise.rb:116:6:116:7 | [ensure: raise] b2 | raise.rb:117:11:117:22 | [ensure: raise] String | semmle.label | true | +| raise.rb:116:6:116:7 | [ensure: return] b2 | raise.rb:94:1:119:3 | exit m9 (normal) | semmle.label | return | +| raise.rb:116:6:116:7 | [ensure: return] b2 | raise.rb:117:11:117:22 | [ensure: return] String | semmle.label | true | | raise.rb:116:6:116:7 | b2 | raise.rb:94:1:119:3 | exit m9 (normal) | semmle.label | false | | raise.rb:116:6:116:7 | b2 | raise.rb:117:11:117:22 | String | semmle.label | true | +| raise.rb:117:5:117:9 | [ensure: raise] raise | raise.rb:117:5:117:22 | [ensure: raise] MethodCall | semmle.label | successor | +| raise.rb:117:5:117:9 | [ensure: return] raise | raise.rb:117:5:117:22 | [ensure: return] MethodCall | semmle.label | successor | | raise.rb:117:5:117:9 | raise | raise.rb:117:5:117:22 | MethodCall | semmle.label | successor | | raise.rb:117:5:117:22 | MethodCall | raise.rb:94:1:119:3 | exit m9 (abnormal) | semmle.label | raise | +| raise.rb:117:5:117:22 | [ensure: raise] MethodCall | raise.rb:94:1:119:3 | exit m9 (abnormal) | semmle.label | raise | +| raise.rb:117:5:117:22 | [ensure: return] MethodCall | raise.rb:94:1:119:3 | exit m9 (abnormal) | semmle.label | raise | | raise.rb:117:11:117:22 | String | raise.rb:117:5:117:9 | raise | semmle.label | successor | +| raise.rb:117:11:117:22 | [ensure: raise] String | raise.rb:117:5:117:9 | [ensure: raise] raise | semmle.label | successor | +| raise.rb:117:11:117:22 | [ensure: return] String | raise.rb:117:5:117:9 | [ensure: return] raise | semmle.label | successor | | raise.rb:121:1:124:3 | enter m10 | raise.rb:121:20:121:30 | String | semmle.label | successor | | raise.rb:121:1:124:3 | exit m10 (abnormal) | raise.rb:121:1:124:3 | exit m10 | semmle.label | successor | | raise.rb:121:14:121:18 | raise | raise.rb:121:14:121:30 | MethodCall | semmle.label | successor |