Initial CFG skeleton code

This commit is contained in:
Tom Hvitved
2020-11-13 13:56:56 +01:00
parent f9c1bbd8f9
commit 4dd4373b53
19 changed files with 2791 additions and 2 deletions

View File

@@ -30,5 +30,5 @@ jobs:
- name: Build Extractor
run: env "PATH=$PATH:${{ github.workspace }}/codeql" ./create-extractor-pack.sh
- name: Run QL tests
run: codeql/codeql test run --search-path "${{ github.workspace }}" ql/test
run: codeql/codeql test run --check-databases --check-unused-labels --search-path "${{ github.workspace }}" --consistency-queries ql/consistency-queries ql/test

5
.gitignore vendored
View File

@@ -1,4 +1,7 @@
/target
extractor-pack
.vscode/launch.json
ql/src/.cache
.cache
ql/test/**/*.testproj
ql/test/**/*.actual
ql/test/**/CONSISTENCY

View File

@@ -0,0 +1 @@
import codeql_ruby.controlflow.internal.Consistency

View File

@@ -0,0 +1,5 @@
name: codeql-ruby-consistency-queries
version: 0.0.0
libraryPathDependencies:
- codeql-ruby
extractor: ruby

View File

@@ -0,0 +1,404 @@
/** Provides classes representing basic blocks. */
import codeql_ruby.ast
private import codeql_ruby.controlflow.ControlFlowGraph
private import internal.ControlFlowGraphImpl
private import SuccessorTypes
/**
* A basic block, that is, a maximal straight-line sequence of control flow nodes
* without branches or joins.
*/
class BasicBlock extends TBasicBlockStart {
/** Gets the scope of this basic block. */
CfgScope getScope() { result = this.getAPredecessor().getScope() }
/** Gets an immediate successor of this basic block, if any. */
BasicBlock getASuccessor() { result = this.getASuccessor(_) }
/** Gets an immediate successor of this basic block of a given type, if any. */
BasicBlock getASuccessor(SuccessorType t) {
result.getFirstNode() = this.getLastNode().getASuccessor(t)
}
/** Gets an immediate predecessor of this basic block, if any. */
BasicBlock getAPredecessor() { result.getASuccessor() = this }
/** Gets an immediate predecessor of this basic block of a given type, if any. */
BasicBlock getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
/** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
CfgNode getNode(int pos) { bbIndex(this.getFirstNode(), result, pos) }
/** Gets a control flow node in this basic block. */
CfgNode getANode() { result = this.getNode(_) }
/** Gets the first control flow node in this basic block. */
CfgNode getFirstNode() { this = TBasicBlockStart(result) }
/** Gets the last control flow node in this basic block. */
CfgNode getLastNode() { result = this.getNode(this.length() - 1) }
/** Gets the length of this basic block. */
int length() { result = strictcount(this.getANode()) }
/**
* Holds if this basic block immediately dominates basic block `bb`.
*
* That is, all paths reaching basic block `bb` from some entry point
* basic block must go through this basic block (which is an immediate
* predecessor of `bb`).
*
* Example:
*
* ```rb
* def m b
* if b
* return 0
* end
* return 1
* end
* ```
*
* The basic block starting on line 2 immediately dominates the
* basic block on line 5 (all paths from the entry point of `m`
* to `return 1` must go through the `if` block).
*/
predicate immediatelyDominates(BasicBlock bb) { bbIDominates(this, bb) }
/**
* Holds if this basic block strictly dominates basic block `bb`.
*
* That is, all paths reaching basic block `bb` from some entry point
* basic block must go through this basic block (which must be different
* from `bb`).
*
* Example:
*
* ```rb
* def m b
* if b
* return 0
* end
* return 1
* end
* ```
*
* The basic block starting on line 2 strictly dominates the
* basic block on line 5 (all paths from the entry point of `m`
* to `return 1` must go through the `if` block).
*/
predicate strictlyDominates(BasicBlock bb) { bbIDominates+(this, bb) }
/**
* Holds if this basic block dominates basic block `bb`.
*
* That is, all paths reaching basic block `bb` from some entry point
* basic block must go through this basic block.
*
* Example:
*
* ```rb
* def m b
* if b
* return 0
* end
* return 1
* end
* ```
*
* The basic block starting on line 2 dominates the basic
* basic block on line 5 (all paths from the entry point of `m`
* to `return 1` must go through the `if` block).
*/
predicate dominates(BasicBlock bb) {
bb = this or
this.strictlyDominates(bb)
}
/**
* Holds if `df` is in the dominance frontier of this basic block.
* That is, this basic block dominates a predecessor of `df`, but
* does not dominate `df` itself.
*
* Example:
*
* ```rb
* def m x
* if x < 0
* x = -x
* if x > 10
* x = x - 1
* end
* end
* puts x
* end
* ```
*
* The basic block on line 8 is in the dominance frontier
* of the basic block starting on line 3 because that block
* dominates the basic block on line 4, which is a predecessor of
* `puts x`. Also, the basic block starting on line 3 does not
* dominate the basic block on line 8.
*/
predicate inDominanceFrontier(BasicBlock df) {
this.dominatesPredecessor(df) and
not strictlyDominates(df)
}
/**
* Holds if this basic block dominates a predecessor of `df`.
*/
private predicate dominatesPredecessor(BasicBlock df) { this.dominates(df.getAPredecessor()) }
/**
* Gets the basic block that immediately dominates this basic block, if any.
*
* That is, all paths reaching this basic block from some entry point
* basic block must go through the result, which is an immediate basic block
* predecessor of this basic block.
*
* Example:
*
* ```rb
* def m b
* if b
* return 0
* end
* return 1
* end
* ```
*
* The basic block starting on line 2 is an immediate dominator of
* the basic block on line 5 (all paths from the entry point of `m`
* to `return 1` must go through the `if` block, and the `if` block
* is an immediate predecessor of `return 1`).
*/
BasicBlock getImmediateDominator() { bbIDominates(result, this) }
/**
* Holds if this basic block strictly post-dominates basic block `bb`.
*
* That is, all paths reaching a normal exit point basic block from basic
* block `bb` must go through this basic block (which must be different
* from `bb`).
*
* Example:
*
* ```rb
* def m b
* if b
* puts "b"
* end
* puts "m"
* end
* ```
*
* The basic block on line 5 strictly post-dominates the basic block on
* line 3 (all paths to the exit point of `m` from `puts "b"` must go
* through `puts "m"`).
*/
predicate strictlyPostDominates(BasicBlock bb) { bbIPostDominates+(this, bb) }
/**
* Holds if this basic block post-dominates basic block `bb`.
*
* That is, all paths reaching a normal exit point basic block from basic
* block `bb` must go through this basic block.
*
* Example:
*
* ```rb
* def m b
* if b
* puts "b"
* end
* puts "m"
* end
* ```
*
* The basic block on line 5 post-dominates the basic block on line 3
* (all paths to the exit point of `m` from `puts "b"` must go through
* `puts "m"`).
*/
predicate postDominates(BasicBlock bb) {
this.strictlyPostDominates(bb) or
this = bb
}
/** Holds if this basic block is in a loop in the control flow graph. */
predicate inLoop() { this.getASuccessor+() = this }
/** Gets a textual representation of this basic block. */
string toString() { result = this.getFirstNode().toString() }
/** Gets the location of this basic block. */
Location getLocation() { result = this.getFirstNode().getLocation() }
}
cached
private module Cached {
/** Internal representation of basic blocks. */
cached
newtype TBasicBlock = TBasicBlockStart(CfgNode cfn) { startsBB(cfn) }
/** Holds if `cfn` starts a new basic block. */
private predicate startsBB(CfgNode cfn) {
not exists(cfn.getAPredecessor()) and exists(cfn.getASuccessor())
or
cfn.isJoin()
or
cfn.getAPredecessor().isBranch()
}
/**
* Holds if `succ` is a control flow successor of `pred` within
* the same basic block.
*/
private predicate intraBBSucc(CfgNode pred, CfgNode succ) {
succ = pred.getASuccessor() and
not startsBB(succ)
}
/**
* Holds if `cfn` is the `i`th node in basic block `bb`.
*
* In other words, `i` is the shortest distance from a node `bb`
* that starts a basic block to `cfn` along the `intraBBSucc` relation.
*/
cached
predicate bbIndex(CfgNode bbStart, CfgNode cfn, int i) =
shortestDistances(startsBB/1, intraBBSucc/2)(bbStart, cfn, i)
/**
* Holds if the first node of basic block `succ` is a control flow
* successor of the last node of basic block `pred`.
*/
private predicate succBB(BasicBlock pred, BasicBlock succ) { succ = pred.getASuccessor() }
/** Holds if `dom` is an immediate dominator of `bb`. */
cached
predicate bbIDominates(BasicBlock dom, BasicBlock bb) =
idominance(entryBB/1, succBB/2)(_, dom, bb)
/** Holds if `pred` is a basic block predecessor of `succ`. */
private predicate predBB(BasicBlock succ, BasicBlock pred) { succBB(pred, succ) }
/** Holds if `bb` is an exit basic block that represents normal exit. */
private predicate normalExitBB(BasicBlock bb) {
bb.getANode().(CfgNodes::AnnotatedExitNode).isNormal()
}
/** Holds if `dom` is an immediate post-dominator of `bb`. */
cached
predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) =
idominance(normalExitBB/1, predBB/2)(_, dom, bb)
/**
* Gets the `i`th predecessor of join block `jb`, with respect to some
* arbitrary order.
*/
cached
JoinBlockPredecessor getJoinBlockPredecessor(JoinBlock jb, int i) {
result =
rank[i + 1](JoinBlockPredecessor jbp |
jbp = jb.getAPredecessor()
|
jbp order by JoinBlockPredecessors::getId(jbp), JoinBlockPredecessors::getSplitString(jbp)
)
}
}
private import Cached
/** Holds if `bb` is an entry basic block. */
private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof CfgNodes::EntryNode }
/**
* An entry basic block, that is, a basic block whose first node is
* an entry node.
*/
class EntryBasicBlock extends BasicBlock {
EntryBasicBlock() { entryBB(this) }
override CfgScope getScope() { this.getFirstNode() = TEntryNode(result) }
}
/**
* An annotated exit basic block, that is, a basic block whose last node is
* an annotated exit node.
*/
class AnnotatedExitBasicBlock extends BasicBlock {
AnnotatedExitBasicBlock() { this.getANode() instanceof CfgNodes::AnnotatedExitNode }
}
/**
* An exit basic block, that is, a basic block whose last node is
* an exit node.
*/
class ExitBasicBlock extends BasicBlock {
ExitBasicBlock() { this.getLastNode() instanceof CfgNodes::ExitNode }
}
private module JoinBlockPredecessors {
private import CfgNodes
private predicate id(AstNode x, AstNode y) { x = y }
private predicate idOf(AstNode x, int y) = equivalenceRelation(id/2)(x, y)
int getId(JoinBlockPredecessor jbp) {
idOf(jbp.getFirstNode().(AstCfgNode).getNode(), result)
or
idOf(jbp.(EntryBasicBlock).getScope(), result)
}
string getSplitString(JoinBlockPredecessor jbp) {
result = jbp.getFirstNode().(AstCfgNode).getSplitsString()
or
not exists(jbp.getFirstNode().(AstCfgNode).getSplitsString()) and
result = ""
}
}
/** A basic block with more than one predecessor. */
class JoinBlock extends BasicBlock {
JoinBlock() { getFirstNode().isJoin() }
/**
* Gets the `i`th predecessor of this join block, with respect to some
* arbitrary order.
*/
JoinBlockPredecessor getJoinBlockPredecessor(int i) { result = getJoinBlockPredecessor(this, i) }
}
/** A basic block that is an immediate predecessor of a join block. */
class JoinBlockPredecessor extends BasicBlock {
JoinBlockPredecessor() { this.getASuccessor() instanceof JoinBlock }
}
/** A basic block that terminates in a condition, splitting the subsequent control flow. */
class ConditionBlock extends BasicBlock {
ConditionBlock() { this.getLastNode().isCondition() }
/**
* Holds if basic block `succ` is immediately controlled by this basic
* block with conditional value `s`. That is, `succ` is an immediate
* successor of this block, and `succ` can only be reached from
* the callable entry point by going via the `s` edge out of this basic block.
*/
pragma[nomagic]
predicate immediatelyControls(BasicBlock succ, BooleanSuccessor s) {
succ = this.getASuccessor(s) and
forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != this | succ.dominates(pred))
}
/**
* Holds if basic block `controlled` is controlled by this basic block with
* conditional value `s`. That is, `controlled` can only be reached from
* the callable entry point by going via the `s` edge out of this basic block.
*/
predicate controls(BasicBlock controlled, BooleanSuccessor s) {
exists(BasicBlock succ | this.immediatelyControls(succ, s) | succ.dominates(controlled))
}
}

View File

@@ -0,0 +1,309 @@
/** Provides classes representing the control flow graph. */
import codeql_ruby.ast
private import codeql_ruby.controlflow.BasicBlocks
private import SuccessorTypes
private import internal.ControlFlowGraphImpl
private import internal.Splitting
private import internal.Completion
/** An AST node with an associated control-flow graph. */
class CfgScope = Method; // TODO: add more cases (e.g. bodies of lambdas/do blocks)
/**
* A control flow node.
*
* A control flow node is a node in the control flow graph (CFG). There is a
* many-to-one relationship between CFG nodes and AST nodes.
*
* Only nodes that can be reached from an entry point are included in the CFG.
*/
class CfgNode extends TCfgNode {
/** Gets a textual representation of this control flow node. */
string toString() { none() }
/** Gets the AST node that this node corresponds to, if any. */
AstNode getNode() { none() }
/** Gets the location of this control flow node. */
Location getLocation() { result = this.getNode().getLocation() }
/** Holds if this control flow node has conditional successors. */
final predicate isCondition() { exists(this.getASuccessor(any(BooleanSuccessor bs))) }
/** Gets the scope of this node. */
final CfgScope getScope() { result = this.getBasicBlock().getScope() }
/** Gets the basic block that this control flow node belongs to. */
BasicBlock getBasicBlock() { result.getANode() = this }
/** Gets a successor node of a given type, if any. */
final CfgNode getASuccessor(SuccessorType t) { result = getASuccessor(this, t) }
/** Gets an immediate successor, if any. */
final CfgNode getASuccessor() { result = this.getASuccessor(_) }
/** Gets an immediate predecessor node of a given flow type, if any. */
final CfgNode getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
/** Gets an immediate predecessor, if any. */
final CfgNode getAPredecessor() { result = this.getAPredecessor(_) }
/** Holds if this node has more than one predecessor. */
final predicate isJoin() { strictcount(this.getAPredecessor()) > 1 }
/** Holds if this node has more than one successor. */
final predicate isBranch() { strictcount(this.getASuccessor()) > 1 }
}
/** Provides different types of control flow nodes. */
module CfgNodes {
/** An entry node for a given scope. */
class EntryNode extends CfgNode, TEntryNode {
private CfgScope scope;
EntryNode() { this = TEntryNode(scope) }
final override EntryBasicBlock getBasicBlock() { result = CfgNode.super.getBasicBlock() }
final override Location getLocation() { result = scope.getLocation() }
final override string toString() { result = "enter " + scope.getName() }
}
/** An exit node for a given scope, annotated with the type of exit. */
class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode {
private CfgScope scope;
private boolean normal;
AnnotatedExitNode() { this = TAnnotatedExitNode(scope, normal) }
/** Holds if this node represent a normal exit. */
final predicate isNormal() { normal = true }
final override AnnotatedExitBasicBlock getBasicBlock() {
result = CfgNode.super.getBasicBlock()
}
final override Location getLocation() { result = scope.getLocation() }
final override string toString() {
exists(string s |
normal = true and s = "normal"
or
normal = false and s = "abnormal"
|
result = "exit " + scope.getName() + " (" + s + ")"
)
}
}
/** An exit node for a given scope. */
class ExitNode extends CfgNode, TExitNode {
private CfgScope scope;
ExitNode() { this = TExitNode(scope) }
final override Location getLocation() { result = scope.getLocation() }
final override string toString() { result = "exit " + scope.getName() }
}
/**
* A node for an AST node.
*
* Each AST node maps to zero or more `AstCfgNode`s: zero when the node in unreachable
* (dead) code or not important for control flow, and multiple when there are different
* splits for the AST node.
*/
class AstCfgNode extends CfgNode, TAstNode {
private Splits splits;
private AstNode n;
AstCfgNode() { this = TAstNode(n, splits) }
final override AstNode getNode() { result = n }
final override string toString() {
result = "[" + this.getSplitsString() + "] " + n.toString()
or
not exists(this.getSplitsString()) and result = n.toString()
}
/** Gets a comma-separated list of strings for each split in this node, if any. */
final string getSplitsString() {
result = splits.toString() and
result != ""
}
/** Gets a split for this control flow node, if any. */
final Split getASplit() { result = splits.getASplit() }
}
}
/** The type of a control flow successor. */
class SuccessorType extends TSuccessorType {
/** Gets a textual representation of successor type. */
string toString() { none() }
}
/** Provides different types of control flow successor types. */
module SuccessorTypes {
/** A normal control flow successor. */
class NormalSuccessor extends SuccessorType, TSuccessorSuccessor {
final override string toString() { result = "successor" }
}
/**
* A Boolean control flow successor.
*
* For example, in
*
* ```rb
* if x >= 0
* puts "positive"
* else
* puts "negative"
* end
* ```
*
* `x >= 0` has both a `true` successor and a `false` successor.
*/
class BooleanSuccessor extends SuccessorType, TBooleanSuccessor {
/** Gets the Boolean value. */
final boolean getValue() { this = TBooleanSuccessor(result) }
final override string toString() { result = getValue().toString() }
}
/**
* A `return` control flow successor.
*
* Example:
*
* ```rb
* def sum(x,y)
* return x + y
* end
* ```
*
* The exit node of `sum` is a `return` successor of the `return x + y`
* statement.
*/
class ReturnSuccessor extends SuccessorType, TReturnSuccessor {
final override string toString() { result = "return" }
}
/**
* A `break` control flow successor.
*
* Example:
*
* ```rb
* def m
* while x >= 0
* x -= 1
* if num > 100
* break
* end
* end
* puts "done"
* end
* ```
*
* The node `puts "done"` is `break` successor of the node `break`.
*/
class BreakSuccessor extends SuccessorType, TBreakSuccessor {
final override string toString() { result = "break" }
}
/**
* A `next` control flow successor.
*
* Example:
*
* ```rb
* def m
* while x >= 0
* x -= 1
* if num > 100
* next
* end
* end
* puts "done"
* end
* ```
*
* The node `x >= 0` is `next` successor of the node `next`.
*/
class NextSuccessor extends SuccessorType, TNextSuccessor {
final override string toString() { result = "next" }
}
/**
* A `redo` control flow successor.
*
* Example:
*
* Example:
*
* ```rb
* def m
* while x >= 0
* x -= 1
* if num > 100
* redo
* end
* end
* puts "done"
* end
* ```
*
* The node `x -= 1` is `redo` successor of the node `redo`.
*/
class RedoSuccessor extends SuccessorType, TRedoSuccessor {
final override string toString() { result = "redo" }
}
/**
* An exceptional control flow successor.
*
* Example:
*
* ```rb
* def m x
* if x > 2
* raise "x > 2"
* end
* puts "x <= 2"
* end
* ```
*
* The exit node of `m` is an exceptional successor of the node
* `raise "x > 2"`.
*/
class RaiseSuccessor extends SuccessorType, TRaiseSuccessor {
final override string toString() { result = "raise" }
}
/**
* An exit control flow successor.
*
* Example:
*
* ```rb
* def m x
* if x > 2
* exit 1
* end
* puts "x <= 2"
* end
* ```
*
* The exit node of `m` is an exit successor of the node
* `exit 1`.
*/
class ExitSuccessor extends SuccessorType, TExitSuccessor {
final override string toString() { result = "exit" }
}
}

View File

@@ -0,0 +1,73 @@
/**
* Provides various helper classes for AST nodes. The definitions in this file
* will likely be part of the hand-written user-facing AST layer.
*/
import codeql_ruby.ast
class LogicalNotAstNode extends Unary {
AstNode operand;
LogicalNotAstNode() {
this.getOperator().toString() in ["!", "not"] and
operand = this.getOperand()
}
}
class LogicalAndAstNode extends Binary {
AstNode left;
AstNode right;
LogicalAndAstNode() {
this.getOperator().toString() in ["&&", "and"] and
left = this.getLeft() and
right = this.getRight()
}
AstNode getAnOperand() { result in [left, right] }
}
class LogicalOrAstNode extends Binary {
AstNode left;
AstNode right;
LogicalOrAstNode() {
this.getOperator().toString() in ["||", "or"] and
left = this.getLeft() and
right = this.getRight()
}
AstNode getAnOperand() { result in [left, right] }
}
private class If_or_elisif = @if or @elsif;
class IfElsifAstNode extends AstNode, If_or_elisif {
AstNode getConditionNode() { none() }
AstNode getConsequenceNode() { none() }
AstNode getAlternativeNode() { none() }
}
private class IfAstNode extends IfElsifAstNode, If {
override AstNode getConditionNode() { result = this.getCondition() }
override AstNode getConsequenceNode() { result = this.getConsequence() }
override AstNode getAlternativeNode() { result = this.getAlternative() }
}
private class ElsifAstNode extends IfElsifAstNode, Elsif {
override AstNode getConditionNode() { result = this.getCondition() }
override AstNode getConsequenceNode() { result = this.getConsequence() }
override AstNode getAlternativeNode() { result = this.getAlternative() }
}
class ParenthesizedStatement extends ParenthesizedStatements {
ParenthesizedStatement() { strictcount(int i | exists(this.getChild(i))) = 1 }
AstNode getChild() { result = this.getChild(0) }
}

View File

@@ -0,0 +1,10 @@
import codeql_ruby.controlflow.ControlFlowGraph
query predicate nodes(CfgNode n) { any() }
query predicate edges(CfgNode pred, CfgNode succ, string attr, string val) {
exists(SuccessorType t | succ = pred.getASuccessor(t) |
attr = "semmle.label" and
val = t.toString()
)
}

View File

@@ -0,0 +1,282 @@
/**
* Provides classes representing control flow completions.
*
* A completion represents how a statement or expression terminates.
*/
import codeql_ruby.ast
private import codeql_ruby.controlflow.ControlFlowGraph
private import AstNodes
private import NonReturning
private import SuccessorTypes
private newtype TCompletion =
TSimpleCompletion() or
TBooleanCompletion(boolean b) { b = true or b = false } or
TReturnCompletion() or
TBreakCompletion() or
TNextCompletion() or
TRedoCompletion() or
TRaiseCompletion() or // TODO: Add exception type?
TExitCompletion() or
TNestedCompletion(Completion inner, Completion outer) {
outer = TSimpleCompletion() and
inner = TBreakCompletion()
}
pragma[noinline]
private predicate completionIsValidForStmt(AstNode n, Completion c) {
n instanceof Break and
c = TBreakCompletion()
or
n instanceof Next and
c = TNextCompletion()
or
n instanceof Redo and
c = TRedoCompletion()
or
n instanceof Return and
c = TReturnCompletion()
}
/** A completion of a statement or an expression. */
abstract class Completion extends TCompletion {
/** Holds if this completion is valid for node `n`. */
predicate isValidFor(AstNode n) {
this = n.(NonReturningCall).getACompletion()
or
completionIsValidForStmt(n, this)
or
mustHaveBooleanCompletion(n) and
(
exists(boolean value | isBooleanConstant(n, value) | this = TBooleanCompletion(value))
or
not isBooleanConstant(n, _) and
this = TBooleanCompletion(_)
)
or
not n instanceof NonReturningCall and
not completionIsValidForStmt(n, _) and
not mustHaveBooleanCompletion(n) and
this = TSimpleCompletion()
}
/**
* Holds if this completion will continue a loop when it is the completion
* of a loop body.
*/
predicate continuesLoop() {
this instanceof NormalCompletion or
this instanceof NextCompletion
}
/**
* Gets the inner completion. This is either the inner completion,
* when the completion is nested, or the completion itself.
*/
Completion getInnerCompletion() { result = this }
/**
* Gets the outer completion. This is either the outer completion,
* when the completion is nested, or the completion itself.
*/
Completion getOuterCompletion() { result = this }
/** Gets a successor type that matches this completion. */
abstract SuccessorType getAMatchingSuccessorType();
/** Gets a textual representation of this completion. */
abstract string toString();
}
/** Holds if node `n` has the Boolean constant value `value`. */
private predicate isBooleanConstant(AstNode n, boolean value) {
mustHaveBooleanCompletion(n) and
(
n.(Constant).getValue() = "true" and
value = true
or
n.(Constant).getValue() = "false" and
value = false
)
}
/**
* Holds if a normal completion of `n` must be a Boolean completion.
*/
private predicate mustHaveBooleanCompletion(AstNode n) {
inBooleanContext(n) and
not n instanceof NonReturningCall
}
/**
* Holds if `n` is used in a Boolean context. That is, the value
* that `n` evaluates to determines a true/false branch successor.
*/
private predicate inBooleanContext(AstNode n) {
n = any(IfElsifAstNode parent).getConditionNode()
or
n = any(While parent).getCondition()
or
exists(LogicalAndAstNode parent |
n = parent.getLeft()
or
inBooleanContext(parent) and
n = parent.getRight()
)
or
exists(LogicalOrAstNode parent |
n = parent.getLeft()
or
inBooleanContext(parent) and
n = parent.getRight()
)
or
n = any(LogicalNotAstNode parent | inBooleanContext(parent)).getOperand()
or
n = any(ParenthesizedStatement parent | inBooleanContext(parent)).getChild()
}
/**
* A completion that represents normal evaluation of a statement or an
* expression.
*/
abstract class NormalCompletion extends Completion { }
/** A simple (normal) completion. */
class SimpleCompletion extends NormalCompletion, TSimpleCompletion {
override NormalSuccessor getAMatchingSuccessorType() { any() }
override string toString() { result = "simple" }
}
/**
* A completion that represents evaluation of an expression
* with a Boolean value.
*/
class BooleanCompletion extends NormalCompletion, TBooleanCompletion {
private boolean value;
BooleanCompletion() { this = TBooleanCompletion(value) }
/** Gets the Boolean value of this completion. */
boolean getValue() { result = value }
/** Gets the dual Boolean completion. */
BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) }
override BooleanSuccessor getAMatchingSuccessorType() { result.getValue() = value }
override string toString() { result = value.toString() }
}
/** A Boolean `true` completion. */
class TrueCompletion extends BooleanCompletion {
TrueCompletion() { this.getValue() = true }
}
/** A Boolean `false` completion. */
class FalseCompletion extends BooleanCompletion {
FalseCompletion() { this.getValue() = false }
}
/**
* A completion that represents evaluation of a statement or an
* expression resulting in a return.
*/
class ReturnCompletion extends Completion, TReturnCompletion {
override ReturnSuccessor getAMatchingSuccessorType() { any() }
override string toString() { 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 {
override BreakSuccessor getAMatchingSuccessorType() { any() }
override string toString() { 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 {
override NextSuccessor getAMatchingSuccessorType() { any() }
override string toString() { 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 {
override RedoSuccessor getAMatchingSuccessorType() { any() }
override string toString() { result = "redo" }
}
/**
* A completion that represents evaluation of a statement or an
* expression resulting in a thrown exception.
*/
class RaiseCompletion extends Completion, TRaiseCompletion {
override RaiseSuccessor getAMatchingSuccessorType() { any() }
override string toString() { result = "raise" }
}
/**
* A completion that represents evaluation of a statement or an
* expression resulting in an abort/exit.
*/
class ExitCompletion extends Completion, TExitCompletion {
override ExitSuccessor getAMatchingSuccessorType() { any() }
override string toString() { result = "exit" }
}
/**
* A nested completion. For example, in
*
* ```rb
* def m
* while x >= 0
* x -= 1
* if num > 100
* break
* end
* end
* puts "done"
* end
* ```
*
* 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 {
Completion inner;
Completion outer;
NestedCompletion() { this = TNestedCompletion(inner, outer) }
override Completion getInnerCompletion() { result = inner }
override Completion getOuterCompletion() { result = outer }
override SuccessorType getAMatchingSuccessorType() {
inner = TBreakCompletion() and
outer = TSimpleCompletion() and
result instanceof BreakSuccessor
}
override string toString() { result = outer + " [" + inner + "]" }
}
private class NestedNormalCompletion extends NormalCompletion, NestedCompletion {
NestedNormalCompletion() { outer instanceof NormalCompletion }
}

View File

@@ -0,0 +1,53 @@
import codeql_ruby.ast
import codeql_ruby.controlflow.ControlFlowGraph
import Completion
import Splitting
query predicate nonUniqueSetRepresentation(Splits s1, Splits s2) {
forex(Split s | s = s1.getASplit() | s = s2.getASplit()) and
forex(Split s | s = s2.getASplit() | s = s1.getASplit()) and
s1 != s2
}
query predicate breakInvariant2(
AstNode pred, Splits predSplits, AstNode succ, Splits succSplits, SplitImpl split, Completion c
) {
succSplits(pred, predSplits, succ, succSplits, c) and
split = predSplits.getASplit() and
split.hasSuccessor(pred, succ, c) and
not split = succSplits.getASplit()
}
query predicate breakInvariant3(
AstNode pred, Splits predSplits, AstNode succ, Splits succSplits, SplitImpl split, Completion c
) {
succSplits(pred, predSplits, succ, succSplits, c) and
split = predSplits.getASplit() and
split.hasExit(pred, succ, c) and
not split.hasEntry(pred, succ, c) and
split = succSplits.getASplit()
}
query predicate breakInvariant4(
AstNode pred, Splits predSplits, AstNode succ, Splits succSplits, SplitImpl split, Completion c
) {
succSplits(pred, predSplits, succ, succSplits, c) and
split.hasEntry(pred, succ, c) and
not split.getKind() = predSplits.getASplit().getKind() and
not split = succSplits.getASplit()
}
query predicate breakInvariant5(
AstNode pred, Splits predSplits, AstNode succ, Splits succSplits, SplitImpl split, Completion c
) {
succSplits(pred, predSplits, succ, succSplits, c) and
split = succSplits.getASplit() and
not (split.hasSuccessor(pred, succ, c) and split = predSplits.getASplit()) and
not split.hasEntry(pred, succ, c)
}
query predicate multipleSuccessors(CfgNode node, SuccessorType t, CfgNode successor) {
not node instanceof CfgNodes::EntryNode and
strictcount(node.getASuccessor(t)) > 1 and
successor = node.getASuccessor(t)
}

View File

@@ -0,0 +1,550 @@
/**
* Provides auxiliary classes and predicates used to construct the basic successor
* relation on control flow elements.
*
* The implementation is centered around the concept of a _completion_, which
* models how the execution of a statement or expression terminates.
* Completions are represented as an algebraic data type `Completion` defined in
* `Completion.qll`.
*
* The CFG is built by structural recursion over the AST. To achieve this the
* CFG edges related to a given AST node, `n`, are divided into three categories:
*
* 1. The in-going edge that points to the first CFG node to execute when
* `n` is going to be executed.
* 2. The out-going edges for control flow leaving `n` that are going to some
* other node in the surrounding context of `n`.
* 3. The edges that have both of their end-points entirely within the AST
* node and its children.
*
* The edges in (1) and (2) are inherently non-local and are therefore
* initially calculated as half-edges, that is, the single node, `k`, of the
* edge contained within `n`, by the predicates `k = first(n)` and `k = last(n, _)`,
* respectively. The edges in (3) can then be enumerated directly by the predicate
* `succ` by calling `first` and `last` recursively on the children of `n` and
* connecting the end-points. This yields the entire CFG, since all edges are in
* (3) for _some_ AST node.
*
* The second parameter of `last` is the completion, which is necessary to distinguish
* the out-going edges from `n`. Note that the completion changes as the calculation of
* `last` proceeds outward through the AST; for example, a `BreakCompletion` is
* caught up by its surrounding loop and turned into a `NormalCompletion`.
*/
import codeql_ruby.ast
private import AstNodes
private import codeql_ruby.controlflow.ControlFlowGraph
private import Completion
private import SuccessorTypes
private import Splitting
abstract private class ControlFlowTree extends AstNode {
/**
* Holds if `first` is the first element executed within this AST node.
*/
pragma[nomagic]
abstract predicate first(AstNode first);
/**
* Holds if `last` with completion `c` is a potential last element executed
* within this AST node.
*/
pragma[nomagic]
abstract predicate last(AstNode last, Completion c);
/** Holds if abnormal execution of `child` should propagate upwards. */
abstract predicate propagatesAbnormal(AstNode child);
/**
* Holds if `succ` is a control flow successor for `pred`, given that `pred`
* finishes with completion `c`.
*/
pragma[nomagic]
abstract predicate succ(AstNode pred, AstNode succ, Completion c);
/**
* Holds if this node should be hidden in the CFG. That is, edges
* `pred -> this -> succ` are converted to a single edge `pred -> succ`.
*/
predicate isHidden() { none() }
}
/** Holds if `first` is the first element executed within AST node `n`. */
predicate first(ControlFlowTree n, AstNode first) { n.first(first) }
/**
* Holds if `last` with completion `c` is a potential last element executed
* within AST node `n`.
*/
predicate last(ControlFlowTree n, AstNode last, Completion c) {
n.last(last, c)
or
exists(AstNode child |
n.propagatesAbnormal(child) and
last(child, last, c) and
not c instanceof NormalCompletion
)
}
private predicate succImpl(AstNode pred, AstNode succ, Completion c) {
any(ControlFlowTree cft).succ(pred, succ, c)
}
private predicate isHidden(ControlFlowTree t) { t.isHidden() }
private predicate succImplIfHidden(AstNode pred, AstNode succ) {
isHidden(pred) and
succImpl(pred, succ, any(SimpleCompletion c))
}
/**
* Holds if `succ` is a control flow successor for `pred`, given that `pred`
* finishes with completion `c`.
*/
pragma[nomagic]
predicate succ(AstNode pred, AstNode succ, Completion c) {
exists(AstNode n |
succImpl(pred, n, c) and
succImplIfHidden*(n, succ) and
not isHidden(pred) and
not isHidden(succ)
)
}
/** Holds if `first` is first executed when entering `scope`. */
pragma[nomagic]
predicate succEntry(CfgScope scope, AstNode first) {
exists(AstNode n |
first(scope, n) and
succImplIfHidden*(n, first) and
not isHidden(first)
)
}
/** Holds if `last` with completion `c` can exit `scope`. */
pragma[nomagic]
predicate succExit(AstNode last, CfgScope scope, Completion c) {
exists(AstNode n |
last(scope, n, c) and
succImplIfHidden*(last, n) and
not isHidden(last)
)
}
/**
* An AST node where the children are evaluated following a standard left-to-right
* evaluation. The actual evaluation order is determined by the predicate
* `getChildNode()`.
*/
abstract private class StandardNode extends ControlFlowTree {
/** Gets the `i`th child node, in order of evaluation. */
abstract AstNode getChildNode(int i);
private AstNode getChildNodeRanked(int i) {
result = rank[i + 1](AstNode child, int j | child = this.getChildNode(j) | child order by j)
}
/** Gets the first child node of this element. */
final AstNode getFirstChildNode() { result = this.getChildNodeRanked(0) }
/** Gets the last child node of this node. */
final AstNode getLastChildNode() {
exists(int last |
last = max(int i | exists(this.getChildNodeRanked(i))) and
result = this.getChildNodeRanked(last)
)
}
/** Gets the `i`th child, which is not the last node. */
pragma[nomagic]
private AstNode getNonLastChildNode(int i) {
result = this.getChildNodeRanked(i) and
not result = this.getLastChildNode()
}
final override predicate propagatesAbnormal(AstNode child) { child = this.getChildNode(_) }
pragma[nomagic]
override predicate succ(AstNode pred, AstNode succ, Completion c) {
exists(int i |
last(this.getNonLastChildNode(i), pred, c) and
c instanceof NormalCompletion and
first(this.getChildNodeRanked(i + 1), succ)
)
}
}
abstract private class PreOrderTree extends ControlFlowTree {
final override predicate first(AstNode first) { first = this }
}
abstract private class StandardPreOrderTree extends StandardNode, PreOrderTree {
final override predicate last(AstNode last, Completion c) {
last(this.getLastChildNode(), last, c)
or
not exists(this.getLastChildNode()) and
c.isValidFor(this) and
last = this
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
StandardNode.super.succ(pred, succ, c)
or
pred = this and
first(this.getFirstChildNode(), succ) and
c instanceof SimpleCompletion
}
}
abstract private class PostOrderTree extends ControlFlowTree {
override predicate last(AstNode last, Completion c) {
last = this and
c.isValidFor(last)
}
}
abstract private class StandardPostOrderTree extends StandardNode, PostOrderTree {
final override predicate first(AstNode first) {
first(this.getFirstChildNode(), first)
or
not exists(this.getFirstChildNode()) and
first = this
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
StandardNode.super.succ(pred, succ, c)
or
last(this.getLastChildNode(), pred, c) and
succ = this and
c instanceof NormalCompletion
}
}
abstract private class LeafTree extends PreOrderTree, PostOrderTree {
final override predicate propagatesAbnormal(AstNode child) { none() }
final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() }
}
/** Defines the CFG by dispatch on the various AST types. */
private module Trees {
private class ArgumentListTree extends StandardPostOrderTree, ArgumentList {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
override predicate isHidden() { any() }
}
private class ArrayTree extends StandardPostOrderTree, Array {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
}
private class AssignmentTree extends StandardPostOrderTree, Assignment {
final override AstNode getChildNode(int i) {
result = this.getLeft() and i = 0
or
result = this.getRight() and i = 1
}
}
private class BinaryTree extends StandardPostOrderTree, Binary {
BinaryTree() { not this instanceof LogicalAndAstNode and not this instanceof LogicalOrAstNode }
final override AstNode getChildNode(int i) {
result = this.getLeft() and i = 0
or
result = this.getRight() and i = 1
}
}
private class BlockParameterTree extends LeafTree, BlockParameter { }
private class BlockParametersTree extends StandardPostOrderTree, BlockParameters {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
override predicate isHidden() { any() }
}
private class BreakTree extends StandardPostOrderTree, Break {
final override AstNode getChildNode(int i) { result = this.getChild() and i = 0 }
}
private class CallTree extends StandardPostOrderTree, Call {
final override AstNode getChildNode(int i) {
result = this.getReceiver() and i = 0
or
result = this.getMethod() and i = 1
}
}
private class DoTree extends StandardPreOrderTree, Do {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
override predicate isHidden() { any() }
}
private class ElseTree extends StandardPreOrderTree, Else {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
override predicate isHidden() { any() }
}
private class IdentifierTree extends LeafTree, Identifier { }
private class IfElsifTree extends PreOrderTree, IfElsifAstNode {
final override predicate propagatesAbnormal(AstNode child) { child = this.getConditionNode() }
final override predicate last(AstNode last, Completion c) {
last(this.getConditionNode(), last, c) and
c instanceof FalseCompletion and
not exists(this.getAlternativeNode())
or
last(this.getConsequenceNode(), last, c)
or
last(this.getAlternativeNode(), last, c)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
pred = this and
first(this.getConditionNode(), succ) and
c instanceof SimpleCompletion
or
last(this.getConditionNode(), pred, c) and
(
c instanceof TrueCompletion and first(this.getConsequenceNode(), succ)
or
c instanceof FalseCompletion and first(this.getAlternativeNode(), succ)
)
}
}
private class IntegerTree extends LeafTree, Integer { }
class LogicalAndTree extends PostOrderTree, LogicalAndAstNode {
final override predicate propagatesAbnormal(AstNode child) { child in [left, right] }
final override predicate first(AstNode first) { first(left, first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
last(left, pred, c) and
c instanceof TrueCompletion and
first(right, succ)
or
last(left, pred, c) and
c instanceof FalseCompletion and
succ = this
or
last(right, pred, c) and
c instanceof NormalCompletion and
succ = this
}
}
class LogicalOrTree extends PostOrderTree, LogicalOrAstNode {
final override predicate propagatesAbnormal(AstNode child) { child in [left, right] }
final override predicate first(AstNode first) { first(left, first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
last(left, pred, c) and
c instanceof FalseCompletion and
first(right, succ)
or
last(left, pred, c) and
c instanceof TrueCompletion and
succ = this
or
last(right, pred, c) and
c instanceof NormalCompletion and
succ = this
}
}
class LogicalNotTree extends PostOrderTree, LogicalNotAstNode {
final override predicate propagatesAbnormal(AstNode child) { child = operand }
final override predicate first(AstNode first) { first(operand, first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
succ = this and
(
last(operand, pred, c.(BooleanCompletion).getDual())
or
last(operand, pred, c) and
c instanceof SimpleCompletion
)
}
}
private class MethodTree extends StandardPreOrderTree, Method {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
override predicate isHidden() { any() }
}
private class MethodCallTree extends StandardPostOrderTree, MethodCall {
final override AstNode getChildNode(int i) {
result = this.getArguments() and i = 0
or
result = this.getMethod() and i = 1
or
result = this.getBlock() and i = 2
}
}
private class NextTree extends StandardPostOrderTree, Next {
final override AstNode getChildNode(int i) { result = this.getChild() and i = 0 }
}
private class OperatorAssignmentTree extends StandardPostOrderTree, OperatorAssignment {
final override AstNode getChildNode(int i) {
result = this.getLeft() and i = 0
or
result = this.getRight() and i = 1
}
}
private class ParenthesizedStatementsTree extends StandardPostOrderTree, ParenthesizedStatements {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
}
private class RedoTree extends StandardPostOrderTree, Redo {
final override AstNode getChildNode(int i) { result = this.getChild() and i = 0 }
}
private class ReturnTree extends StandardPostOrderTree, Return {
final override AstNode getChildNode(int i) { result = this.getChild() and i = 0 }
}
private class StringTree extends LeafTree, String { }
private class ThenTree extends StandardPreOrderTree, Then {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
override predicate isHidden() { any() }
}
private class UnaryTree extends StandardPostOrderTree, Unary {
UnaryTree() { not this instanceof LogicalNotAstNode }
final override AstNode getChildNode(int i) { result = this.getOperand() and i = 0 }
}
private class WhileTree extends PreOrderTree, While {
final override predicate propagatesAbnormal(AstNode child) { child = this.getCondition() }
final override predicate last(AstNode last, Completion c) {
last(this.getCondition(), last, c) and
c instanceof FalseCompletion
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
)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
pred = this and
first(this.getCondition(), succ) and
c instanceof SimpleCompletion
or
last(this.getCondition(), pred, c) and
c instanceof TrueCompletion and
first(this.getBody(), succ)
or
last(this.getBody(), pred, c) and
first(this.getCondition(), succ) and
c.continuesLoop()
or
last(this.getBody(), pred, any(RedoCompletion rc)) and
first(this.getBody(), succ) and
c instanceof SimpleCompletion
}
}
}
cached
private module Cached {
private predicate isAbnormalExitType(SuccessorType t) {
t instanceof RaiseSuccessor or t instanceof ExitSuccessor
}
cached
newtype TCfgNode =
TEntryNode(CfgScope scope) { succEntrySplits(scope, _, _, _) } or
TAnnotatedExitNode(CfgScope scope, boolean normal) {
exists(Reachability::SameSplitsBlock b, SuccessorType t | b.isReachable(_) |
succExitSplits(b.getANode(), _, scope, t) and
if isAbnormalExitType(t) then normal = false else normal = true
)
} or
TExitNode(CfgScope scope) {
exists(Reachability::SameSplitsBlock b | b.isReachable(_) |
succExitSplits(b.getANode(), _, scope, _)
)
} or
TAstNode(AstNode n, Splits splits) {
exists(Reachability::SameSplitsBlock b | b.isReachable(splits) | n = b.getANode())
}
cached
newtype TSuccessorType =
TSuccessorSuccessor() or
TBooleanSuccessor(boolean b) { b = true or b = false } or
TReturnSuccessor() or
TBreakSuccessor() or
TNextSuccessor() or
TRedoSuccessor() or
TRaiseSuccessor() or // TODO: Add exception type?
TExitSuccessor()
/** Gets a successor node of a given flow type, if any. */
cached
CfgNode getASuccessor(CfgNode pred, SuccessorType t) {
exists(CfgScope scope, AstNode succElement, Splits succSplits |
pred = TEntryNode(scope) and
succEntrySplits(scope, succElement, succSplits, t) and
result = TAstNode(succElement, succSplits)
)
or
exists(AstNode predNode, Splits predSplits | pred = TAstNode(predNode, predSplits) |
exists(CfgScope scope, boolean normal |
succExitSplits(predNode, predSplits, scope, t) and
(if isAbnormalExitType(t) then normal = false else normal = true) and
result = TAnnotatedExitNode(scope, normal)
)
or
exists(AstNode succElement, Splits succSplits, Completion c |
succSplits(predNode, predSplits, succElement, succSplits, c) and
t = c.getAMatchingSuccessorType() and
result = TAstNode(succElement, succSplits)
)
)
or
exists(CfgScope scope |
pred = TAnnotatedExitNode(scope, _) and
t instanceof SuccessorTypes::NormalSuccessor and
result = TExitNode(scope)
)
}
/** Gets a first control flow element executed within `n`. */
cached
AstNode getAControlFlowEntryNode(AstNode n) { first(n, result) }
/** Gets a potential last control flow element executed within `n`. */
cached
AstNode getAControlFlowExitNode(AstNode n) { last(n, result, _) }
}
import Cached
/** An AST node that is split into multiple control flow nodes. */
class SplitAstNode extends AstNode {
SplitAstNode() { strictcount(CfgNode n | n.getNode() = this) > 1 }
}

View File

@@ -0,0 +1,22 @@
/** Provides a simple analysis for identifying calls that will not return. */
import codeql_ruby.ast
private import Completion
/** A call that definitely does not return (conservative analysis). */
abstract class NonReturningCall extends AstNode {
/** Gets a valid completion for this non-returning call. */
abstract Completion getACompletion();
}
private class RaiseCall extends NonReturningCall, MethodCall {
RaiseCall() { this.getMethod().toString() = "raise" }
override RaiseCompletion getACompletion() { any() }
}
private class ExitCall extends NonReturningCall, MethodCall {
ExitCall() { this.getMethod().toString() in ["abort", "exit"] }
override ExitCompletion getACompletion() { any() }
}

View File

@@ -0,0 +1,690 @@
/**
* Provides classes and predicates relevant for splitting the control flow graph.
*/
import codeql_ruby.ast
private import AstNodes
private import Completion
private import ControlFlowGraphImpl
private import SuccessorTypes
private import codeql_ruby.controlflow.ControlFlowGraph
/** The maximum number of splits allowed for a given node. */
private int maxSplits() { result = 5 }
cached
private module Cached {
cached
newtype TSplitKind = TConditionalCompletionSplitKind()
cached
newtype TSplit = TConditionalCompletionSplit(BooleanCompletion c)
cached
newtype TSplits =
TSplitsNil() or
TSplitsCons(SplitImpl head, Splits tail) {
exists(AstNode pred, Splits predSplits, AstNode succ, Completion c, int rnk |
case2aFromRank(pred, predSplits, succ, tail, c, rnk + 1) and
head = case2aSomeAtRank(pred, predSplits, succ, c, rnk)
)
or
succEntrySplitsCons(_, _, head, tail, _)
}
cached
string splitsToString(Splits splits) {
splits = TSplitsNil() and
result = ""
or
exists(SplitImpl head, Splits tail, string headString, string tailString |
splits = TSplitsCons(head, tail)
|
headString = head.toString() and
tailString = tail.toString() and
if tailString = ""
then result = headString
else
if headString = ""
then result = tailString
else result = headString + ", " + tailString
)
}
}
private import Cached
/** A split for a control flow node. */
class Split extends TSplit {
/** Gets a textual representation of this split. */
string toString() { none() }
}
private CfgScope getScope(AstNode n) { result.getAFieldOrChild*() = n }
/**
* Holds if split kinds `sk1` and `sk2` may overlap. That is, they may apply
* to at least one common AST node inside `scope`.
*/
private predicate overlapping(CfgScope scope, SplitKind sk1, SplitKind sk2) {
exists(AstNode n |
sk1.appliesTo(n) and
sk2.appliesTo(n) and
scope = getScope(n)
)
}
/**
* A split kind. Each control flow node can have at most one split of a
* given kind.
*/
abstract private class SplitKind extends TSplitKind {
/** Gets a split of this kind. */
SplitImpl getASplit() { result.getKind() = this }
/** Holds if some split of this kind applies to AST node `n`. */
predicate appliesTo(AstNode n) { this.getASplit().appliesTo(n) }
/**
* Gets a unique integer representing this split kind. The integer is used
* to represent sets of splits as ordered lists.
*/
abstract int getListOrder();
/** Gets the rank of this split kind among all overlapping kinds for `c`. */
private int getRank(CfgScope scope) {
this = rank[result](SplitKind sk | overlapping(scope, this, sk) | sk order by sk.getListOrder())
}
/**
* Holds if this split kind is enabled for AST node `n`. For performance reasons,
* the number of splits is restricted by the `maxSplits()` predicate.
*/
predicate isEnabled(AstNode n) {
this.appliesTo(n) and
this.getRank(getScope(n)) <= maxSplits()
}
/**
* Gets the rank of this split kind among all the split kinds that apply to
* AST node `n`. The rank is based on the order defined by `getListOrder()`.
*/
int getListRank(AstNode n) {
this.isEnabled(n) and
this = rank[result](SplitKind sk | sk.appliesTo(n) | sk order by sk.getListOrder())
}
/** Gets a textual representation of this split kind. */
abstract string toString();
}
// This class only exists to not pollute the externally visible `Split` class
// with internal helper predicates
abstract class SplitImpl extends Split {
/** Gets the kind of this split. */
abstract SplitKind getKind();
/**
* Holds if this split is entered when control passes from `pred` to `succ` with
* completion `c`.
*
* Invariant: `hasEntry(pred, succ, c) implies succ(pred, succ, c)`.
*/
abstract predicate hasEntry(AstNode pred, AstNode succ, Completion c);
/**
* Holds if this split is entered when control passes from `scope` to the entry point
* `first`.
*
* Invariant: `hasEntryScope(scope, first) implies succEntry(scope, first)`.
*/
abstract predicate hasEntryScope(CfgScope scope, AstNode first);
/**
* Holds if this split is left when control passes from `pred` to `succ` with
* completion `c`.
*
* Invariant: `hasExit(pred, succ, c) implies succ(pred, succ, c)`.
*/
abstract predicate hasExit(AstNode pred, AstNode succ, Completion c);
/**
* Holds if this split is left when control passes from `last` out of the enclosing
* scope `scope` with completion `c`.
*
* Invariant: `hasExitScope(last, scope, c) implies succExit(last, scope, c)`
*/
abstract predicate hasExitScope(AstNode last, CfgScope scope, Completion c);
/**
* Holds if this split is maintained when control passes from `pred` to `succ` with
* completion `c`.
*
* Invariant: `hasSuccessor(pred, succ, c) implies succ(pred, succ, c)`
*/
abstract predicate hasSuccessor(AstNode pred, AstNode succ, Completion c);
/** Holds if this split applies to AST node `n`. */
final predicate appliesTo(AstNode n) {
this.hasEntry(_, n, _)
or
this.hasEntryScope(_, n)
or
exists(AstNode pred | this.appliesTo(pred) | this.hasSuccessor(pred, n, _))
}
}
private module ConditionalCompletionSplitting {
/**
* A split for conditional completions. For example, in
*
* ```rb
* def method x
* if x < 2 and x > 0
* puts "x is 1"
* end
* end
* ```
*
* we record whether `x < 2` and `x > 0` evaluate to `true` or `false`, and
* restrict the edges out of `x < 2 and x > 0` accordingly.
*/
class ConditionalCompletionSplit extends Split, TConditionalCompletionSplit {
BooleanCompletion completion;
ConditionalCompletionSplit() { this = TConditionalCompletionSplit(completion) }
override string toString() { result = completion.toString() }
}
private class ConditionalCompletionSplitKind extends SplitKind, TConditionalCompletionSplitKind {
override int getListOrder() { result = 0 }
override predicate isEnabled(AstNode n) { this.appliesTo(n) }
override string toString() { result = "ConditionalCompletion" }
}
int getNextListOrder() { result = 1 }
private class ConditionalCompletionSplitImpl extends SplitImpl, ConditionalCompletionSplit {
override ConditionalCompletionSplitKind getKind() { any() }
override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
succ(pred, succ, c) and
last(succ, _, completion) and
(
last(succ.(LogicalNotAstNode).getOperand(), pred, c) and
completion.(BooleanCompletion).getDual() = c
or
last(succ.(LogicalAndAstNode).getAnOperand(), pred, c) and
completion = c
or
last(succ.(LogicalOrAstNode).getAnOperand(), pred, c) and
completion = c
or
last(succ.(ParenthesizedStatement).getChild(), pred, c) and
completion = c
)
}
override predicate hasEntryScope(CfgScope scope, AstNode succ) { none() }
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()
}
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()
}
override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) { none() }
}
}
/**
* A set of control flow node splits. The set is represented by a list of splits,
* ordered by ascending rank.
*/
class Splits extends TSplits {
/** Gets a textual representation of this set of splits. */
string toString() { result = splitsToString(this) }
/** Gets a split belonging to this set of splits. */
SplitImpl getASplit() {
exists(SplitImpl head, Splits tail | this = TSplitsCons(head, tail) |
result = head
or
result = tail.getASplit()
)
}
}
private predicate succEntrySplitsFromRank(CfgScope pred, AstNode succ, Splits splits, int rnk) {
splits = TSplitsNil() and
succEntry(pred, succ) and
rnk = 0
or
exists(SplitImpl head, Splits tail | succEntrySplitsCons(pred, succ, head, tail, rnk) |
splits = TSplitsCons(head, tail)
)
}
private predicate succEntrySplitsCons(
CfgScope pred, AstNode succ, SplitImpl head, Splits tail, int rnk
) {
succEntrySplitsFromRank(pred, succ, tail, rnk - 1) and
head.hasEntryScope(pred, succ) and
rnk = head.getKind().getListRank(succ)
}
/**
* Holds if `succ` with splits `succSplits` is the first element that is executed
* when entering scope `pred`.
*/
pragma[noinline]
predicate succEntrySplits(CfgScope pred, AstNode succ, Splits succSplits, SuccessorType t) {
exists(int rnk |
succEntry(pred, succ) and
t instanceof NormalSuccessor and
succEntrySplitsFromRank(pred, succ, succSplits, rnk)
|
rnk = 0 and
not any(SplitImpl split).hasEntryScope(pred, succ)
or
rnk = max(SplitImpl split | split.hasEntryScope(pred, succ) | split.getKind().getListRank(succ))
)
}
/**
* Holds if `last` with splits `predSplits` can exit the enclosing scope
* `scope` with type `t`.
*/
predicate succExitSplits(AstNode last, Splits predSplits, CfgScope scope, SuccessorType t) {
exists(Reachability::SameSplitsBlock b, Completion c | last = b.getANode() |
b.isReachable(predSplits) and
t = c.getAMatchingSuccessorType() and
succExit(last, scope, c) and
forall(SplitImpl predSplit | predSplit = predSplits.getASplit() |
predSplit.hasExitScope(last, scope, c)
)
)
}
/**
* Provides a predicate for the successor relation with split information,
* as well as logic used to construct the type `TSplits` representing sets
* of splits. Only sets of splits that can be reached are constructed, hence
* the predicates are mutually recursive.
*
* For the successor relation
*
* ```ql
* succSplits(AstNode pred, Splits predSplits, AstNode succ, Splits succSplits, Completion c)
* ```
*
* the following invariants are maintained:
*
* 1. `pred` is reachable with split set `predSplits`.
* 2. For all `split` in `predSplits`:
* - If `split.hasSuccessor(pred, succ, c)` then `split` in `succSplits`.
* 3. For all `split` in `predSplits`:
* - If `split.hasExit(pred, succ, c)` and not `split.hasEntry(pred, succ, c)` then
* `split` not in `succSplits`.
* 4. For all `split` with kind not in `predSplits`:
* - If `split.hasEntry(pred, succ, c)` then `split` in `succSplits`.
* 5. For all `split` in `succSplits`:
* - `split.hasSuccessor(pred, succ, c)` and `split` in `predSplits`, or
* - `split.hasEntry(pred, succ, c)`.
*
* The algorithm divides into four cases:
*
* 1. The set of splits for the successor is the same as the set of splits
* for the predecessor:
* a) The successor is in the same `SameSplitsBlock` as the predecessor.
* b) The successor is *not* in the same `SameSplitsBlock` as the predecessor.
* 2. The set of splits for the successor is different from the set of splits
* for the predecessor:
* a) The set of splits for the successor is *maybe* non-empty.
* b) The set of splits for the successor is *always* empty.
*
* Only case 2a may introduce new sets of splits, so only predicates from
* this case are used in the definition of `TSplits`.
*
* The predicates in this module are named after the cases above.
*/
private module SuccSplits {
private predicate succInvariant1(
Reachability::SameSplitsBlock b, AstNode pred, Splits predSplits, AstNode succ, Completion c
) {
pred = b.getANode() and
b.isReachable(predSplits) and
succ(pred, succ, c)
}
private predicate case1b0(AstNode pred, Splits predSplits, AstNode succ, Completion c) {
exists(Reachability::SameSplitsBlock b |
// Invariant 1
succInvariant1(b, pred, predSplits, succ, c)
|
(succ = b.getANode() implies succ = b) and
// Invariant 4
not exists(SplitImpl split | split.hasEntry(pred, succ, c))
)
}
/**
* Case 1b.
*
* Invariants 1 and 4 hold in the base case, and invariants 2, 3, and 5 are
* maintained for all splits in `predSplits` (= `succSplits`), except
* possibly for the splits in `except`.
*
* The predicate is written using explicit recursion, as opposed to a `forall`,
* to avoid negative recursion.
*/
private predicate case1bForall(
AstNode pred, Splits predSplits, AstNode succ, Completion c, Splits except
) {
case1b0(pred, predSplits, succ, c) and
except = predSplits
or
exists(SplitImpl split |
case1bForallCons(pred, predSplits, succ, c, split, except) and
split.hasSuccessor(pred, succ, c)
)
}
pragma[noinline]
private predicate case1bForallCons(
AstNode pred, Splits predSplits, AstNode succ, Completion c, SplitImpl exceptHead,
Splits exceptTail
) {
case1bForall(pred, predSplits, succ, c, TSplitsCons(exceptHead, exceptTail))
}
private predicate case1(AstNode pred, Splits predSplits, AstNode succ, Completion c) {
// Case 1a
exists(Reachability::SameSplitsBlock b | succInvariant1(b, pred, predSplits, succ, c) |
succ = b.getANode() and
not succ = b
)
or
// Case 1b
case1bForall(pred, predSplits, succ, c, TSplitsNil())
}
pragma[noinline]
private SplitImpl succInvariant1GetASplit(
Reachability::SameSplitsBlock b, AstNode pred, Splits predSplits, AstNode succ, Completion c
) {
succInvariant1(b, pred, predSplits, succ, c) and
result = predSplits.getASplit()
}
private predicate case2aux(AstNode pred, Splits predSplits, AstNode succ, Completion c) {
exists(Reachability::SameSplitsBlock b |
succInvariant1(b, pred, predSplits, succ, c) and
(succ = b.getANode() implies succ = b)
|
succInvariant1GetASplit(b, pred, predSplits, succ, c).hasExit(pred, succ, c)
or
any(SplitImpl split).hasEntry(pred, succ, c)
)
}
/**
* Holds if `succSplits` should not inherit a split of kind `sk` from
* `predSplits`, except possibly because of a split in `except`.
*
* The predicate is written using explicit recursion, as opposed to a `forall`,
* to avoid negative recursion.
*/
private predicate case2aNoneInheritedOfKindForall(
AstNode pred, Splits predSplits, AstNode succ, Completion c, SplitKind sk, Splits except
) {
case2aux(pred, predSplits, succ, c) and
sk.appliesTo(succ) and
except = predSplits
or
exists(Splits mid, SplitImpl split |
case2aNoneInheritedOfKindForall(pred, predSplits, succ, c, sk, mid) and
mid = TSplitsCons(split, except)
|
split.getKind() = any(SplitKind sk0 | sk0 != sk and sk0.appliesTo(succ))
or
split.hasExit(pred, succ, c)
)
}
pragma[nomagic]
private predicate entryOfKind(
AstNode pred, AstNode succ, Completion c, SplitImpl split, SplitKind sk
) {
split.hasEntry(pred, succ, c) and
sk = split.getKind()
}
/** Holds if `succSplits` should not have a split of kind `sk`. */
pragma[nomagic]
private predicate case2aNoneOfKind(
AstNode pred, Splits predSplits, AstNode succ, Completion c, SplitKind sk
) {
// None inherited from predecessor
case2aNoneInheritedOfKindForall(pred, predSplits, succ, c, sk, TSplitsNil()) and
// None newly entered into
not entryOfKind(pred, succ, c, _, sk)
}
/** Holds if `succSplits` should not have a split of kind `sk` at rank `rnk`. */
pragma[nomagic]
private predicate case2aNoneAtRank(
AstNode pred, Splits predSplits, AstNode succ, Completion c, int rnk
) {
exists(SplitKind sk | case2aNoneOfKind(pred, predSplits, succ, c, sk) |
rnk = sk.getListRank(succ)
)
}
pragma[nomagic]
private SplitImpl case2auxGetAPredecessorSplit(
AstNode pred, Splits predSplits, AstNode succ, Completion c
) {
case2aux(pred, predSplits, succ, c) and
result = predSplits.getASplit()
}
/** Gets a split that should be in `succSplits`. */
pragma[nomagic]
private SplitImpl case2aSome(
AstNode pred, Splits predSplits, AstNode succ, Completion c, SplitKind sk
) {
(
// Inherited from predecessor
result = case2auxGetAPredecessorSplit(pred, predSplits, succ, c) and
result.hasSuccessor(pred, succ, c)
or
// Newly entered into
exists(SplitKind sk0 |
case2aNoneInheritedOfKindForall(pred, predSplits, succ, c, sk0, TSplitsNil())
|
entryOfKind(pred, succ, c, result, sk0)
)
) and
sk = result.getKind()
}
/** Gets a split that should be in `succSplits` at rank `rnk`. */
pragma[nomagic]
SplitImpl case2aSomeAtRank(AstNode pred, Splits predSplits, AstNode succ, Completion c, int rnk) {
exists(SplitKind sk | result = case2aSome(pred, predSplits, succ, c, sk) |
rnk = sk.getListRank(succ)
)
}
/**
* Case 2a.
*
* As opposed to the other cases, in this case we need to construct a new set
* of splits `succSplits`. Since this involves constructing the very IPA type,
* we cannot recurse directly over the structure of `succSplits`. Instead, we
* recurse over the ranks of all splits that *might* be in `succSplits`.
*
* - Invariant 1 holds in the base case,
* - invariant 2 holds for all splits with rank at least `rnk`,
* - invariant 3 holds for all splits in `predSplits`,
* - invariant 4 holds for all splits in `succSplits` with rank at least `rnk`,
* and
* - invariant 4 holds for all splits in `succSplits` with rank at least `rnk`.
*/
predicate case2aFromRank(
AstNode pred, Splits predSplits, AstNode succ, Splits succSplits, Completion c, int rnk
) {
case2aux(pred, predSplits, succ, c) and
succSplits = TSplitsNil() and
rnk = max(any(SplitKind sk).getListRank(succ)) + 1
or
case2aFromRank(pred, predSplits, succ, succSplits, c, rnk + 1) and
case2aNoneAtRank(pred, predSplits, succ, c, rnk)
or
exists(Splits mid, SplitImpl split | split = case2aCons(pred, predSplits, succ, mid, c, rnk) |
succSplits = TSplitsCons(split, mid)
)
}
pragma[noinline]
private SplitImpl case2aCons(
AstNode pred, Splits predSplits, AstNode succ, Splits succSplits, Completion c, int rnk
) {
case2aFromRank(pred, predSplits, succ, succSplits, c, rnk + 1) and
result = case2aSomeAtRank(pred, predSplits, succ, c, rnk)
}
/**
* Case 2b.
*
* Invariants 1, 4, and 5 hold in the base case, and invariants 2 and 3 are
* maintained for all splits in `predSplits`, except possibly for the splits
* in `except`.
*
* The predicate is written using explicit recursion, as opposed to a `forall`,
* to avoid negative recursion.
*/
private predicate case2bForall(
AstNode pred, Splits predSplits, AstNode succ, Completion c, Splits except
) {
// Invariant 1
case2aux(pred, predSplits, succ, c) and
// Invariants 4 and 5
not any(SplitKind sk).appliesTo(succ) and
except = predSplits
or
exists(SplitImpl split | case2bForallCons(pred, predSplits, succ, c, split, except) |
// Invariants 2 and 3
split.hasExit(pred, succ, c)
)
}
pragma[noinline]
private predicate case2bForallCons(
AstNode pred, Splits predSplits, AstNode succ, Completion c, SplitImpl exceptHead,
Splits exceptTail
) {
case2bForall(pred, predSplits, succ, c, TSplitsCons(exceptHead, exceptTail))
}
private predicate case2(
AstNode pred, Splits predSplits, AstNode succ, Splits succSplits, Completion c
) {
case2aFromRank(pred, predSplits, succ, succSplits, c, 1)
or
case2bForall(pred, predSplits, succ, c, TSplitsNil()) and
succSplits = TSplitsNil()
}
/**
* Holds if `succ` with splits `succSplits` is a successor of type `t` for `pred`
* with splits `predSplits`.
*/
predicate succSplits(
AstNode pred, Splits predSplits, AstNode succ, Splits succSplits, Completion c
) {
case1(pred, predSplits, succ, c) and
succSplits = predSplits
or
case2(pred, predSplits, succ, succSplits, c)
}
}
import SuccSplits
/** Provides logic for calculating reachable control flow nodes. */
module Reachability {
/**
* Holds if `n` is an AST node where the set of possible splits may be different
* from the set of possible splits for one of `n`'s predecessors. That is, `n`
* starts a new block of elements with the same set of splits.
*/
private predicate startsSplits(AstNode n) {
succEntry(_, n)
or
exists(SplitImpl s |
s.hasEntry(_, n, _)
or
s.hasExit(_, n, _)
)
or
exists(AstNode pred, SplitImpl split, Completion c | succ(pred, n, c) |
split.appliesTo(pred) and
not split.hasSuccessor(pred, n, c)
)
}
private predicate intraSplitsSucc(AstNode pred, AstNode succ) {
succ(pred, succ, _) and
not startsSplits(succ)
}
private predicate splitsBlockContains(AstNode start, AstNode n) =
fastTC(intraSplitsSucc/2)(start, n)
/**
* A block of AST nodes where the set of splits is guaranteed to remain unchanged,
* represented by the first element in the block.
*/
class SameSplitsBlock extends AstNode {
SameSplitsBlock() { startsSplits(this) }
/** Gets an AST node in this block. */
AstNode getANode() {
splitsBlockContains(this, result)
or
result = this
}
pragma[noinline]
private SameSplitsBlock getASuccessor(Splits predSplits, Splits succSplits) {
exists(AstNode pred | pred = this.getANode() |
succSplits(pred, predSplits, result, succSplits, _)
)
}
/**
* Holds if the elements of this block are reachable from an point, with the
* splits `splits`.
*/
predicate isReachable(Splits splits) {
// Base case
succEntrySplits(_, this, splits, _)
or
// Recursive case
exists(SameSplitsBlock pred, Splits predSplits | pred.isReachable(predSplits) |
this = pred.getASuccessor(predSplits, splits)
)
}
}
}

View File

@@ -0,0 +1,319 @@
nodes
| exit.rb:1:1:6:3 | enter m1 |
| exit.rb:1:1:6:3 | exit m1 |
| exit.rb:1:1:6:3 | exit m1 (abnormal) |
| exit.rb:1:1:6:3 | exit m1 (normal) |
| exit.rb:2:3:4:5 | If |
| exit.rb:2:6:2:6 | x |
| exit.rb:2:6:2:10 | Binary |
| exit.rb:2:10:2:10 | 2 |
| exit.rb:3:5:3:8 | exit |
| exit.rb:3:5:3:10 | MethodCall |
| exit.rb:3:10:3:10 | 1 |
| exit.rb:5:3:5:6 | puts |
| exit.rb:5:3:5:15 | MethodCall |
| exit.rb:5:8:5:15 | String |
| exit.rb:8:1:13:3 | enter m2 |
| exit.rb:8:1:13:3 | exit m2 |
| exit.rb:8:1:13:3 | exit m2 (abnormal) |
| exit.rb:8:1:13:3 | exit m2 (normal) |
| exit.rb:9:3:11:5 | If |
| exit.rb:9:6:9:6 | x |
| exit.rb:9:6:9:10 | Binary |
| exit.rb:9:10:9:10 | 2 |
| exit.rb:10:5:10:9 | abort |
| exit.rb:10:5:10:18 | MethodCall |
| exit.rb:10:11:10:18 | String |
| exit.rb:12:3:12:6 | puts |
| exit.rb:12:3:12:15 | MethodCall |
| exit.rb:12:8:12:15 | String |
| ifs.rb:1:1:9:3 | enter m1 |
| ifs.rb:1:1:9:3 | exit m1 |
| ifs.rb:1:1:9:3 | exit m1 (normal) |
| ifs.rb:2:3:8:5 | If |
| ifs.rb:2:6:2:6 | x |
| ifs.rb:2:6:2:10 | Binary |
| ifs.rb:2:10:2:10 | 2 |
| ifs.rb:3:5:3:8 | puts |
| ifs.rb:3:5:3:30 | MethodCall |
| ifs.rb:3:10:3:30 | String |
| ifs.rb:4:3:7:35 | Elsif |
| ifs.rb:4:9:4:9 | x |
| ifs.rb:4:9:4:14 | Binary |
| ifs.rb:4:9:4:24 | [false] Binary |
| ifs.rb:4:9:4:24 | [true] Binary |
| ifs.rb:4:9:4:38 | [false] Binary |
| ifs.rb:4:9:4:38 | [true] Binary |
| ifs.rb:4:14:4:14 | 2 |
| ifs.rb:4:20:4:20 | x |
| ifs.rb:4:20:4:24 | Binary |
| ifs.rb:4:24:4:24 | 0 |
| ifs.rb:4:30:4:38 | [false] Unary |
| ifs.rb:4:30:4:38 | [true] Unary |
| ifs.rb:4:31:4:38 | [false] ParenthesizedStatements |
| ifs.rb:4:31:4:38 | [true] ParenthesizedStatements |
| ifs.rb:4:32:4:32 | x |
| ifs.rb:4:32:4:37 | Binary |
| ifs.rb:4:37:4:37 | 5 |
| ifs.rb:5:5:5:8 | puts |
| ifs.rb:5:5:5:17 | MethodCall |
| ifs.rb:5:10:5:17 | String |
| ifs.rb:7:5:7:8 | puts |
| ifs.rb:7:5:7:35 | MethodCall |
| ifs.rb:7:10:7:35 | String |
| ifs.rb:11:1:16:3 | enter m2 |
| ifs.rb:11:1:16:3 | exit m2 |
| ifs.rb:11:1:16:3 | exit m2 (normal) |
| ifs.rb:12:3:14:5 | If |
| ifs.rb:12:6:12:6 | b |
| ifs.rb:13:5:13:12 | Return |
| ifs.rb:13:12:13:12 | 0 |
| ifs.rb:15:3:15:10 | Return |
| ifs.rb:15:10:15:10 | 1 |
| ifs.rb:18:1:26:3 | enter m3 |
| ifs.rb:18:1:26:3 | exit m3 |
| ifs.rb:18:1:26:3 | exit m3 (normal) |
| ifs.rb:19:3:24:5 | If |
| ifs.rb:19:6:19:6 | x |
| ifs.rb:19:6:19:10 | Binary |
| ifs.rb:19:10:19:10 | 0 |
| ifs.rb:20:5:20:5 | x |
| ifs.rb:20:5:20:10 | Assignment |
| ifs.rb:20:9:20:10 | Unary |
| ifs.rb:20:10:20:10 | x |
| ifs.rb:21:5:23:7 | If |
| ifs.rb:21:8:21:8 | x |
| ifs.rb:21:8:21:13 | Binary |
| ifs.rb:21:12:21:13 | 10 |
| ifs.rb:22:7:22:7 | x |
| ifs.rb:22:7:22:15 | Assignment |
| ifs.rb:22:11:22:11 | x |
| ifs.rb:22:11:22:15 | Binary |
| ifs.rb:22:15:22:15 | 1 |
| ifs.rb:25:3:25:6 | puts |
| ifs.rb:25:3:25:8 | MethodCall |
| ifs.rb:25:8:25:8 | x |
| loops.rb:1:1:6:3 | enter m1 |
| loops.rb:1:1:6:3 | exit m1 |
| loops.rb:1:1:6:3 | exit m1 (normal) |
| loops.rb:2:3:5:5 | While |
| loops.rb:2:9:2:9 | x |
| loops.rb:2:9:2:14 | Binary |
| loops.rb:2:14:2:14 | 0 |
| loops.rb:3:5:3:8 | puts |
| loops.rb:3:5:3:10 | MethodCall |
| loops.rb:3:10:3:10 | x |
| loops.rb:4:5:4:5 | x |
| loops.rb:4:5:4:10 | OperatorAssignment |
| loops.rb:4:10:4:10 | 1 |
| loops.rb:8:1:22:3 | enter m2 |
| loops.rb:8:1:22:3 | exit m2 |
| loops.rb:8:1:22:3 | exit m2 (normal) |
| loops.rb:9:3:20:5 | While |
| loops.rb:9:9:9:9 | x |
| loops.rb:9:9:9:14 | Binary |
| loops.rb:9:14:9:14 | 0 |
| loops.rb:10:5:10:8 | puts |
| loops.rb:10:5:10:10 | MethodCall |
| loops.rb:10:10:10:10 | x |
| loops.rb:11:5:11:5 | x |
| loops.rb:11:5:11:10 | OperatorAssignment |
| loops.rb:11:10:11:10 | 1 |
| loops.rb:12:5:18:7 | If |
| loops.rb:12:8:12:8 | x |
| loops.rb:12:8:12:14 | Binary |
| loops.rb:12:12:12:14 | 100 |
| loops.rb:13:7:13:11 | Break |
| loops.rb:14:5:17:10 | Elsif |
| loops.rb:14:11:14:11 | x |
| loops.rb:14:11:14:16 | Binary |
| loops.rb:14:15:14:16 | 50 |
| loops.rb:15:7:15:10 | Next |
| loops.rb:16:5:17:10 | Elsif |
| loops.rb:16:11:16:11 | x |
| loops.rb:16:11:16:16 | Binary |
| loops.rb:16:15:16:16 | 10 |
| loops.rb:17:7:17:10 | Redo |
| loops.rb:19:5:19:8 | puts |
| loops.rb:19:5:19:15 | MethodCall |
| loops.rb:19:10:19:15 | String |
| loops.rb:21:3:21:6 | puts |
| loops.rb:21:3:21:13 | MethodCall |
| loops.rb:21:8:21:13 | String |
| raise.rb:1:1:6:3 | enter m1 |
| raise.rb:1:1:6:3 | exit m1 |
| raise.rb:1:1:6:3 | exit m1 (abnormal) |
| raise.rb:1:1:6:3 | exit m1 (normal) |
| raise.rb:2:3:4:5 | If |
| raise.rb:2:6:2:6 | x |
| raise.rb:2:6:2:10 | Binary |
| raise.rb:2:10:2:10 | 2 |
| raise.rb:3:5:3:9 | raise |
| raise.rb:3:5:3:17 | MethodCall |
| raise.rb:3:11:3:17 | String |
| raise.rb:5:3:5:6 | puts |
| raise.rb:5:3:5:15 | MethodCall |
| raise.rb:5:8:5:15 | String |
edges
| exit.rb:1:1:6:3 | enter m1 | exit.rb:2:3:4:5 | If | semmle.label | successor |
| exit.rb:1:1:6:3 | exit m1 (abnormal) | exit.rb:1:1:6:3 | exit m1 | semmle.label | successor |
| exit.rb:1:1:6:3 | exit m1 (normal) | exit.rb:1:1:6:3 | exit m1 | semmle.label | successor |
| exit.rb:2:3:4:5 | If | exit.rb:2:6:2:6 | x | semmle.label | successor |
| exit.rb:2:6:2:6 | x | exit.rb:2:10:2:10 | 2 | semmle.label | successor |
| exit.rb:2:6:2:10 | Binary | exit.rb:3:10:3:10 | 1 | semmle.label | true |
| exit.rb:2:6:2:10 | Binary | exit.rb:5:8:5:15 | String | semmle.label | false |
| exit.rb:2:10:2:10 | 2 | exit.rb:2:6:2:10 | Binary | semmle.label | successor |
| exit.rb:3:5:3:8 | exit | exit.rb:3:5:3:10 | MethodCall | semmle.label | successor |
| exit.rb:3:5:3:10 | MethodCall | exit.rb:1:1:6:3 | exit m1 (abnormal) | semmle.label | exit |
| exit.rb:3:10:3:10 | 1 | exit.rb:3:5:3:8 | exit | semmle.label | successor |
| exit.rb:5:3:5:6 | puts | exit.rb:5:3:5:15 | MethodCall | semmle.label | successor |
| exit.rb:5:3:5:15 | MethodCall | exit.rb:1:1:6:3 | exit m1 (normal) | semmle.label | successor |
| exit.rb:5:8:5:15 | String | exit.rb:5:3:5:6 | puts | semmle.label | successor |
| exit.rb:8:1:13:3 | enter m2 | exit.rb:9:3:11:5 | If | semmle.label | successor |
| exit.rb:8:1:13:3 | exit m2 (abnormal) | exit.rb:8:1:13:3 | exit m2 | semmle.label | successor |
| exit.rb:8:1:13:3 | exit m2 (normal) | exit.rb:8:1:13:3 | exit m2 | semmle.label | successor |
| exit.rb:9:3:11:5 | If | exit.rb:9:6:9:6 | x | semmle.label | successor |
| exit.rb:9:6:9:6 | x | exit.rb:9:10:9:10 | 2 | semmle.label | successor |
| exit.rb:9:6:9:10 | Binary | exit.rb:10:11:10:18 | String | semmle.label | true |
| exit.rb:9:6:9:10 | Binary | exit.rb:12:8:12:15 | String | semmle.label | false |
| exit.rb:9:10:9:10 | 2 | exit.rb:9:6:9:10 | Binary | semmle.label | successor |
| exit.rb:10:5:10:9 | abort | exit.rb:10:5:10:18 | MethodCall | semmle.label | successor |
| exit.rb:10:5:10:18 | MethodCall | exit.rb:8:1:13:3 | exit m2 (abnormal) | semmle.label | exit |
| exit.rb:10:11:10:18 | String | exit.rb:10:5:10:9 | abort | semmle.label | successor |
| exit.rb:12:3:12:6 | puts | exit.rb:12:3:12:15 | MethodCall | semmle.label | successor |
| exit.rb:12:3:12:15 | MethodCall | exit.rb:8:1:13:3 | exit m2 (normal) | semmle.label | successor |
| exit.rb:12:8:12:15 | String | exit.rb:12:3:12:6 | puts | semmle.label | successor |
| ifs.rb:1:1:9:3 | enter m1 | ifs.rb:2:3:8:5 | If | semmle.label | successor |
| ifs.rb:1:1:9:3 | exit m1 (normal) | ifs.rb:1:1:9:3 | exit m1 | semmle.label | successor |
| ifs.rb:2:3:8:5 | If | ifs.rb:2:6:2:6 | x | semmle.label | successor |
| ifs.rb:2:6:2:6 | x | ifs.rb:2:10:2:10 | 2 | semmle.label | successor |
| ifs.rb:2:6:2:10 | Binary | ifs.rb:3:10:3:30 | String | semmle.label | true |
| ifs.rb:2:6:2:10 | Binary | ifs.rb:4:3:7:35 | Elsif | semmle.label | false |
| ifs.rb:2:10:2:10 | 2 | ifs.rb:2:6:2:10 | Binary | semmle.label | successor |
| ifs.rb:3:5:3:8 | puts | ifs.rb:3:5:3:30 | MethodCall | semmle.label | successor |
| ifs.rb:3:5:3:30 | MethodCall | ifs.rb:1:1:9:3 | exit m1 (normal) | semmle.label | successor |
| ifs.rb:3:10:3:30 | String | ifs.rb:3:5:3:8 | puts | semmle.label | successor |
| ifs.rb:4:3:7:35 | Elsif | ifs.rb:4:9:4:9 | x | semmle.label | successor |
| ifs.rb:4:9:4:9 | x | ifs.rb:4:14:4:14 | 2 | semmle.label | successor |
| ifs.rb:4:9:4:14 | Binary | ifs.rb:4:9:4:24 | [false] Binary | semmle.label | false |
| ifs.rb:4:9:4:14 | Binary | ifs.rb:4:20:4:20 | x | semmle.label | true |
| ifs.rb:4:9:4:24 | [false] Binary | ifs.rb:4:9:4:38 | [false] Binary | semmle.label | false |
| ifs.rb:4:9:4:24 | [true] Binary | ifs.rb:4:32:4:32 | x | semmle.label | true |
| ifs.rb:4:9:4:38 | [false] Binary | ifs.rb:7:10:7:35 | String | semmle.label | false |
| ifs.rb:4:9:4:38 | [true] Binary | ifs.rb:5:10:5:17 | String | semmle.label | true |
| ifs.rb:4:14:4:14 | 2 | ifs.rb:4:9:4:14 | Binary | semmle.label | successor |
| ifs.rb:4:20:4:20 | x | ifs.rb:4:24:4:24 | 0 | semmle.label | successor |
| ifs.rb:4:20:4:24 | Binary | ifs.rb:4:9:4:24 | [false] Binary | semmle.label | false |
| ifs.rb:4:20:4:24 | Binary | ifs.rb:4:9:4:24 | [true] Binary | semmle.label | true |
| ifs.rb:4:24:4:24 | 0 | ifs.rb:4:20:4:24 | Binary | semmle.label | successor |
| ifs.rb:4:30:4:38 | [false] Unary | ifs.rb:4:9:4:38 | [false] Binary | semmle.label | false |
| ifs.rb:4:30:4:38 | [true] Unary | ifs.rb:4:9:4:38 | [true] Binary | semmle.label | true |
| ifs.rb:4:31:4:38 | [false] ParenthesizedStatements | ifs.rb:4:30:4:38 | [true] Unary | semmle.label | false |
| ifs.rb:4:31:4:38 | [true] ParenthesizedStatements | ifs.rb:4:30:4:38 | [false] Unary | semmle.label | true |
| ifs.rb:4:32:4:32 | x | ifs.rb:4:37:4:37 | 5 | semmle.label | successor |
| ifs.rb:4:32:4:37 | Binary | ifs.rb:4:31:4:38 | [false] ParenthesizedStatements | semmle.label | false |
| ifs.rb:4:32:4:37 | Binary | ifs.rb:4:31:4:38 | [true] ParenthesizedStatements | semmle.label | true |
| ifs.rb:4:37:4:37 | 5 | ifs.rb:4:32:4:37 | Binary | semmle.label | successor |
| ifs.rb:5:5:5:8 | puts | ifs.rb:5:5:5:17 | MethodCall | semmle.label | successor |
| ifs.rb:5:5:5:17 | MethodCall | ifs.rb:1:1:9:3 | exit m1 (normal) | semmle.label | successor |
| ifs.rb:5:10:5:17 | String | ifs.rb:5:5:5:8 | puts | semmle.label | successor |
| ifs.rb:7:5:7:8 | puts | ifs.rb:7:5:7:35 | MethodCall | semmle.label | successor |
| ifs.rb:7:5:7:35 | MethodCall | ifs.rb:1:1:9:3 | exit m1 (normal) | semmle.label | successor |
| ifs.rb:7:10:7:35 | String | ifs.rb:7:5:7:8 | puts | semmle.label | successor |
| ifs.rb:11:1:16:3 | enter m2 | ifs.rb:12:3:14:5 | If | semmle.label | successor |
| ifs.rb:11:1:16:3 | exit m2 (normal) | ifs.rb:11:1:16:3 | exit m2 | semmle.label | successor |
| ifs.rb:12:3:14:5 | If | ifs.rb:12:6:12:6 | b | semmle.label | successor |
| ifs.rb:12:6:12:6 | b | ifs.rb:13:12:13:12 | 0 | semmle.label | true |
| ifs.rb:12:6:12:6 | b | ifs.rb:15:10:15:10 | 1 | semmle.label | false |
| ifs.rb:13:5:13:12 | Return | ifs.rb:11:1:16:3 | exit m2 (normal) | semmle.label | return |
| ifs.rb:13:12:13:12 | 0 | ifs.rb:13:5:13:12 | Return | semmle.label | successor |
| ifs.rb:15:3:15:10 | Return | ifs.rb:11:1:16:3 | exit m2 (normal) | semmle.label | return |
| ifs.rb:15:10:15:10 | 1 | ifs.rb:15:3:15:10 | Return | semmle.label | successor |
| ifs.rb:18:1:26:3 | enter m3 | ifs.rb:19:3:24:5 | If | semmle.label | successor |
| ifs.rb:18:1:26:3 | exit m3 (normal) | ifs.rb:18:1:26:3 | exit m3 | semmle.label | successor |
| ifs.rb:19:3:24:5 | If | ifs.rb:19:6:19:6 | x | semmle.label | successor |
| ifs.rb:19:6:19:6 | x | ifs.rb:19:10:19:10 | 0 | semmle.label | successor |
| ifs.rb:19:6:19:10 | Binary | ifs.rb:20:5:20:5 | x | semmle.label | true |
| ifs.rb:19:6:19:10 | Binary | ifs.rb:25:8:25:8 | x | semmle.label | false |
| ifs.rb:19:10:19:10 | 0 | ifs.rb:19:6:19:10 | Binary | semmle.label | successor |
| ifs.rb:20:5:20:5 | x | ifs.rb:20:10:20:10 | x | semmle.label | successor |
| ifs.rb:20:5:20:10 | Assignment | ifs.rb:21:5:23:7 | If | semmle.label | successor |
| ifs.rb:20:9:20:10 | Unary | ifs.rb:20:5:20:10 | Assignment | semmle.label | successor |
| ifs.rb:20:10:20:10 | x | ifs.rb:20:9:20:10 | Unary | semmle.label | successor |
| ifs.rb:21:5:23:7 | If | ifs.rb:21:8:21:8 | x | semmle.label | successor |
| ifs.rb:21:8:21:8 | x | ifs.rb:21:12:21:13 | 10 | semmle.label | successor |
| ifs.rb:21:8:21:13 | Binary | ifs.rb:22:7:22:7 | x | semmle.label | true |
| ifs.rb:21:8:21:13 | Binary | ifs.rb:25:8:25:8 | x | semmle.label | false |
| ifs.rb:21:12:21:13 | 10 | ifs.rb:21:8:21:13 | Binary | semmle.label | successor |
| ifs.rb:22:7:22:7 | x | ifs.rb:22:11:22:11 | x | semmle.label | successor |
| ifs.rb:22:7:22:15 | Assignment | ifs.rb:25:8:25:8 | x | semmle.label | successor |
| ifs.rb:22:11:22:11 | x | ifs.rb:22:15:22:15 | 1 | semmle.label | successor |
| ifs.rb:22:11:22:15 | Binary | ifs.rb:22:7:22:15 | Assignment | semmle.label | successor |
| ifs.rb:22:15:22:15 | 1 | ifs.rb:22:11:22:15 | Binary | semmle.label | successor |
| ifs.rb:25:3:25:6 | puts | ifs.rb:25:3:25:8 | MethodCall | semmle.label | successor |
| ifs.rb:25:3:25:8 | MethodCall | ifs.rb:18:1:26:3 | exit m3 (normal) | semmle.label | successor |
| ifs.rb:25:8:25:8 | x | ifs.rb:25:3:25:6 | puts | semmle.label | successor |
| loops.rb:1:1:6:3 | enter m1 | loops.rb:2:3:5:5 | While | semmle.label | successor |
| loops.rb:1:1:6:3 | exit m1 (normal) | loops.rb:1:1:6:3 | exit m1 | semmle.label | successor |
| loops.rb:2:3:5:5 | While | loops.rb:2:9:2:9 | x | semmle.label | successor |
| loops.rb:2:9:2:9 | x | loops.rb:2:14:2:14 | 0 | semmle.label | successor |
| loops.rb:2:9:2:14 | Binary | loops.rb:1:1:6:3 | exit m1 (normal) | semmle.label | false |
| loops.rb:2:9:2:14 | Binary | loops.rb:3:10:3:10 | x | semmle.label | true |
| loops.rb:2:14:2:14 | 0 | loops.rb:2:9:2:14 | Binary | semmle.label | successor |
| loops.rb:3:5:3:8 | puts | loops.rb:3:5:3:10 | MethodCall | semmle.label | successor |
| loops.rb:3:5:3:10 | MethodCall | loops.rb:4:5:4:5 | x | semmle.label | successor |
| loops.rb:3:10:3:10 | x | loops.rb:3:5:3:8 | puts | semmle.label | successor |
| loops.rb:4:5:4:5 | x | loops.rb:4:10:4:10 | 1 | semmle.label | successor |
| loops.rb:4:5:4:10 | OperatorAssignment | loops.rb:2:9:2:9 | x | semmle.label | successor |
| loops.rb:4:10:4:10 | 1 | loops.rb:4:5:4:10 | OperatorAssignment | semmle.label | successor |
| loops.rb:8:1:22:3 | enter m2 | loops.rb:9:3:20:5 | While | semmle.label | successor |
| loops.rb:8:1:22:3 | exit m2 (normal) | loops.rb:8:1:22:3 | exit m2 | semmle.label | successor |
| loops.rb:9:3:20:5 | While | loops.rb:9:9:9:9 | x | semmle.label | successor |
| loops.rb:9:9:9:9 | x | loops.rb:9:14:9:14 | 0 | semmle.label | successor |
| loops.rb:9:9:9:14 | Binary | loops.rb:10:10:10:10 | x | semmle.label | true |
| loops.rb:9:9:9:14 | Binary | loops.rb:21:8:21:13 | String | semmle.label | false |
| loops.rb:9:14:9:14 | 0 | loops.rb:9:9:9:14 | Binary | semmle.label | successor |
| loops.rb:10:5:10:8 | puts | loops.rb:10:5:10:10 | MethodCall | semmle.label | successor |
| loops.rb:10:5:10:10 | MethodCall | loops.rb:11:5:11:5 | x | semmle.label | successor |
| loops.rb:10:10:10:10 | x | loops.rb:10:5:10:8 | puts | semmle.label | successor |
| loops.rb:11:5:11:5 | x | loops.rb:11:10:11:10 | 1 | semmle.label | successor |
| loops.rb:11:5:11:10 | OperatorAssignment | loops.rb:12:5:18:7 | If | semmle.label | successor |
| loops.rb:11:10:11:10 | 1 | loops.rb:11:5:11:10 | OperatorAssignment | semmle.label | successor |
| loops.rb:12:5:18:7 | If | loops.rb:12:8:12:8 | x | semmle.label | successor |
| loops.rb:12:8:12:8 | x | loops.rb:12:12:12:14 | 100 | semmle.label | successor |
| loops.rb:12:8:12:14 | Binary | loops.rb:13:7:13:11 | Break | semmle.label | true |
| loops.rb:12:8:12:14 | Binary | loops.rb:14:5:17:10 | Elsif | semmle.label | false |
| loops.rb:12:12:12:14 | 100 | loops.rb:12:8:12:14 | Binary | semmle.label | successor |
| loops.rb:13:7:13:11 | Break | loops.rb:21:8:21:13 | String | semmle.label | break |
| loops.rb:14:5:17:10 | Elsif | loops.rb:14:11:14:11 | x | semmle.label | successor |
| loops.rb:14:11:14:11 | x | loops.rb:14:15:14:16 | 50 | semmle.label | successor |
| loops.rb:14:11:14:16 | Binary | loops.rb:15:7:15:10 | Next | semmle.label | true |
| loops.rb:14:11:14:16 | Binary | loops.rb:16:5:17:10 | Elsif | semmle.label | false |
| loops.rb:14:15:14:16 | 50 | loops.rb:14:11:14:16 | Binary | semmle.label | successor |
| loops.rb:15:7:15:10 | Next | loops.rb:9:9:9:9 | x | semmle.label | next |
| loops.rb:16:5:17:10 | Elsif | loops.rb:16:11:16:11 | x | semmle.label | successor |
| loops.rb:16:11:16:11 | x | loops.rb:16:15:16:16 | 10 | semmle.label | successor |
| loops.rb:16:11:16:16 | Binary | loops.rb:17:7:17:10 | Redo | semmle.label | true |
| loops.rb:16:11:16:16 | Binary | loops.rb:19:10:19:15 | String | semmle.label | false |
| loops.rb:16:15:16:16 | 10 | loops.rb:16:11:16:16 | Binary | semmle.label | successor |
| loops.rb:17:7:17:10 | Redo | loops.rb:10:10:10:10 | x | semmle.label | successor |
| loops.rb:19:5:19:8 | puts | loops.rb:19:5:19:15 | MethodCall | semmle.label | successor |
| loops.rb:19:5:19:15 | MethodCall | loops.rb:9:9:9:9 | x | semmle.label | successor |
| loops.rb:19:10:19:15 | String | loops.rb:19:5:19:8 | puts | semmle.label | successor |
| loops.rb:21:3:21:6 | puts | loops.rb:21:3:21:13 | MethodCall | semmle.label | successor |
| loops.rb:21:3:21:13 | MethodCall | loops.rb:8:1:22:3 | exit m2 (normal) | semmle.label | successor |
| loops.rb:21:8:21:13 | String | loops.rb:21:3:21:6 | puts | semmle.label | successor |
| raise.rb:1:1:6:3 | enter m1 | raise.rb:2:3:4:5 | If | semmle.label | successor |
| raise.rb:1:1:6:3 | exit m1 (abnormal) | raise.rb:1:1:6:3 | exit m1 | semmle.label | successor |
| raise.rb:1:1:6:3 | exit m1 (normal) | raise.rb:1:1:6:3 | exit m1 | semmle.label | successor |
| raise.rb:2:3:4:5 | If | raise.rb:2:6:2:6 | x | semmle.label | successor |
| raise.rb:2:6:2:6 | x | raise.rb:2:10:2:10 | 2 | semmle.label | successor |
| raise.rb:2:6:2:10 | Binary | raise.rb:3:11:3:17 | String | semmle.label | true |
| raise.rb:2:6:2:10 | Binary | raise.rb:5:8:5:15 | String | semmle.label | false |
| raise.rb:2:10:2:10 | 2 | raise.rb:2:6:2:10 | Binary | semmle.label | successor |
| raise.rb:3:5:3:9 | raise | raise.rb:3:5:3:17 | MethodCall | semmle.label | successor |
| raise.rb:3:5:3:17 | MethodCall | raise.rb:1:1:6:3 | exit m1 (abnormal) | semmle.label | raise |
| raise.rb:3:11:3:17 | String | raise.rb:3:5:3:9 | raise | semmle.label | successor |
| raise.rb:5:3:5:6 | puts | raise.rb:5:3:5:15 | MethodCall | semmle.label | successor |
| raise.rb:5:3:5:15 | MethodCall | raise.rb:1:1:6:3 | exit m1 (normal) | semmle.label | successor |
| raise.rb:5:8:5:15 | String | raise.rb:5:3:5:6 | puts | semmle.label | successor |

View File

@@ -0,0 +1 @@
codeql_ruby/controlflow/internal/Cfg.ql

View File

@@ -0,0 +1,13 @@
def m1 x
if x > 2
exit 1
end
puts "x <= 2"
end
def m2 x
if x > 2
abort "abort!"
end
puts "x <= 2"
end

View File

@@ -0,0 +1,26 @@
def m1 x
if x > 2
puts "x is greater than 2"
elsif x <= 2 and x > 0 and !(x == 5)
puts "x is 1"
else
puts "I can't guess the number"
end
end
def m2 b
if b
return 0
end
return 1
end
def m3 x
if x < 0
x = -x
if x > 10
x = x - 1
end
end
puts x
end

View File

@@ -0,0 +1,22 @@
def m1 x
while x >= 0
puts x
x -= 1
end
end
def m2 x
while x >= 0
puts x
x -= 1
if x > 100
break
elsif x > 50
next
elsif x > 10
redo
end
puts "Iter"
end
puts "Done"
end

View File

@@ -0,0 +1,6 @@
def m1 x
if x > 2
raise "x > 2"
end
puts "x <= 2"
end