Merge pull request #157 from github/cfg_impl

Port CFG implementation to public AST interface
This commit is contained in:
Nick Rolfe
2021-03-22 14:57:43 +00:00
committed by GitHub
23 changed files with 1667 additions and 1720 deletions

View File

@@ -440,6 +440,9 @@ class ConditionalLoop extends Loop, TConditionalLoop {
or
pred = "getCondition" and result = this.getCondition()
}
/** Holds if the loop body is entered when the condition is `condValue`. */
predicate entersLoopWhenConditionIs(boolean condValue) { none() }
}
/**
@@ -463,6 +466,12 @@ class WhileExpr extends ConditionalLoop, TWhileExpr {
final override Expr getCondition() { toGenerated(result) = g.getCondition() }
/**
* Holds if the loop body is entered when the condition is `condValue`. For
* `while` loops, this holds when `condValue` is true.
*/
final override predicate entersLoopWhenConditionIs(boolean condValue) { condValue = true }
final override string toString() { result = "while ..." }
}
@@ -487,6 +496,12 @@ class UntilExpr extends ConditionalLoop, TUntilExpr {
final override Expr getCondition() { toGenerated(result) = g.getCondition() }
/**
* Holds if the loop body is entered when the condition is `condValue`. For
* `until` loops, this holds when `condValue` is false.
*/
final override predicate entersLoopWhenConditionIs(boolean condValue) { condValue = false }
final override string toString() { result = "until ..." }
}
@@ -505,6 +520,12 @@ class WhileModifierExpr extends ConditionalLoop, TWhileModifierExpr {
final override Expr getCondition() { toGenerated(result) = g.getCondition() }
/**
* Holds if the loop body is entered when the condition is `condValue`. For
* `while`-modifier loops, this holds when `condValue` is true.
*/
final override predicate entersLoopWhenConditionIs(boolean condValue) { condValue = true }
final override string getAPrimaryQlClass() { result = "WhileModifierExpr" }
final override string toString() { result = "... while ..." }
@@ -525,6 +546,12 @@ class UntilModifierExpr extends ConditionalLoop, TUntilModifierExpr {
final override Expr getCondition() { toGenerated(result) = g.getCondition() }
/**
* Holds if the loop body is entered when the condition is `condValue`. For
* `until`-modifier loops, this holds when `condValue` is false.
*/
final override predicate entersLoopWhenConditionIs(boolean condValue) { condValue = false }
final override string getAPrimaryQlClass() { result = "UntilModifierExpr" }
final override string toString() { result = "... until ..." }

View File

@@ -73,8 +73,8 @@ class StmtSequence extends Expr, TStmtSequence {
/** Gets a statement in this sequence. */
final Stmt getAStmt() { result = this.getStmt(_) }
/** Gets the last expression in this sequence, if any. */
final Expr getLastExpr() { result = this.getStmt(this.getNumberOfStatements() - 1) }
/** Gets the last statement in this sequence, if any. */
final Stmt getLastStmt() { result = this.getStmt(this.getNumberOfStatements() - 1) }
/** Gets the number of statements in this sequence. */
final int getNumberOfStatements() { result = count(this.getAStmt()) }

View File

@@ -4,7 +4,7 @@ private import internal.AST
private import internal.TreeSitter
/** A callable. */
class Callable extends Expr, CfgScope, TCallable {
class Callable extends Expr, TCallable {
/** Gets the number of parameters of this callable. */
final int getNumberOfParameters() { result = count(this.getAParameter()) }

View File

@@ -47,7 +47,8 @@ class Toplevel extends ModuleBase, TToplevel {
* Gets the `n`th `BEGIN` block.
*/
final BeginBlock getBeginBlock(int n) {
toGenerated(result) = rank[n](int i, Generated::BeginBlock b | b = g.getChild(i) | b order by i)
toGenerated(result) =
rank[n + 1](int i, Generated::BeginBlock b | b = g.getChild(i) | b order by i)
}
/**

View File

@@ -63,7 +63,14 @@ class TuplePattern extends Pattern, TTuplePattern {
or
result = toGenerated(this).(Generated::DestructuredLeftAssignment).getChild(i)
or
result = toGenerated(this).(Generated::LeftAssignmentList).getChild(i)
toGenerated(this) =
any(Generated::LeftAssignmentList lal |
if
strictcount(int j | exists(lal.getChild(j))) = 1 and
lal.getChild(0) instanceof Generated::DestructuredLeftAssignment
then result = lal.getChild(0).(Generated::DestructuredLeftAssignment).getChild(i)
else result = lal.getChild(i)
)
}
/** Gets the `i`th pattern in this tuple pattern. */

View File

@@ -15,7 +15,7 @@ class Stmt extends AstNode, TStmt {
CfgNodes::AstCfgNode getAControlFlowNode() { result.getNode() = this }
/** Gets the control-flow scope of this statement, if any. */
CfgScope getCfgScope() { result = getCfgScope(toGenerated(this)) }
CfgScope getCfgScope() { result = getCfgScope(this) }
/** Gets the enclosing callable, if any. */
Callable getEnclosingCallable() { result = this.getCfgScope() }

View File

@@ -90,7 +90,9 @@ private module Cached {
TComplexLiteral(Generated::Complex g) or
TDefinedExpr(Generated::Unary g) { g instanceof @unary_definedquestion } or
TDelimitedSymbolLiteral(Generated::DelimitedSymbol g) or
TDestructuredLeftAssignment(Generated::DestructuredLeftAssignment g) or
TDestructuredLeftAssignment(Generated::DestructuredLeftAssignment g) {
not strictcount(int i | exists(g.getParent().(Generated::LeftAssignmentList).getChild(i))) = 1
} or
TDivExpr(Generated::Binary g) { g instanceof @binary_slash } or
TDo(Generated::Do g) or
TDoBlock(Generated::DoBlock g) { not g.getParent() instanceof Generated::Lambda } or

View File

@@ -1,7 +1,6 @@
/** Provides classes representing nodes in a control flow graph. */
private import codeql_ruby.AST
private import codeql_ruby.ast.internal.AST
private import codeql_ruby.controlflow.BasicBlocks
private import ControlFlowGraph
private import internal.ControlFlowGraphImpl
@@ -67,7 +66,7 @@ class AstCfgNode extends CfgNode, TAstCfgNode {
private Splits splits;
private AstNode n;
AstCfgNode() { this = TAstCfgNode(toGenerated(n), splits) }
AstCfgNode() { this = TAstCfgNode(n, splits) }
final override AstNode getNode() { result = n }
@@ -132,7 +131,7 @@ abstract private class ExprChildMapping extends Expr {
pragma[noinline]
private BasicBlock getABasicBlockInScope() {
result.getANode() = TAstCfgNode(toGenerated(this.getAChildStar()), _)
result.getANode() = TAstCfgNode(this.getAChildStar(), _)
}
pragma[nomagic]
@@ -284,7 +283,7 @@ module ExprNodes {
}
private class StmtSequenceChildMapping extends ExprChildMapping, StmtSequence {
override predicate relevantChild(Expr e) { e = this.getLastExpr() }
override predicate relevantChild(Expr e) { e = this.getLastStmt() }
}
/** A control-flow node that wraps a `StmtSequence` AST expression. */
@@ -293,8 +292,8 @@ module ExprNodes {
final override StmtSequence getExpr() { result = ExprCfgNode.super.getExpr() }
/** Gets the last expression in this sequence, if any. */
final ExprCfgNode getLastExpr() { e.hasCfgChild(e.getLastExpr(), this, result) }
/** Gets the last statement in this sequence, if any. */
final ExprCfgNode getLastStmt() { e.hasCfgChild(e.getLastStmt(), this, result) }
}
private class ForExprChildMapping extends ExprChildMapping, ForExpr {

View File

@@ -1,8 +1,7 @@
/** Provides classes representing the control flow graph. */
private import codeql.Locations
private import codeql_ruby.AST as AST
private import codeql_ruby.ast.internal.AST as ASTInternal
private import codeql_ruby.AST
private import codeql_ruby.controlflow.BasicBlocks
private import SuccessorTypes
private import internal.ControlFlowGraphImpl
@@ -10,14 +9,14 @@ private import internal.Splitting
private import internal.Completion
/** An AST node with an associated control-flow graph. */
class CfgScope extends AST::AstNode {
CfgScope() { ASTInternal::toGenerated(this) instanceof CfgScope::Range_ }
class CfgScope extends Scope {
CfgScope() { this instanceof CfgScope::Range_ }
/** Gets the CFG scope that this scope is nested under, if any. */
final CfgScope getOuterCfgScope() {
exists(AST::AstNode parent |
exists(AstNode parent |
parent = this.getParent() and
result = getCfgScope(ASTInternal::toGenerated(parent))
result = getCfgScope(parent)
)
}
}
@@ -35,7 +34,7 @@ class CfgNode extends TCfgNode {
string toString() { none() }
/** Gets the AST node that this node corresponds to, if any. */
AST::AstNode getNode() { none() }
AstNode getNode() { none() }
/** Gets the location of this control flow node. */
Location getLocation() { none() }

View File

@@ -1,149 +0,0 @@
/**
* Provides various helper classes for AST nodes. The definitions in this file
* will likely be part of the hand-written user-facing AST layer.
*/
private import codeql_ruby.ast.internal.TreeSitter::Generated
private import codeql_ruby.controlflow.internal.Completion
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 or @conditional or @if_modifier or @unless or @unless_modifier;
class IfElsifAstNode extends AstNode, If_or_elisif {
AstNode getConditionNode() { none() }
AstNode getBranch(boolean b) { none() }
}
private class IfAstNode extends IfElsifAstNode, If {
override AstNode getConditionNode() { result = this.getCondition() }
override AstNode getBranch(boolean b) {
b = true and result = this.getConsequence()
or
b = false and result = this.getAlternative()
}
}
private class ElsifAstNode extends IfElsifAstNode, Elsif {
override AstNode getConditionNode() { result = this.getCondition() }
override AstNode getBranch(boolean b) {
b = true and result = this.getConsequence()
or
b = false and result = this.getAlternative()
}
}
private class ConditionalAstNode extends IfElsifAstNode, Conditional {
override AstNode getConditionNode() { result = this.getCondition() }
override AstNode getBranch(boolean b) {
b = true and result = this.getConsequence()
or
b = false and result = this.getAlternative()
}
}
private class IfModifierAstNode extends IfElsifAstNode, IfModifier {
override AstNode getConditionNode() { result = this.getCondition() }
override AstNode getBranch(boolean b) { b = true and result = this.getBody() }
}
private class UnlessAstNode extends IfElsifAstNode, Unless {
override AstNode getConditionNode() { result = this.getCondition() }
override AstNode getBranch(boolean b) {
b = false and result = this.getConsequence()
or
b = true and result = this.getAlternative()
}
}
private class UnlessModifierAstNode extends IfElsifAstNode, UnlessModifier {
override AstNode getConditionNode() { result = this.getCondition() }
override AstNode getBranch(boolean b) { b = false and result = this.getBody() }
}
private class CondLoop = @while or @while_modifier or @until or @until_modifier;
class ConditionalLoopAstNode extends AstNode, CondLoop {
AstNode getConditionNode() { none() }
AstNode getBodyNode() { none() }
predicate continueLoop(BooleanCompletion c) { c instanceof TrueCompletion }
final predicate endLoop(BooleanCompletion c) { continueLoop(c.getDual()) }
}
private class WhileLoop extends ConditionalLoopAstNode, While {
override UnderscoreStatement getConditionNode() { result = this.getCondition() }
override Do getBodyNode() { result = this.getBody() }
}
private class WhileModifierLoop extends ConditionalLoopAstNode, WhileModifier {
override AstNode getConditionNode() { result = this.getCondition() }
override UnderscoreStatement getBodyNode() { result = this.getBody() }
}
private class UntilLoop extends ConditionalLoopAstNode, Until {
override UnderscoreStatement getConditionNode() { result = this.getCondition() }
override Do getBodyNode() { result = this.getBody() }
override predicate continueLoop(BooleanCompletion c) { c instanceof FalseCompletion }
}
private class UntilModifierLoop extends ConditionalLoopAstNode, UntilModifier {
override AstNode getConditionNode() { result = this.getCondition() }
override UnderscoreStatement getBodyNode() { result = this.getBody() }
override predicate continueLoop(BooleanCompletion c) { c instanceof FalseCompletion }
}
class ParenthesizedStatement extends ParenthesizedStatements {
ParenthesizedStatement() { strictcount(int i | exists(this.getChild(i))) = 1 }
AstNode getChild() { result = this.getChild(0) }
}

View File

@@ -14,7 +14,7 @@ query predicate nodes(CfgNode n, string attr, string val) {
p
order by
p.getLocation().getFile().getBaseName(), p.getLocation().getFile().getAbsolutePath(),
p.getLocation().getStartLine()
p.getLocation().getStartLine(), p.getLocation().getStartColumn()
)
).toString()
}

View File

@@ -49,7 +49,7 @@ private predicate nestedEnsureCompletion(Completion outer, int nestLevel) {
or
outer = TExitCompletion()
) and
nestLevel = any(Trees::RescueEnsureBlockTree t).nestLevel()
nestLevel = any(Trees::BodyStmtTree t).getNestLevel()
}
pragma[noinline]
@@ -176,7 +176,7 @@ private predicate inBooleanContext(AstNode n) {
or
n = any(NotExpr parent | inBooleanContext(parent)).getOperand()
or
n = any(StmtSequence parent | inBooleanContext(parent)).getLastExpr()
n = any(StmtSequence parent | inBooleanContext(parent)).getLastStmt()
or
exists(CaseExpr c, WhenExpr w |
not exists(c.getValue()) and
@@ -206,7 +206,7 @@ private predicate inMatchingContext(AstNode n) {
w.getPattern(_) = n
)
or
toGenerated(n).(Trees::DefaultValueParameterTree).hasDefaultValue()
n.(Trees::DefaultValueParameterTree).hasDefaultValue()
}
/**

View File

@@ -1,4 +1,4 @@
private import codeql_ruby.ast.internal.TreeSitter::Generated
private import codeql_ruby.AST
private import codeql_ruby.CFG
private import Completion
private import Splitting

View File

@@ -2,9 +2,7 @@
* Provides classes and predicates relevant for splitting the control flow graph.
*/
private import codeql_ruby.ast.internal.TreeSitter::Generated
private import codeql_ruby.ast.internal.AST as ASTInternal
private import codeql_ruby.AST as AST
private import codeql_ruby.AST
private import Completion
private import ControlFlowGraphImpl
private import SuccessorTypes
@@ -18,13 +16,13 @@ private module Cached {
cached
newtype TSplitKind =
TConditionalCompletionSplitKind() or
TEnsureSplitKind(int nestLevel) { nestLevel = any(Trees::RescueEnsureBlockTree t).nestLevel() }
TEnsureSplitKind(int nestLevel) { nestLevel = any(Trees::BodyStmtTree t).getNestLevel() }
cached
newtype TSplit =
TConditionalCompletionSplit(ConditionalCompletion c) or
TEnsureSplit(EnsureSplitting::EnsureSplitType type, int nestLevel) {
nestLevel = any(Trees::RescueEnsureBlockTree t).nestLevel()
nestLevel = any(Trees::BodyStmtTree t).getNestLevel()
}
cached
@@ -219,28 +217,19 @@ private module ConditionalCompletionSplitting {
succ(pred, succ, c) and
last(succ, _, completion) and
(
last(ASTInternal::toGenerated(ASTInternal::fromGenerated(succ).(AST::NotExpr).getOperand()),
pred, c) and
last(succ.(NotExpr).getOperand(), pred, c) and
completion.(BooleanCompletion).getDual() = c
or
last(ASTInternal::toGenerated(ASTInternal::fromGenerated(succ)
.(AST::LogicalAndExpr)
.getAnOperand()), pred, c) and
last(succ.(LogicalAndExpr).getAnOperand(), pred, c) and
completion = c
or
last(ASTInternal::toGenerated(ASTInternal::fromGenerated(succ)
.(AST::LogicalOrExpr)
.getAnOperand()), pred, c) and
last(succ.(LogicalOrExpr).getAnOperand(), pred, c) and
completion = c
or
last(ASTInternal::toGenerated(ASTInternal::fromGenerated(succ)
.(AST::ParenthesizedExpr)
.getLastExpr()), pred, c) and
last(succ.(StmtSequence).getLastStmt(), pred, c) and
completion = c
or
last(ASTInternal::toGenerated(ASTInternal::fromGenerated(succ)
.(AST::ConditionalExpr)
.getBranch(_)), pred, c) and
last(succ.(ConditionalExpr).getBranch(_), pred, c) and
completion = c
)
}
@@ -255,7 +244,7 @@ private module ConditionalCompletionSplitting {
override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) {
this.appliesTo(last) and
succExit(ASTInternal::toGenerated(scope), last, c) and
succExit(scope, last, c) and
if c instanceof ConditionalCompletion then completion = c else any()
}
@@ -288,12 +277,11 @@ module EnsureSplitting {
/** A node that belongs to an `ensure` block. */
private class EnsureNode extends AstNode {
private Trees::RescueEnsureBlockTree block;
private Trees::BodyStmtTree block;
EnsureNode() { this = block.getAnEnsureDescendant() }
/** Gets the immediate block that this node belongs to. */
Trees::RescueEnsureBlockTree getBlock() { result = block }
int getNestLevel() { result = block.getNestLevel() }
/** Holds if this node is the entry node in the `ensure` block it belongs to. */
predicate isEntryNode() { first(block.getEnsure(), this) }
@@ -366,7 +354,7 @@ module EnsureSplitting {
pragma[noinline]
private predicate hasEntry0(AstNode pred, EnsureNode succ, int nestLevel, Completion c) {
succ.isEntryNode() and
nestLevel = succ.getBlock().nestLevel() and
nestLevel = succ.getNestLevel() and
succ(pred, succ, c)
}
@@ -389,11 +377,9 @@ module EnsureSplitting {
}
pragma[noinline]
private predicate exit0(
AstNode pred, Trees::RescueEnsureBlockTree block, int nestLevel, Completion c
) {
private predicate exit0(AstNode pred, Trees::BodyStmtTree block, int nestLevel, Completion c) {
this.appliesToPredecessor(pred) and
nestLevel = block.nestLevel() and
nestLevel = block.getNestLevel() and
block.lastInner(pred, c)
}
@@ -402,9 +388,7 @@ module EnsureSplitting {
* `inherited` indicates whether `c` is an inherited completion from the
* body.
*/
private predicate exit(
Trees::RescueEnsureBlockTree block, AstNode pred, Completion c, boolean inherited
) {
private predicate exit(Trees::BodyStmtTree block, AstNode pred, Completion c, boolean inherited) {
exists(EnsureSplitType type |
exit0(pred, block, this.getNestLevel(), c) and
type = this.getType()
@@ -472,7 +456,7 @@ module EnsureSplitting {
}
override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) {
succExit(ASTInternal::toGenerated(scope), last, c) and
succExit(scope, last, c) and
(
exit(_, last, c, _)
or
@@ -488,10 +472,10 @@ module EnsureSplitting {
if en.isEntryNode()
then
// entering a nested `ensure` block
en.getBlock().nestLevel() > this.getNestLevel()
en.getNestLevel() > this.getNestLevel()
else
// staying in the same (possibly nested) `ensure` block as `pred`
en.getBlock().nestLevel() >= this.getNestLevel()
en.getNestLevel() >= this.getNestLevel()
)
}
}
@@ -517,7 +501,7 @@ class Splits extends TSplits {
private predicate succEntrySplitsFromRank(CfgScope pred, AstNode succ, Splits splits, int rnk) {
splits = TSplitsNil() and
succEntry(ASTInternal::toGenerated(pred), succ) and
succEntry(pred, succ) and
rnk = 0
or
exists(SplitImpl head, Splits tail | succEntrySplitsCons(pred, succ, head, tail, rnk) |
@@ -540,7 +524,7 @@ private predicate succEntrySplitsCons(
pragma[noinline]
predicate succEntrySplits(CfgScope pred, AstNode succ, Splits succSplits, SuccessorType t) {
exists(int rnk |
succEntry(ASTInternal::toGenerated(pred), succ) and
succEntry(pred, succ) and
t instanceof NormalSuccessor and
succEntrySplitsFromRank(pred, succ, succSplits, rnk)
|
@@ -559,7 +543,7 @@ predicate succExitSplits(AstNode last, Splits predSplits, CfgScope scope, Succes
exists(Reachability::SameSplitsBlock b, Completion c | last = b.getANode() |
b.isReachable(predSplits) and
t = c.getAMatchingSuccessorType() and
succExit(ASTInternal::toGenerated(scope), last, c) and
succExit(scope, last, c) and
forall(SplitImpl predSplit | predSplit = predSplits.getASplit() |
predSplit.hasExitScope(scope, last, c)
)

View File

@@ -126,7 +126,7 @@ private module Cached {
or
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::AssignExprCfgNode).getRhs()
or
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::StmtSequenceCfgNode).getLastExpr()
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::StmtSequenceCfgNode).getLastStmt()
or
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::ConditionalExprCfgNode).getBranch(_)
or

View File

@@ -49,7 +49,7 @@ private predicate capturedExitRead(AnnotatedExitBasicBlock bb, int i, LocalVaria
i = bb.length()
}
private CfgScope getCaptureOuterCfgScope(Callable scope) {
private CfgScope getCaptureOuterCfgScope(CfgScope scope) {
result = scope.getOuterCfgScope() and
(
scope instanceof Block

View File

@@ -1185,9 +1185,8 @@ control/loops.rb:
# 24| getAnOperand/getRightOperand: [LocalVariableAccess] value
# 28| getStmt: [ForExpr] for ... in ...
# 28| getPattern: [TuplePattern] (..., ...)
# 28| getElement: [TuplePattern] (..., ...)
# 28| getElement: [LocalVariableAccess] key
# 28| getElement: [LocalVariableAccess] value
# 28| getElement: [LocalVariableAccess] key
# 28| getElement: [LocalVariableAccess] value
# 28| <in>: [???] In
# 28| getValue: [HashLiteral] {...}
# 28| getElement: [Pair] Pair

View File

@@ -1,5 +1,4 @@
import ruby
import codeql_ruby.ast.internal.TreeSitter
private string getMethodName(Call c) {
result = c.(MethodCall).getMethodName()

View File

@@ -29,7 +29,8 @@ forExprs
forExprsTuplePatterns
| loops.rb:22:1:25:3 | for ... in ... | loops.rb:22:5:22:14 | (..., ...) | 0 | loops.rb:22:5:22:7 | key |
| loops.rb:22:1:25:3 | for ... in ... | loops.rb:22:5:22:14 | (..., ...) | 1 | loops.rb:22:10:22:14 | value |
| loops.rb:28:1:32:3 | for ... in ... | loops.rb:28:5:28:16 | (..., ...) | 0 | loops.rb:28:5:28:16 | (..., ...) |
| loops.rb:28:1:32:3 | for ... in ... | loops.rb:28:5:28:16 | (..., ...) | 0 | loops.rb:28:6:28:8 | key |
| loops.rb:28:1:32:3 | for ... in ... | loops.rb:28:5:28:16 | (..., ...) | 1 | loops.rb:28:11:28:15 | value |
whileExprs
| loops.rb:35:1:39:3 | while ... | loops.rb:35:7:35:11 | ... < ... | loops.rb:35:12:39:3 | ...; ... | 0 | loops.rb:36:3:36:8 | ... += ... |
| loops.rb:35:1:39:3 | while ... | loops.rb:35:7:35:11 | ... < ... | loops.rb:35:12:39:3 | ...; ... | 1 | loops.rb:37:3:37:8 | ... += ... |

View File

@@ -3,6 +3,6 @@ toplevel
| modules.rb:1:1:61:3 | modules.rb | Toplevel |
| toplevel.rb:1:1:5:23 | toplevel.rb | Toplevel |
beginBlocks
| toplevel.rb:1:1:5:23 | toplevel.rb | 1 | toplevel.rb:5:1:5:22 | BEGIN { ... } |
| toplevel.rb:1:1:5:23 | toplevel.rb | 0 | toplevel.rb:5:1:5:22 | BEGIN { ... } |
endBlocks
| toplevel.rb:1:1:5:23 | toplevel.rb | toplevel.rb:3:1:3:18 | END { ... } |

File diff suppressed because it is too large Load Diff

View File

@@ -130,6 +130,9 @@ module M
Constant = 5
end
class EmptyClass; end
module EmptyModule; end
1/0 rescue puts "div by zero"
(*init, last) = 1, 2, 3