diff --git a/codeql b/codeql index fc2fe6cccb8..65ea01e1455 160000 --- a/codeql +++ b/codeql @@ -1 +1 @@ -Subproject commit fc2fe6cccb81b7d3ec5d9746ef05b07497ceb635 +Subproject commit 65ea01e1455b5f8c0f12a804f0d4715d64681d8c diff --git a/ql/consistency-queries/SsaConsistency.ql b/ql/consistency-queries/SsaConsistency.ql new file mode 100644 index 00000000000..8f17c6f9466 --- /dev/null +++ b/ql/consistency-queries/SsaConsistency.ql @@ -0,0 +1,22 @@ +import ruby +import codeql_ruby.dataflow.SSA +import codeql_ruby.controlflow.ControlFlowGraph + +query predicate nonUniqueDef(CfgNode read, Ssa::Definition def) { + read = def.getARead() and + exists(Ssa::Definition other | read = other.getARead() and other != def) +} + +query predicate readWithoutDef(LocalVariableReadAccess read) { + exists(CfgNode node | + node = read.getAControlFlowNode() and + not node = any(Ssa::Definition def).getARead() + ) +} + +query predicate deadDef(Ssa::Definition def, LocalVariable v) { + v = def.getSourceVariable() and + not v.isCaptured() and + not exists(def.getARead()) and + not def = any(Ssa::PhiNode phi).getAnInput() +} diff --git a/ql/src/codeql_ruby/AST.qll b/ql/src/codeql_ruby/AST.qll index a90e4b63965..b35c4e6607d 100644 --- a/ql/src/codeql_ruby/AST.qll +++ b/ql/src/codeql_ruby/AST.qll @@ -29,6 +29,7 @@ class AstNode extends @ast_node { string getAPrimaryQlClass() { result = "???" } /** Gets a textual representation of this node. */ + cached string toString() { result = "AstNode" } /** Gets the location of this node. */ diff --git a/ql/src/codeql_ruby/CFG.qll b/ql/src/codeql_ruby/CFG.qll new file mode 100644 index 00000000000..77507b05a7f --- /dev/null +++ b/ql/src/codeql_ruby/CFG.qll @@ -0,0 +1,5 @@ +/** Provides classes representing the control flow graph. */ + +import controlflow.ControlFlowGraph +import controlflow.CfgNodes as CfgNodes +import controlflow.BasicBlocks diff --git a/ql/src/codeql_ruby/ast/Expr.qll b/ql/src/codeql_ruby/ast/Expr.qll index 03463cb469e..3d75b346df1 100644 --- a/ql/src/codeql_ruby/ast/Expr.qll +++ b/ql/src/codeql_ruby/ast/Expr.qll @@ -1,5 +1,8 @@ private import codeql_ruby.AST +private import codeql_ruby.CFG private import internal.Expr +private import internal.Variable +private import codeql_ruby.controlflow.internal.ControlFlowGraphImpl /** * An expression. @@ -10,6 +13,18 @@ class Expr extends AstNode { Expr::Range range; Expr() { this = range } + + /** Gets a control-flow node for this expression, if any. */ + CfgNodes::AstCfgNode getAControlFlowNode() { result.getNode() = this } + + /** Gets the control-flow scope of this expression, if any. */ + CfgScope getCfgScope() { result = getCfgScope(this) } + + /** Gets the variable scope that this expression belongs to. */ + VariableScope getVariableScope() { result = enclosingScope(this) } + + /** Gets the enclosing callable, if any. */ + Callable getEnclosingCallable() { result = this.getCfgScope() } } /** diff --git a/ql/src/codeql_ruby/ast/Method.qll b/ql/src/codeql_ruby/ast/Method.qll index 5b8f5c5ccbd..d721dc0bca3 100644 --- a/ql/src/codeql_ruby/ast/Method.qll +++ b/ql/src/codeql_ruby/ast/Method.qll @@ -1,9 +1,10 @@ private import codeql_ruby.AST +private import codeql_ruby.controlflow.ControlFlowGraph private import internal.TreeSitter private import internal.Method /** A callable. */ -class Callable extends AstNode { +class Callable extends AstNode, CfgScope { Callable::Range range; Callable() { range = this } diff --git a/ql/src/codeql_ruby/ast/Parameter.qll b/ql/src/codeql_ruby/ast/Parameter.qll index 5c148958b73..b9ed67a144c 100644 --- a/ql/src/codeql_ruby/ast/Parameter.qll +++ b/ql/src/codeql_ruby/ast/Parameter.qll @@ -22,10 +22,10 @@ class Parameter extends AstNode { final int getPosition() { result = pos } /** Gets a variable introduced by this parameter. */ - Variable getAVariable() { none() } + LocalVariable getAVariable() { none() } /** Gets the variable named `name` introduced by this parameter. */ - final Variable getVariable(string name) { + final LocalVariable getVariable(string name) { result = this.getAVariable() and result.getName() = name } @@ -37,7 +37,7 @@ class Parameter extends AstNode { * This includes both simple parameters and tuple parameters. */ class PatternParameter extends Parameter, Pattern { - override Variable getAVariable() { result = Pattern.super.getAVariable() } + override LocalVariable getAVariable() { result = Pattern.super.getAVariable() } } /** A parameter defined using a tuple pattern. */ @@ -53,9 +53,9 @@ class NamedParameter extends Parameter { string getName() { none() } /** Gets the variable introduced by this parameter. */ - Variable getVariable() { none() } + LocalVariable getVariable() { none() } - override Variable getAVariable() { result = this.getVariable() } + override LocalVariable getAVariable() { result = this.getVariable() } /** Gets an access to this parameter. */ final VariableAccess getAnAccess() { result = this.getVariable().getAnAccess() } @@ -65,9 +65,9 @@ class NamedParameter extends Parameter { class SimpleParameter extends NamedParameter, PatternParameter, VariablePattern { final override string getName() { result = range.getVariableName() } - final override Variable getVariable() { result = TLocalVariable(_, _, this) } + final override LocalVariable getVariable() { result = TLocalVariable(_, _, this) } - final override Variable getAVariable() { result = this.getVariable() } + final override LocalVariable getAVariable() { result = this.getVariable() } final override string getAPrimaryQlClass() { result = "SimpleParameter" } @@ -85,7 +85,7 @@ class SimpleParameter extends NamedParameter, PatternParameter, VariablePattern class BlockParameter extends @block_parameter, NamedParameter { final override Generated::BlockParameter generated; - final override Variable getVariable() { result = TLocalVariable(_, _, generated.getName()) } + final override LocalVariable getVariable() { result = TLocalVariable(_, _, generated.getName()) } final override string getAPrimaryQlClass() { result = "BlockParameter" } @@ -106,7 +106,7 @@ class BlockParameter extends @block_parameter, NamedParameter { class HashSplatParameter extends @hash_splat_parameter, NamedParameter { final override Generated::HashSplatParameter generated; - final override Variable getVariable() { result = TLocalVariable(_, _, generated.getName()) } + final override LocalVariable getVariable() { result = TLocalVariable(_, _, generated.getName()) } final override string getAPrimaryQlClass() { result = "HashSplatParameter" } @@ -129,7 +129,7 @@ class HashSplatParameter extends @hash_splat_parameter, NamedParameter { class KeywordParameter extends @keyword_parameter, NamedParameter { final override Generated::KeywordParameter generated; - final override Variable getVariable() { result = TLocalVariable(_, _, generated.getName()) } + final override LocalVariable getVariable() { result = TLocalVariable(_, _, generated.getName()) } final override string getAPrimaryQlClass() { result = "KeywordParameter" } @@ -164,7 +164,7 @@ class KeywordParameter extends @keyword_parameter, NamedParameter { class OptionalParameter extends @optional_parameter, NamedParameter { final override Generated::OptionalParameter generated; - final override Variable getVariable() { result = TLocalVariable(_, _, generated.getName()) } + final override LocalVariable getVariable() { result = TLocalVariable(_, _, generated.getName()) } final override string getAPrimaryQlClass() { result = "OptionalParameter" } @@ -191,7 +191,7 @@ class OptionalParameter extends @optional_parameter, NamedParameter { class SplatParameter extends @splat_parameter, NamedParameter { final override Generated::SplatParameter generated; - final override Variable getVariable() { result = TLocalVariable(_, _, generated.getName()) } + final override LocalVariable getVariable() { result = TLocalVariable(_, _, generated.getName()) } final override string getAPrimaryQlClass() { result = "SplatParameter" } diff --git a/ql/src/codeql_ruby/ast/Variable.qll b/ql/src/codeql_ruby/ast/Variable.qll index a4c01ec7c97..8b524b504db 100644 --- a/ql/src/codeql_ruby/ast/Variable.qll +++ b/ql/src/codeql_ruby/ast/Variable.qll @@ -57,6 +57,25 @@ class LocalVariable extends Variable, TLocalVariable { override LocalVariable::Range range; final override LocalVariableAccess getAnAccess() { result.getVariable() = this } + + /** Gets the access where this local variable is first introduced. */ + VariableAccess getDefiningAccess() { result = range.getDefiningAccess() } + + /** + * Holds if this variable is captured. For example in + * + * ```rb + * def m x + * x.times do |y| + * puts x + * end + * puts x + * end + * ``` + * + * `x` is a captured variable, whereas `y` is not. + */ + predicate isCaptured() { this.getAnAccess().isCapturedAccess() } } /** A global variable. */ @@ -133,6 +152,23 @@ class LocalVariableAccess extends VariableAccess, @token_identifier { final override string getAPrimaryQlClass() { not this instanceof SimpleParameter and result = "LocalVariableAccess" } + + /** + * Holds if this access is a captured variable access. For example in + * + * ```rb + * def m x + * x.times do |y| + * puts x + * end + * puts x + * end + * ``` + * + * the access to `x` in the first `puts x` is a captured access, while + * the access to `x` in the second `puts x` is not. + */ + final predicate isCapturedAccess() { isCapturedAccess(this) } } /** An access to a local variable where the value is updated. */ diff --git a/ql/src/codeql_ruby/ast/internal/Pattern.qll b/ql/src/codeql_ruby/ast/internal/Pattern.qll index b2ecd5a6963..967a5ec300a 100644 --- a/ql/src/codeql_ruby/ast/internal/Pattern.qll +++ b/ql/src/codeql_ruby/ast/internal/Pattern.qll @@ -39,6 +39,7 @@ predicate implicitParameterAssignmentNode(Generated::AstNode n, Callable c) { module Pattern { abstract class Range extends AstNode { + cached Range() { explicitAssignmentNode(this, _) or diff --git a/ql/src/codeql_ruby/ast/internal/Variable.qll b/ql/src/codeql_ruby/ast/internal/Variable.qll index 914291dee64..85e0f996c55 100644 --- a/ql/src/codeql_ruby/ast/internal/Variable.qll +++ b/ql/src/codeql_ruby/ast/internal/Variable.qll @@ -9,11 +9,6 @@ private Generated::AstNode parent(Generated::AstNode n) { not n = any(VariableScope s).getScopeElement() } -/** Gets the enclosing scope for `node`. */ -private VariableScope enclosingScope(Generated::AstNode node) { - result.getScopeElement() = parent*(node.getParent()) -} - private predicate parameterAssignment( CallableScope::Range scope, string name, Generated::Identifier i ) { @@ -98,6 +93,12 @@ private class CapturingScope extends VariableScope { cached private module Cached { + /** Gets the enclosing scope for `node`. */ + cached + VariableScope enclosingScope(Generated::AstNode node) { + result.getScopeElement() = parent*(node.getParent()) + } + cached newtype TScope = TGlobalScope() or @@ -301,6 +302,11 @@ private module Cached { or scopeDefinesParameterVariable(_, _, access) } + + cached + predicate isCapturedAccess(LocalVariableAccess::Range access) { + access.getVariable().getDeclaringScope() != enclosingScope(access) + } } import Cached @@ -391,6 +397,8 @@ module LocalVariable { final override Location getLocation() { result = i.getLocation() } final override VariableScope getDeclaringScope() { result = scope } + + final VariableAccess getDefiningAccess() { result = i } } } diff --git a/ql/src/codeql_ruby/controlflow/BasicBlocks.qll b/ql/src/codeql_ruby/controlflow/BasicBlocks.qll index f9ba58f9c59..2ecdd5bafd0 100644 --- a/ql/src/codeql_ruby/controlflow/BasicBlocks.qll +++ b/ql/src/codeql_ruby/controlflow/BasicBlocks.qll @@ -4,6 +4,7 @@ private import codeql.Locations private import codeql_ruby.ast.internal.TreeSitter::Generated private import codeql_ruby.controlflow.ControlFlowGraph private import internal.ControlFlowGraphImpl +private import CfgNodes private import SuccessorTypes /** @@ -286,9 +287,7 @@ private module Cached { 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() - } + private predicate normalExitBB(BasicBlock bb) { bb.getANode().(AnnotatedExitNode).isNormal() } /** Holds if `dom` is an immediate post-dominator of `bb`. */ cached @@ -313,7 +312,7 @@ private module Cached { private import Cached /** Holds if `bb` is an entry basic block. */ -private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof CfgNodes::EntryNode } +private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof EntryNode } /** * An entry basic block, that is, a basic block whose first node is @@ -330,7 +329,17 @@ class EntryBasicBlock extends BasicBlock { * an annotated exit node. */ class AnnotatedExitBasicBlock extends BasicBlock { - AnnotatedExitBasicBlock() { this.getANode() instanceof CfgNodes::AnnotatedExitNode } + private boolean normal; + + AnnotatedExitBasicBlock() { + exists(AnnotatedExitNode n | + n = this.getANode() and + if n.isNormal() then normal = true else normal = false + ) + } + + /** Holds if this block represent a normal exit. */ + final predicate isNormal() { normal = true } } /** @@ -338,12 +347,10 @@ class AnnotatedExitBasicBlock extends BasicBlock { * an exit node. */ class ExitBasicBlock extends BasicBlock { - ExitBasicBlock() { this.getLastNode() instanceof CfgNodes::ExitNode } + ExitBasicBlock() { this.getLastNode() instanceof 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) diff --git a/ql/src/codeql_ruby/controlflow/CfgNodes.qll b/ql/src/codeql_ruby/controlflow/CfgNodes.qll new file mode 100644 index 00000000000..a0098df7a90 --- /dev/null +++ b/ql/src/codeql_ruby/controlflow/CfgNodes.qll @@ -0,0 +1,202 @@ +/** Provides classes representing nodes in a control flow graph. */ + +private import codeql_ruby.AST +private import codeql_ruby.ast.internal.TreeSitter +private import codeql_ruby.controlflow.BasicBlocks +private import ControlFlowGraph +private import internal.ControlFlowGraphImpl +private import internal.Splitting + +/** 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 } +} + +/** 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 + " (" + 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 } +} + +/** + * 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 Generated::AstNode n; + + AstCfgNode() { this = TAstNode(n, splits) } + + final override AstNode getNode() { result = n } + + final override string toString() { + exists(string s | + // TODO: Remove once the SSA implementation is based on the AST layer + s = n.(AstNode).toString() and + s != "AstNode" + or + n.(AstNode).toString() = "AstNode" and + s = n.toString() + | + result = "[" + this.getSplitsString() + "] " + s + or + not exists(this.getSplitsString()) and result = s + ) + } + + /** 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() } +} + +/** A control-flow node that wraps an AST expression. */ +class ExprCfgNode extends AstCfgNode { + Expr e; + + ExprCfgNode() { e = this.getNode() } + + /** Gets the underlying expression. */ + Expr getExpr() { result = e } +} + +/** + * A class for mapping parent-child AST nodes to parent-child CFG nodes. + */ +abstract private class ExprChildMapping extends Expr { + /** + * Holds if `child` is a (possibly nested) child of this expression + * for which we would like to find a matching CFG child. + */ + abstract predicate relevantChild(Expr child); + + private AstNode getAChildStar() { + result = this + or + result.(Generated::AstNode).getParent() = this.getAChildStar() + } + + pragma[noinline] + private BasicBlock getABasicBlockInScope() { result.getANode().getNode() = this.getAChildStar() } + + pragma[nomagic] + private predicate reachesBasicBlockBase(Expr child, CfgNode cfn, BasicBlock bb) { + this.relevantChild(child) and + cfn = this.getAControlFlowNode() and + bb.getANode() = cfn + } + + pragma[nomagic] + private predicate reachesBasicBlock(Expr child, CfgNode cfn, BasicBlock bb) { + this.reachesBasicBlockBase(child, cfn, bb) + or + this.relevantChild(child) and + this.reachesBasicBlockRec(child, cfn, bb) and + bb = this.getABasicBlockInScope() + } + + pragma[nomagic] + private predicate reachesBasicBlockRec(Expr child, CfgNode cfn, BasicBlock bb) { + exists(BasicBlock mid | this.reachesBasicBlock(child, cfn, mid) | + bb = mid.getASuccessor() + or + bb = mid.getAPredecessor() + ) + } + + /** + * Holds if there is a control-flow path from `cfn` to `cfnChild`, where `cfn` + * is a control-flow node for this expression, and `cfnChild` is a control-flow + * node for `child`. + * + * The path never escapes the syntactic scope of this expression. + */ + cached + predicate hasCfgChild(Expr child, CfgNode cfn, CfgNode cfnChild) { + exists(BasicBlock bb | + this.reachesBasicBlockBase(child, cfn, bb) and + cfnChild = bb.getANode() and + cfnChild = child.getAControlFlowNode() + ) + or + exists(BasicBlock bb | + this.reachesBasicBlockRec(child, cfn, bb) and + cfnChild = bb.getANode() and + cfnChild = child.getAControlFlowNode() + ) + } +} + +/** Provides classes for control-flow nodes that wrap AST expressions. */ +module ExprNodes { + // TODO: Add more classes + private class BinaryOperationExprChildMapping extends ExprChildMapping, BinaryOperation { + override predicate relevantChild(Expr e) { e = this.getAnOperand() } + } + + /** A control-flow node that wraps a `BinaryOperation` AST expression. */ + class BinaryOperationCfgNode extends ExprCfgNode { + override BinaryOperationExprChildMapping e; + + final override BinaryOperation getExpr() { result = ExprCfgNode.super.getExpr() } + + /** Gets the left operand of this binary operation. */ + final ExprCfgNode getLeftOperand() { e.hasCfgChild(e.getLeftOperand(), this, result) } + + /** Gets the right operand of this binary operation. */ + final ExprCfgNode getRightOperand() { e.hasCfgChild(e.getRightOperand(), this, result) } + } + + /** A control-flow node that wraps a `VariableReadAccess` AST expression. */ + class VariableReadAccessCfgNode extends ExprCfgNode { + override VariableReadAccess e; + + final override VariableReadAccess getExpr() { result = ExprCfgNode.super.getExpr() } + } +} diff --git a/ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll b/ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll index 8e85627c29f..43668d3e248 100644 --- a/ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll +++ b/ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll @@ -10,13 +10,16 @@ private import internal.Splitting private import internal.Completion /** An AST node with an associated control-flow graph. */ -class CfgScope extends AstNode { - CfgScope::Range_ range; +class CfgScope extends AST::AstNode { + CfgScope() { this instanceof CfgScope::Range_ } - CfgScope() { range = this } - - /** Gets the name of this scope. */ - string getName() { result = range.getName() } + /** Gets the CFG scope that this scope is nested under, if any. */ + final CfgScope getOuterCfgScope() { + exists(AstNode parent | + parent.getAFieldOrChild() = this and + result = getCfgScope(parent) + ) + } } /** @@ -32,7 +35,7 @@ class CfgNode extends TCfgNode { string toString() { none() } /** Gets the AST node that this node corresponds to, if any. */ - AstNode getNode() { none() } + AST::AstNode getNode() { none() } /** Gets the location of this control flow node. */ Location getLocation() { result = this.getNode().getLocation() } @@ -65,100 +68,6 @@ class CfgNode extends TCfgNode { 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() { - exists(string s | - // TODO: Remove once the SSA implementation is based on the AST layer - s = n.(AST::AstNode).toString() and - s != "AstNode" - or - n.(AST::AstNode).toString() = "AstNode" and - s = n.toString() - | - result = "[" + this.getSplitsString() + "] " + s - or - not exists(this.getSplitsString()) and result = s - ) - } - - /** 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. */ diff --git a/ql/src/codeql_ruby/controlflow/internal/Cfg.ql b/ql/src/codeql_ruby/controlflow/internal/Cfg.ql index 8ec24510d14..cf92f1d94e3 100644 --- a/ql/src/codeql_ruby/controlflow/internal/Cfg.ql +++ b/ql/src/codeql_ruby/controlflow/internal/Cfg.ql @@ -2,7 +2,7 @@ * @kind graph */ -import codeql_ruby.controlflow.ControlFlowGraph +import codeql_ruby.CFG query predicate nodes(CfgNode n) { any() } diff --git a/ql/src/codeql_ruby/controlflow/internal/Consistency.qll b/ql/src/codeql_ruby/controlflow/internal/Consistency.qll index b631ec3a405..ccec52fd56e 100644 --- a/ql/src/codeql_ruby/controlflow/internal/Consistency.qll +++ b/ql/src/codeql_ruby/controlflow/internal/Consistency.qll @@ -1,5 +1,5 @@ private import codeql_ruby.ast.internal.TreeSitter::Generated -private import codeql_ruby.controlflow.ControlFlowGraph +private import codeql_ruby.CFG private import Completion private import Splitting diff --git a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll index 9055e530277..546a1f4e102 100644 --- a/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll +++ b/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll @@ -41,24 +41,18 @@ private import codeql.files.FileSystem module CfgScope { abstract class Range_ extends AstNode { - abstract string getName(); - abstract predicate entry(AstNode first); abstract predicate exit(AstNode last, Completion c); } private class ProgramScope extends Range_, Program { - final override string getName() { result = "top-level" } - final override predicate entry(AstNode first) { first(this, first) } final override predicate exit(AstNode last, Completion c) { last(this, last, c) } } private class BeginBlockScope extends Range_, BeginBlock { - final override string getName() { result = "BEGIN block" } - final override predicate entry(AstNode first) { first(this.(Trees::BeginBlockTree).getFirstChildNode(), first) } @@ -69,8 +63,6 @@ module CfgScope { } private class EndBlockScope extends Range_, EndBlock { - final override string getName() { result = "END block" } - final override predicate entry(AstNode first) { first(this.(Trees::EndBlockTree).getFirstChildNode(), first) } @@ -83,8 +75,6 @@ module CfgScope { private class MethodScope extends Range_, AstNode { MethodScope() { this instanceof Method } - final override string getName() { result = this.(Method).getName().toString() } - final override predicate entry(AstNode first) { this.(Trees::RescueEnsureBlockTree).firstInner(first) } @@ -97,8 +87,6 @@ module CfgScope { private class SingletonMethodScope extends Range_, AstNode { SingletonMethodScope() { this instanceof SingletonMethod } - final override string getName() { result = this.(SingletonMethod).getName().toString() } - final override predicate entry(AstNode first) { this.(Trees::RescueEnsureBlockTree).firstInner(first) } @@ -111,8 +99,6 @@ module CfgScope { private class DoBlockScope extends Range_, DoBlock { DoBlockScope() { not this.getParent() instanceof Lambda } - final override string getName() { result = "do block" } - final override predicate entry(AstNode first) { this.(Trees::RescueEnsureBlockTree).firstInner(first) } @@ -125,8 +111,6 @@ module CfgScope { private class BlockScope extends Range_, Block { BlockScope() { not this.getParent() instanceof Lambda } - final override string getName() { result = "block" } - final override predicate entry(AstNode first) { first(this.(Trees::BlockTree).getFirstChildNode(), first) } @@ -137,8 +121,6 @@ module CfgScope { } private class LambdaScope extends Range_, Lambda { - final override string getName() { result = "lambda" } - final override predicate entry(AstNode first) { first(this.getParameters(), first) or @@ -168,9 +150,6 @@ private AstNode parent(AstNode n) { not n instanceof CfgScope } -/** Gets the CFG scope of node `n`. */ -CfgScope getScope(AstNode n) { result = unique(CfgScope scope | scope = parent+(n)) } - abstract private class ControlFlowTree extends AstNode { /** * Holds if `first` is the first element executed within this AST node. @@ -913,7 +892,7 @@ module Trees { pragma[noinline] private AstNode getAChildInScope(AstNode n, CfgScope scope) { result.getParent() = n and - scope = getScope(result) + scope = getCfgScope(result) } /** A block that may contain `rescue`/`ensure`. */ @@ -962,7 +941,7 @@ module Trees { or exists(AstNode mid | mid = this.getAnEnsureDescendant() and - result = getAChildInScope(mid, getScope(mid)) and + result = getAChildInScope(mid, getCfgScope(mid)) and not exists(RescueEnsureBlockTree nestedBlock | result = nestedBlock.getEnsure() and nestedBlock != this @@ -976,7 +955,7 @@ module Trees { */ private predicate nestedEnsure(RescueEnsureBlockTree innerBlock) { exists(Ensure innerEnsure | - innerEnsure = getAChildInScope(this.getAnEnsureDescendant(), getScope(this)) and + innerEnsure = getAChildInScope(this.getAnEnsureDescendant(), getCfgScope(this)) and innerEnsure = innerBlock.getEnsure() ) } @@ -1279,6 +1258,10 @@ module Trees { cached private module Cached { + /** Gets the CFG scope of node `n`. */ + cached + CfgScope getCfgScope(AstNode n) { result = unique(CfgScope scope | scope = parent+(n)) } + private predicate isAbnormalExitType(SuccessorType t) { t instanceof RaiseSuccessor or t instanceof ExitSuccessor } diff --git a/ql/src/codeql_ruby/controlflow/internal/Splitting.qll b/ql/src/codeql_ruby/controlflow/internal/Splitting.qll index bacb06148ba..7ae682dd520 100644 --- a/ql/src/codeql_ruby/controlflow/internal/Splitting.qll +++ b/ql/src/codeql_ruby/controlflow/internal/Splitting.qll @@ -74,7 +74,7 @@ private predicate overlapping(CfgScope scope, SplitKind sk1, SplitKind sk2) { exists(AstNode n | sk1.appliesTo(n) and sk2.appliesTo(n) and - scope = getScope(n) + scope = getCfgScope(n) ) } @@ -106,7 +106,7 @@ abstract private class SplitKind extends TSplitKind { */ predicate isEnabled(AstNode n) { this.appliesTo(n) and - this.getRank(getScope(n)) <= maxSplits() + this.getRank(getCfgScope(n)) <= maxSplits() } /** diff --git a/ql/src/codeql_ruby/dataflow/SSA.qll b/ql/src/codeql_ruby/dataflow/SSA.qll new file mode 100644 index 00000000000..de2604cb238 --- /dev/null +++ b/ql/src/codeql_ruby/dataflow/SSA.qll @@ -0,0 +1,362 @@ +/** + * Provides the module `Ssa` for working with static single assignment (SSA) form. + */ + +/** + * Provides classes for working with static single assignment (SSA) form. + */ +module Ssa { + private import codeql.Locations + private import codeql_ruby.CFG + private import codeql_ruby.ast.Variable + private import internal.SsaImplCommon as SsaImplCommon + private import internal.SsaImpl as SsaImpl + private import CfgNodes::ExprNodes + + /** A static single assignment (SSA) definition. */ + class Definition extends SsaImplCommon::Definition { + /** + * Gets the control flow node of this SSA definition, if any. Phi nodes are + * examples of SSA definitions without a control flow node, as they are + * modelled at index `-1` in the relevant basic block. + */ + final CfgNode getControlFlowNode() { + exists(BasicBlock bb, int i | this.definesAt(_, bb, i) | result = bb.getNode(i)) + } + + /** + * Gets a control-flow node that reads the value of this SSA definition. + * + * Example: + * + * ```rb + * def m b # defines b_0 + * i = 0 # defines i_0 + * puts i # reads i_0 + * puts i + 1 # reads i_0 + * if b # reads b_0 + * i = 1 # defines i_1 + * puts i # reads i_1 + * puts i + 1 # reads i_1 + * else + * i = 2 # defines i_2 + * puts i # reads i_2 + * puts i + 1 # reads i_2 + * end + * # defines i_3 = phi(i_1, i_2) + * puts i # reads i3 + * end + * ``` + */ + final VariableReadAccessCfgNode getARead() { + exists(LocalVariable v, BasicBlock bb, int i | + SsaImplCommon::ssaDefReachesRead(v, this, bb, i) and + SsaImpl::variableReadActual(bb, i, v) and + result = bb.getNode(i) + ) + } + + /** + * Gets a first control-flow node that reads the value of this SSA definition. + * That is, a read that can be reached from this definition without passing + * through other reads. + * + * Example: + * + * ```rb + * def m b # defines b_0 + * i = 0 # defines i_0 + * puts i # first read of i_0 + * puts i + 1 + * if b # first read of b_0 + * i = 1 # defines i_1 + * puts i # first read of i_1 + * puts i + 1 + * else + * i = 2 # defines i_2 + * puts i # first read of i_2 + * puts i + 1 + * end + * # defines i_3 = phi(i_1, i_2) + * puts i # first read of i3 + * end + * ``` + */ + final VariableReadAccessCfgNode getAFirstRead() { SsaImpl::firstRead(this, result) } + + /** + * Gets a last control-flow node that reads the value of this SSA definition. + * That is, a read that can reach the end of the enclosing CFG scope, or another + * SSA definition for the source variable, without passing through any other read. + * + * Example: + * + * ```rb + * def m b # defines b_0 + * i = 0 # defines i_0 + * puts i + * puts i + 1 # last read of i_0 + * if b # last read of b_0 + * i = 1 # defines i_1 + * puts i + * puts i + 1 # last read of i_1 + * else + * i = 2 # defines i_2 + * puts i + * puts i + 1 # last read of i_2 + * end + * # defines i_3 = phi(i_1, i_2) + * puts i # last read of i3 + * end + * ``` + */ + final VariableReadAccessCfgNode getALastRead() { SsaImpl::lastRead(this, result) } + + /** + * Holds if `read1` and `read2` are adjacent reads of this SSA definition. + * That is, `read2` can be reached from `read1` without passing through + * another read. + * + * Example: + * + * ```rb + * def m b + * i = 0 # defines i_0 + * puts i # reads i_0 (read1) + * puts i + 1 # reads i_0 (read2) + * if b + * i = 1 # defines i_1 + * puts i # reads i_1 (read1) + * puts i + 1 # reads i_1 (read2) + * else + * i = 2 # defines i_2 + * puts i # reads i_2 (read1) + * puts i + 1 # reads i_2 (read2) + * end + * puts i + * end + * ``` + */ + final predicate hasAdjacentReads( + VariableReadAccessCfgNode read1, VariableReadAccessCfgNode read2 + ) { + SsaImpl::adjacentReadPair(this, read1, read2) + } + + /** + * Gets a definition that ultimately defines this SSA definition and is + * not itself a phi node. + * + * Example: + * + * ```rb + * def m b + * i = 0 # defines i_0 + * puts i + * puts i + 1 + * if b + * i = 1 # defines i_1 + * puts i + * puts i + 1 + * else + * i = 2 # defines i_2 + * puts i + * puts i + 1 + * end + * # defines i_3 = phi(i_1, i_2); ultimate definitions are i_1 and i_2 + * puts i + * end + * ``` + */ + final override Definition getAnUltimateDefinition() { + result = SsaImplCommon::Definition.super.getAnUltimateDefinition() + } + + override string toString() { result = this.getControlFlowNode().toString() } + + /** Gets the location of this SSA definition. */ + Location getLocation() { result = this.getControlFlowNode().getLocation() } + } + + /** + * An SSA definition that corresponds to a write. For example `x = 10` in + * + * ```rb + * x = 10 + * puts x + * ``` + */ + class WriteDefinition extends Definition, SsaImplCommon::WriteDefinition { + private VariableWriteAccess write; + + WriteDefinition() { + exists(BasicBlock bb, int i, Variable v | + this.definesAt(v, bb, i) and + SsaImpl::variableWriteActual(bb, i, v, write) + ) + } + + /** Gets the underlying write access. */ + final VariableWriteAccess getWriteAccess() { result = write } + + final override string toString() { result = Definition.super.toString() } + + final override Location getLocation() { result = this.getControlFlowNode().getLocation() } + } + + /** + * An SSA definition inserted at the beginning of a scope to represent an + * uninitialized local variable. For example, in + * + * ```rb + * def m + * x = 10 if b + * puts x + * end + * ``` + * + * since the assignment to `x` is conditional, an unitialized definition for + * `x` is inserted at the start of `m`. + */ + class UninitializedDefinition extends Definition, SsaImplCommon::WriteDefinition { + UninitializedDefinition() { + exists(BasicBlock bb, int i, Variable v | + this.definesAt(v, bb, i) and + SsaImpl::uninitializedWrite(bb, i, v) + ) + } + + final override string toString() { result = "" } + + final override Location getLocation() { result = this.getBasicBlock().getLocation() } + } + + /** + * An SSA definition inserted at the beginning of a scope to represent a + * captured local variable. For example, in + * + * ```rb + * def m x + * y = 0 + * x.times do |x| + * y += x + * end + * return y + * end + * ``` + * + * an entry definition for `y` is inserted at the start of the `do` block. + */ + class CapturedEntryDefinition extends Definition, SsaImplCommon::WriteDefinition { + CapturedEntryDefinition() { + exists(BasicBlock bb, int i, Variable v | + this.definesAt(v, bb, i) and + SsaImpl::capturedEntryWrite(bb, i, v) + ) + } + + final override string toString() { result = "" } + + override Location getLocation() { result = this.getBasicBlock().getLocation() } + } + + /** + * An SSA definition inserted at a call that may update the value of a captured + * variable. For example, in + * + * ```rb + * def m x + * y = 0 + * x.times do |x| + * y += x + * end + * return y + * end + * ``` + * + * a definition for `y` is inserted at the call to `times`. + */ + class CapturedCallDefinition extends Definition, SsaImplCommon::UncertainWriteDefinition { + CapturedCallDefinition() { + exists(Variable v, BasicBlock bb, int i | + this.definesAt(v, bb, i) and + SsaImpl::capturedCallWrite(bb, i, v) + ) + } + + final override Definition getPriorDefinition() { + result = SsaImplCommon::UncertainWriteDefinition.super.getPriorDefinition() + } + + override string toString() { result = this.getControlFlowNode().toString() } + } + + /** + * A phi node. For example, in + * + * ```rb + * if b + * x = 0 + * else + * x = 1 + * end + * puts x + * ``` + * + * a phi node for `x` is inserted just before the call `puts x`. + */ + class PhiNode extends Definition, SsaImplCommon::PhiNode { + /** + * Gets an input of this phi node. + * + * Example: + * + * ```rb + * def m b + * i = 0 # defines i_0 + * puts i + * puts i + 1 + * if b + * i = 1 # defines i_1 + * puts i + * puts i + 1 + * else + * i = 2 # defines i_2 + * puts i + * puts i + 1 + * end + * # defines i_3 = phi(i_1, i_2); inputs are i_1 and i_2 + * puts i + * end + * ``` + */ + final override Definition getAnInput() { result = SsaImplCommon::PhiNode.super.getAnInput() } + + private string getSplitString() { + result = this.getBasicBlock().getFirstNode().(CfgNodes::AstCfgNode).getSplitsString() + } + + override string toString() { + exists(string prefix | + prefix = "[" + this.getSplitString() + "] " + or + not exists(this.getSplitString()) and + prefix = "" + | + result = prefix + "phi" + ) + } + + /* + * The location of a phi node is the same as the location of the first node + * in the basic block in which it is defined. + * + * Strictly speaking, the node is *before* the first node, but such a location + * does not exist in the source program. + */ + + final override Location getLocation() { + result = this.getBasicBlock().getFirstNode().getLocation() + } + } +} diff --git a/ql/src/codeql_ruby/dataflow/internal/SsaImpl.qll b/ql/src/codeql_ruby/dataflow/internal/SsaImpl.qll new file mode 100644 index 00000000000..0478ac7b20f --- /dev/null +++ b/ql/src/codeql_ruby/dataflow/internal/SsaImpl.qll @@ -0,0 +1,272 @@ +private import SsaImplCommon +private import codeql_ruby.AST +private import codeql_ruby.CFG +private import codeql_ruby.ast.Variable +private import CfgNodes::ExprNodes + +/** Holds if `v` is uninitialized at index `i` in entry block `bb`. */ +predicate uninitializedWrite(EntryBasicBlock bb, int i, LocalVariable v) { + v.getDeclaringScope().getScopeElement() = bb.getScope() and + i = -1 +} + +/** Holds if `bb` contains a caputured read of variable `v`. */ +pragma[noinline] +private predicate hasCapturedVariableRead(BasicBlock bb, LocalVariable v) { + exists(LocalVariableReadAccess read | + read = bb.getANode().getNode() and + read.isCapturedAccess() and + read.getVariable() = v + ) +} + +/** + * Holds if an entry definition is needed for captured variable `v` at index + * `i` in entry block `bb`. + */ +predicate capturedEntryWrite(EntryBasicBlock bb, int i, LocalVariable v) { + hasCapturedVariableRead(bb.getASuccessor*(), v) and + i = -1 +} + +/** Holds if `bb` contains a caputured write to variable `v`. */ +pragma[noinline] +private predicate writesCapturedVariable(BasicBlock bb, LocalVariable v) { + exists(LocalVariableWriteAccess write | + write = bb.getANode().getNode() and + write.isCapturedAccess() and + write.getVariable() = v + ) +} + +/** + * Holds if a pseudo read of captured variable `v` should be inserted + * at index `i` in exit block `bb`. + */ +private predicate capturedExitRead(AnnotatedExitBasicBlock bb, int i, LocalVariable v) { + bb.isNormal() and + writesCapturedVariable(bb.getAPredecessor*(), v) and + i = bb.length() +} + +private CfgScope getCaptureOuterCfgScope(Callable scope) { + result = scope.getOuterCfgScope() and + ( + scope instanceof Block + or + scope instanceof Lambda + ) +} + +/** Holds if captured variable `v` is read inside `scope`. */ +pragma[noinline] +private predicate hasCapturedRead(Variable v, CfgScope scope) { + any(LocalVariableReadAccess read | + read.getVariable() = v and scope = getCaptureOuterCfgScope*(read.getCfgScope()) + ).isCapturedAccess() +} + +pragma[noinline] +private predicate hasVariableWriteWithCapturedRead(BasicBlock bb, LocalVariable v, CfgScope scope) { + hasCapturedRead(v, scope) and + exists(VariableWriteAccess write | + write = bb.getANode().getNode() and + write.getVariable() = v and + bb.getScope() = scope.getOuterCfgScope() + ) +} + +/** + * Holds if the call at index `i` in basic block `bb` may reach a callable + * that reads captured variable `v`. + */ +private predicate capturedCallRead(BasicBlock bb, int i, LocalVariable v) { + exists(CfgScope scope | + hasVariableWriteWithCapturedRead(bb.getAPredecessor*(), v, scope) and + bb.getNode(i).getNode() instanceof Call + | + not scope instanceof Block + or + // If the read happens inside a block, we restrict to the call that + // contains the block + scope = any(Call c | bb.getNode(i) = c.getAControlFlowNode()).getBlock() + ) +} + +/** Holds if captured variable `v` is written inside `scope`. */ +pragma[noinline] +private predicate hasCapturedWrite(Variable v, CfgScope scope) { + any(LocalVariableWriteAccess write | + write.getVariable() = v and scope = getCaptureOuterCfgScope*(write.getCfgScope()) + ).isCapturedAccess() +} + +/** Holds if `v` is read at index `i` in basic block `bb`. */ +predicate variableReadActual(BasicBlock bb, int i, LocalVariable v) { + exists(VariableReadAccess read | + read.getVariable() = v and + read = bb.getNode(i).getNode() + ) +} + +/** + * Holds if a pseudo read of `v` is inserted at index `i` in basic block `bb`. + * + * Pseudo reads are used to make otherwise dead assignments live, as they will + * otherwise not get an SSA definition. + */ +predicate variableReadPseudo(BasicBlock bb, int i, LocalVariable v) { + capturedCallRead(bb, i, v) + or + capturedExitRead(bb, i, v) +} + +pragma[noinline] +private predicate hasVariableReadWithCapturedWrite(BasicBlock bb, LocalVariable v, CfgScope scope) { + hasCapturedWrite(v, scope) and + exists(VariableReadAccess read | + read = bb.getANode().getNode() and + read.getVariable() = v and + bb.getScope() = scope.getOuterCfgScope() + ) +} + +private predicate adjacentDefReaches(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { + adjacentDefRead(def, bb1, i1, bb2, i2) + or + exists(BasicBlock bb3, int i3 | + adjacentDefReaches(def, bb1, i1, bb3, i3) and + variableReadPseudo(bb3, i3, _) and + adjacentDefRead(def, bb3, i3, bb2, i2) + ) +} + +pragma[noinline] +private predicate adjacentDefActualRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 +) { + adjacentDefReaches(def, bb1, i1, bb2, i2) and + variableReadActual(bb2, i2, _) +} + +private predicate adjacentDefPseudoRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 +) { + adjacentDefReaches(def, bb1, i1, bb2, i2) and + variableReadPseudo(bb2, i2, _) +} + +private predicate reachesLastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { + lastRefRedef(def, bb, i, next) + or + exists(BasicBlock bb0, int i0 | + reachesLastRefRedef(def, bb0, i0, next) and + adjacentDefPseudoRead(def, bb, i, bb0, i0) + ) +} + +private predicate reachesLastRef(Definition def, BasicBlock bb, int i) { + lastRef(def, bb, i) + or + exists(BasicBlock bb0, int i0 | + reachesLastRef(def, bb0, i0) and + adjacentDefPseudoRead(def, bb, i, bb0, i0) + ) +} + +cached +private module Cached { + /** + * Holds if the call at index `i` in basic block `bb` may reach a callable + * that writes captured variable `v`. + */ + cached + predicate capturedCallWrite(BasicBlock bb, int i, LocalVariable v) { + exists(CfgScope scope | + hasVariableReadWithCapturedWrite(bb.getASuccessor*(), v, scope) and + bb.getNode(i).getNode() instanceof Call + | + not scope instanceof Block + or + // If the write happens inside a block, we restrict to the call that + // contains the block + scope = any(Call c | bb.getNode(i) = c.getAControlFlowNode()).getBlock() + ) + } + + /** + * Holds if `v` is written at index `i` in basic block `bb`, and the corresponding + * AST write access is `write`. + */ + cached + predicate variableWriteActual(BasicBlock bb, int i, LocalVariable v, VariableWriteAccess write) { + exists(AstNode n | + write.getVariable() = v and + n = bb.getNode(i).getNode() + | + write.isExplicitWrite(n) + or + write.isImplicitWrite() and + n = write + ) + } + + /** + * Holds if the value defined at SSA definition `def` can reach a read at `read`, + * without passing through any other non-pseudo read. + */ + cached + predicate firstRead(Definition def, VariableReadAccessCfgNode read) { + exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + def.definesAt(_, bb1, i1) and + adjacentDefActualRead(def, bb1, i1, bb2, i2) and + read = bb2.getNode(i2) + ) + } + + /** + * Holds if the read at `read2` is a read of the same SSA definition `def` + * as the read at `read1`, and `read2` can be reached from `read1` without + * passing through another non-pseudo read. + */ + cached + predicate adjacentReadPair( + Definition def, VariableReadAccessCfgNode read1, VariableReadAccessCfgNode read2 + ) { + exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + read1 = bb1.getNode(i1) and + variableReadActual(bb1, i1, _) and + adjacentDefActualRead(def, bb1, i1, bb2, i2) and + read2 = bb2.getNode(i2) + ) + } + + /** + * Holds if the read of `def` at `read` may be a last read. That is, `read` + * can either reach another definition of the underlying source variable or + * the end of the CFG scope, without passing through another non-pseudo read. + */ + cached + predicate lastRead(Definition def, VariableReadAccessCfgNode read) { + exists(BasicBlock bb, int i | + reachesLastRef(def, bb, i) and + variableReadActual(bb, i, _) and + read = bb.getNode(i) + ) + } + + /** + * Holds if the reference to `def` at index `i` in basic block `bb` can reach + * another definition `next` of the same underlying source variable, without + * passing through another write or non-pseudo read. + * + * The reference is either a read of `def` or `def` itself. + */ + cached + predicate lastRefBeforeRedef(Definition def, BasicBlock bb, int i, Definition next) { + reachesLastRefRedef(def, bb, i, next) and + not variableReadPseudo(bb, i, def.getSourceVariable()) + } +} + +import Cached diff --git a/ql/src/codeql_ruby/dataflow/internal/SsaImplCommon.qll b/ql/src/codeql_ruby/dataflow/internal/SsaImplCommon.qll new file mode 100644 index 00000000000..8bfef0053ac --- /dev/null +++ b/ql/src/codeql_ruby/dataflow/internal/SsaImplCommon.qll @@ -0,0 +1,590 @@ +/** + * Provides a language-independant implementation of static single assignment + * (SSA) form. + */ + +private import SsaImplSpecific + +private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb } + +cached +private module Cached { + /** + * Liveness analysis (based on source variables) to restrict the size of the + * SSA representation. + */ + private module Liveness { + /** + * A classification of variable references into reads (of a given kind) and + * (certain or uncertain) writes. + */ + private newtype TRefKind = + Read() or + Write(boolean certain) { certain = true or certain = false } + + private class RefKind extends TRefKind { + string toString() { + this = Read() and result = "read" + or + exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") + } + + int getOrder() { + this = Read() and + result = 0 + or + this = Write(_) and + result = 1 + } + } + + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. + */ + private predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { + variableRead(bb, i, v) and k = Read() + or + exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) + } + + private newtype OrderedRefIndex = + MkOrderedRefIndex(int i, int tag) { + exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) + } + + private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { + ref(bb, i, v, k) and + result = MkOrderedRefIndex(i, ord) and + ord = k.getOrder() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of + * basic block `bb`, which has the given reference kind `k`. + * + * Reads are considered before writes when they happen at the same index. + */ + private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { + refOrd(bb, i, v, k, _) = + rank[result](int j, int ord, OrderedRefIndex res | + res = refOrd(bb, j, v, _, ord) + | + res order by j, ord + ) + } + + private int maxRefRank(BasicBlock bb, SourceVariable v) { + result = refRank(bb, _, v, _) and + not result + 1 = refRank(bb, _, v, _) + } + + /** + * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` + * that is either a read or a certain write. + */ + private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { + result = + min(int r, RefKind k | + r = refRank(bb, _, v, k) and + k != Write(false) + | + r + ) + } + + /** + * Holds if source variable `v` is live at the beginning of basic block `bb`. + */ + predicate liveAtEntry(BasicBlock bb, SourceVariable v) { + // The first read or certain write to `v` inside `bb` is a read + refRank(bb, _, v, Read()) = firstReadOrCertainWrite(bb, v) + or + // There is no certain write to `v` inside `bb`, but `v` is live at entry + // to a successor basic block of `bb` + not exists(firstReadOrCertainWrite(bb, v)) and + liveAtExit(bb, v) + } + + /** + * Holds if source variable `v` is live at the end of basic block `bb`. + */ + predicate liveAtExit(BasicBlock bb, SourceVariable v) { + liveAtEntry(getABasicBlockSuccessor(bb), v) + } + + /** + * Holds if variable `v` is live in basic block `bb` at index `i`. + * The rank of `i` is `rnk` as defined by `refRank()`. + */ + private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { + exists(RefKind kind | rnk = refRank(bb, i, v, kind) | + rnk = maxRefRank(bb, v) and + liveAtExit(bb, v) + or + ref(bb, i, v, kind) and + kind = Read() + or + exists(RefKind nextKind | + liveAtRank(bb, _, v, rnk + 1) and + rnk + 1 = refRank(bb, _, v, nextKind) and + nextKind != Write(true) + ) + ) + } + + /** + * Holds if variable `v` is live after the (certain or uncertain) write at + * index `i` inside basic block `bb`. + */ + predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { + exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) + } + } + + private import Liveness + + /** Holds if `bb1` strictly dominates `bb2`. */ + private predicate strictlyDominates(BasicBlock bb1, BasicBlock bb2) { + bb1 = getImmediateBasicBlockDominator+(bb2) + } + + /** Holds if `bb1` dominates a predecessor of `bb2`. */ + private predicate dominatesPredecessor(BasicBlock bb1, BasicBlock bb2) { + exists(BasicBlock pred | pred = getABasicBlockPredecessor(bb2) | + bb1 = pred + or + strictlyDominates(bb1, pred) + ) + } + + /** Holds if `df` is in the dominance frontier of `bb`. */ + private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { + dominatesPredecessor(bb, df) and + not strictlyDominates(bb, df) + } + + /** + * Holds if `bb` is in the dominance frontier of a block containing a + * definition of `v`. + */ + pragma[noinline] + private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { + exists(BasicBlock defbb, Definition def | + def.definesAt(v, defbb, _) and + inDominanceFrontier(defbb, bb) + ) + } + + cached + newtype TDefinition = + TWriteDef(SourceVariable v, BasicBlock bb, int i) { + variableWrite(bb, i, v, _) and + liveAfterWrite(bb, i, v) + } or + TPhiNode(SourceVariable v, BasicBlock bb) { + inDefDominanceFrontier(bb, v) and + liveAtEntry(bb, v) + } + + private module SsaDefReaches { + newtype TSsaRefKind = + SsaRead() or + SsaDef() + + /** + * A classification of SSA variable references into reads and definitions. + */ + class SsaRefKind extends TSsaRefKind { + string toString() { + this = SsaRead() and + result = "SsaRead" + or + this = SsaDef() and + result = "SsaDef" + } + + int getOrder() { + this = SsaRead() and + result = 0 + or + this = SsaDef() and + result = 1 + } + } + + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v`, + * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` + * is `SsaDef()`). + * + * Unlike `Liveness::ref`, this includes `phi` nodes. + */ + predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { + variableRead(bb, i, v) and + k = SsaRead() + or + exists(Definition def | def.definesAt(v, bb, i)) and + k = SsaDef() + } + + private newtype OrderedSsaRefIndex = + MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } + + private OrderedSsaRefIndex ssaRefOrd( + BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord + ) { + ssaRef(bb, i, v, k) and + result = MkOrderedSsaRefIndex(i, k) and + ord = k.getOrder() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic + * block `bb`, which has the given reference kind `k`. + * + * For example, if `bb` is a basic block with a phi node for `v` (considered + * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: + * + * ```ql + * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node + * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 + * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 + * ``` + * + * Reads are considered before writes when they happen at the same index. + */ + int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { + ssaRefOrd(bb, i, v, k, _) = + rank[result](int j, int ord, OrderedSsaRefIndex res | + res = ssaRefOrd(bb, j, v, _, ord) + | + res order by j, ord + ) + } + + int maxSsaRefRank(BasicBlock bb, SourceVariable v) { + result = ssaRefRank(bb, _, v, _) and + not result + 1 = ssaRefRank(bb, _, v, _) + } + + /** + * Holds if the SSA definition `def` reaches rank index `rnk` in its own + * basic block `bb`. + */ + predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { + exists(int i | + rnk = ssaRefRank(bb, i, v, SsaDef()) and + def.definesAt(v, bb, i) + ) + or + ssaDefReachesRank(bb, def, rnk - 1, v) and + rnk = ssaRefRank(bb, _, v, SsaRead()) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches index `i` in the same + * basic block `bb`, without crossing another SSA definition of `v`. + */ + predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { + exists(int rnk | + ssaDefReachesRank(bb, def, rnk, v) and + rnk = ssaRefRank(bb, i, v, SsaRead()) and + variableRead(bb, i, v) + ) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches uncertain SSA definition + * `redef` in the same basic block, without crossing another SSA definition of `v`. + */ + predicate ssaDefReachesUncertainDefWithinBlock( + SourceVariable v, Definition def, UncertainWriteDefinition redef + ) { + exists(BasicBlock bb, int rnk, int i | + ssaDefReachesRank(bb, def, rnk, v) and + rnk = ssaRefRank(bb, i, v, SsaDef()) - 1 and + redef.definesAt(v, bb, i) + ) + } + + /** + * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. + */ + int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { + v = def.getSourceVariable() and + result = ssaRefRank(bb, i, v, k) and + ( + ssaDefReachesRead(_, def, bb, i) + or + def.definesAt(_, bb, i) + ) + } + + predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) { + exists(ssaDefRank(def, v, bb, _, _)) + } + + pragma[noinline] + private BasicBlock getAMaybeLiveSuccessor(Definition def, BasicBlock bb) { + result = getABasicBlockSuccessor(bb) and + not defOccursInBlock(_, bb, def.getSourceVariable()) and + ssaDefReachesEndOfBlock(bb, def, _) + } + + /** + * Holds if `def` is accessed in basic block `bb1` (either a read or a write), + * `bb2` is a transitive successor of `bb1`, `def` is live at the end of `bb1`, + * and the underlying variable for `def` is neither read nor written in any block + * on the path between `bb1` and `bb2`. + */ + predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { + defOccursInBlock(def, bb1, _) and + bb2 = getABasicBlockSuccessor(bb1) + or + exists(BasicBlock mid | varBlockReaches(def, bb1, mid) | + bb2 = getAMaybeLiveSuccessor(def, mid) + ) + } + + /** + * Holds if `def` is accessed in basic block `bb1` (either a read or a write), + * `def` is read at index `i2` in basic block `bb2`, `bb2` is in a transitive + * successor block of `bb1`, and `def` is neither read nor written in any block + * on a path between `bb1` and `bb2`. + */ + predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { + varBlockReaches(def, bb1, bb2) and + ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1 and + variableRead(bb2, i2, _) + } + } + + private import SsaDefReaches + + pragma[noinline] + private predicate ssaDefReachesEndOfBlockRec(BasicBlock bb, Definition def, SourceVariable v) { + exists(BasicBlock idom | ssaDefReachesEndOfBlock(idom, def, v) | + // The construction of SSA form ensures that each read of a variable is + // dominated by its definition. An SSA definition therefore reaches a + // control flow node if it is the _closest_ SSA definition that dominates + // the node. If two definitions dominate a node then one must dominate the + // other, so therefore the definition of _closest_ is given by the dominator + // tree. Thus, reaching definitions can be calculated in terms of dominance. + idom = getImmediateBasicBlockDominator(bb) + ) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches the end of basic + * block `bb`, at which point it is still live, without crossing another + * SSA definition of `v`. + */ + cached + predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { + exists(int last | last = maxSsaRefRank(bb, v) | + ssaDefReachesRank(bb, def, last, v) and + liveAtExit(bb, v) + ) + or + ssaDefReachesEndOfBlockRec(bb, def, v) and + liveAtExit(bb, v) and + not ssaRef(bb, _, v, SsaDef()) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in + * basic block `bb`, without crossing another SSA definition of `v`. The read + * is of kind `rk`. + */ + cached + predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { + ssaDefReachesReadWithinBlock(v, def, bb, i) + or + variableRead(bb, i, v) and + ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and + not ssaDefReachesReadWithinBlock(v, _, bb, i) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches uncertain SSA definition + * `redef` without crossing another SSA definition of `v`. + */ + cached + predicate ssaDefReachesUncertainDef( + SourceVariable v, Definition def, UncertainWriteDefinition redef + ) { + ssaDefReachesUncertainDefWithinBlock(v, def, redef) + or + exists(BasicBlock bb | + redef.definesAt(v, bb, _) and + ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and + not ssaDefReachesUncertainDefWithinBlock(v, _, redef) + ) + } + + /** + * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read + * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a + * path between them without any read of `def`. + */ + cached + predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { + exists(int rnk | + rnk = ssaDefRank(def, _, bb1, i1, _) and + rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaRead()) and + variableRead(bb1, i2, _) and + bb2 = bb1 + ) + or + exists(SourceVariable v | ssaDefRank(def, v, bb1, i1, _) = maxSsaRefRank(bb1, v)) and + defAdjacentRead(def, bb1, bb2, i2) + } + + /** + * Holds if the node at index `i` in `bb` is a last reference to SSA definition + * `def`. The reference is last because it can reach another write `next`, + * without passing through another read or write. + */ + cached + predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { + exists(int rnk, SourceVariable v, int j | rnk = ssaDefRank(def, v, bb, i, _) | + // Next reference to `v` inside `bb` is a write + next.definesAt(v, bb, j) and + rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) + or + // Can reach a write using one or more steps + rnk = maxSsaRefRank(bb, v) and + exists(BasicBlock bb2 | + varBlockReaches(def, bb, bb2) and + next.definesAt(v, bb2, j) and + 1 = ssaRefRank(bb2, j, v, SsaDef()) + ) + ) + } + + /** + * Holds if the node at index `i` in `bb` is a last reference to SSA + * definition `def`. + * + * That is, the node can reach the end of the enclosing callable, or another + * SSA definition for the underlying source variable, without passing through + * another read. + */ + cached + predicate lastRef(Definition def, BasicBlock bb, int i) { + lastRefRedef(def, bb, i, _) + or + exists(SourceVariable v | ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) | + // Can reach exit directly + bb instanceof ExitBasicBlock + or + // Can reach a block using one or more steps, where `def` is no longer live + exists(BasicBlock bb2 | varBlockReaches(def, bb, bb2) | + not defOccursInBlock(def, bb2, _) and + not ssaDefReachesEndOfBlock(bb2, def, _) + ) + ) + } +} + +import Cached + +/** A static single assignment (SSA) definition. */ +class Definition extends TDefinition { + /** Gets the source variable underlying this SSA definition. */ + SourceVariable getSourceVariable() { this.definesAt(result, _, _) } + + /** + * Holds is this SSA definition is live at the end of basic block `bb`. + * That is, this definition reaches the end of basic block `bb`, at which + * point it is still live, without crossing another SSA definition of the + * same source variable. + */ + final predicate isLiveAtEndOfBlock(BasicBlock bb) { ssaDefReachesEndOfBlock(bb, this, _) } + + /** + * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. + * Phi nodes are considered to be at index `-1`, while normal variable writes + * are at the index of the control flow node they wrap. + */ + final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { + this = TWriteDef(v, bb, i) + or + this = TPhiNode(v, bb) and i = -1 + } + + /** Gets the basic block to which this SSA definition belongs. */ + final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } + + /** + * Gets an SSA definition whose value can flow to this one in one step. This + * includes inputs to phi nodes and the prior definitions of uncertain writes. + */ + private Definition getAPseudoInputOrPriorDefinition() { + result = this.(PhiNode).getAnInput() or + result = this.(UncertainWriteDefinition).getPriorDefinition() + } + + /** + * Gets a definition that ultimately defines this SSA definition and is + * not itself a phi node. + */ + Definition getAnUltimateDefinition() { + result = this.getAPseudoInputOrPriorDefinition*() and + not result instanceof PhiNode + } + + /** Gets a textual representation of this SSA definition. */ + string toString() { none() } +} + +/** An SSA definition that corresponds to a write. */ +class WriteDefinition extends Definition, TWriteDef { + private SourceVariable v; + private BasicBlock bb; + private int i; + + WriteDefinition() { this = TWriteDef(v, bb, i) } + + override string toString() { result = "WriteDef" } +} + +/** A phi node. */ +class PhiNode extends Definition, TPhiNode { + /** Gets an input of this phi node. */ + Definition getAnInput() { + exists(BasicBlock bb, BasicBlock pred, SourceVariable v | + this.definesAt(v, bb, _) and + getABasicBlockPredecessor(bb) = pred and + ssaDefReachesEndOfBlock(pred, result, v) + ) + } + + /** Holds if `inp` is an input to the phi node along the edge originating in `bb`. */ + predicate hasInputFromBlock(Definition inp, BasicBlock bb) { + inp = this.getAnInput() and + getABasicBlockPredecessor(this.getBasicBlock()) = bb and + ssaDefReachesEndOfBlock(bb, inp, _) + } + + override string toString() { result = "Phi" } +} + +/** + * An SSA definition that represents an uncertain update of the underlying + * source variable. + */ +class UncertainWriteDefinition extends WriteDefinition { + UncertainWriteDefinition() { + exists(SourceVariable v, BasicBlock bb, int i | + this.definesAt(v, bb, i) and + variableWrite(bb, i, v, false) + ) + } + + /** + * Gets the immediately preceding definition. Since this update is uncertain, + * the value from the preceding definition might still be valid. + */ + Definition getPriorDefinition() { ssaDefReachesUncertainDef(_, result, this) } +} diff --git a/ql/src/codeql_ruby/dataflow/internal/SsaImplSpecific.qll b/ql/src/codeql_ruby/dataflow/internal/SsaImplSpecific.qll new file mode 100644 index 00000000000..f5d7e8d34ca --- /dev/null +++ b/ql/src/codeql_ruby/dataflow/internal/SsaImplSpecific.qll @@ -0,0 +1,38 @@ +/** Provides the Ruby specific parameters for `SsaImplCommon.qll`. */ + +private import SsaImpl as SsaImpl +private import codeql_ruby.AST +private import codeql_ruby.ast.Parameter +private import codeql_ruby.ast.Variable +private import codeql_ruby.controlflow.BasicBlocks as BasicBlocks +private import codeql_ruby.controlflow.ControlFlowGraph + +class BasicBlock = BasicBlocks::BasicBlock; + +BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() } + +BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } + +class ExitBasicBlock = BasicBlocks::ExitBasicBlock; + +class SourceVariable = LocalVariable; + +predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { + ( + SsaImpl::uninitializedWrite(bb, i, v) + or + SsaImpl::capturedEntryWrite(bb, i, v) + or + SsaImpl::variableWriteActual(bb, i, v, _) + ) and + certain = true + or + SsaImpl::capturedCallWrite(bb, i, v) and + certain = false +} + +predicate variableRead(BasicBlock bb, int i, SourceVariable v) { + SsaImpl::variableReadActual(bb, i, v) + or + SsaImpl::variableReadPseudo(bb, i, v) +} diff --git a/ql/src/queries/variables/DeadStoreOfLocal.ql b/ql/src/queries/variables/DeadStoreOfLocal.ql new file mode 100644 index 00000000000..e8f86f71630 --- /dev/null +++ b/ql/src/queries/variables/DeadStoreOfLocal.ql @@ -0,0 +1,28 @@ +/** + * @name Useless assignment to local variable + * @description An assignment to a local variable that is not used later on, or whose value is always + * overwritten, has no effect. + * @kind problem + * @problem.severity warning + * @id rb/useless-assignment-to-local + * @tags maintainability + * external/cwe/cwe-563 + * @precision low + */ + +import ruby +import codeql_ruby.dataflow.SSA + +class RelevantLocalVariableWriteAccess extends LocalVariableWriteAccess { + RelevantLocalVariableWriteAccess() { + not this.getVariable().getName().charAt(0) = "_" and + not this = any(Parameter p).getAVariable().getDefiningAccess() + } +} + +from RelevantLocalVariableWriteAccess write, LocalVariable v +where + v = write.getVariable() and + exists(write.getAControlFlowNode()) and + not exists(Ssa::WriteDefinition def | def.getWriteAccess() = write) +select write, "This assignment to $@ is useless, since its value is never read.", v, v.getName() diff --git a/ql/src/queries/variables/UninitializedLocal.ql b/ql/src/queries/variables/UninitializedLocal.ql new file mode 100644 index 00000000000..081ab771b7b --- /dev/null +++ b/ql/src/queries/variables/UninitializedLocal.ql @@ -0,0 +1,32 @@ +/** + * @name Potentially uninitialized local variable + * @description Using a local variable before it is initialized gives the variable a default + * 'nil' value. + * @kind problem + * @problem.severity error + * @id rb/uninitialized-local-variable + * @tags reliability + * correctness + * @precision low + */ + +import ruby +import codeql_ruby.dataflow.SSA + +class RelevantLocalVariableReadAccess extends LocalVariableReadAccess { + RelevantLocalVariableReadAccess() { + not exists(Call c | + c.getReceiver() = this and + c.getMethodName() = "nil?" + ) + } +} + +from RelevantLocalVariableReadAccess read, LocalVariable v +where + v = read.getVariable() and + exists(Ssa::Definition def | + def.getAnUltimateDefinition() instanceof Ssa::UninitializedDefinition and + read = def.getARead().getExpr() + ) +select read, "Local variable $@ may be used before it is initialized.", v, v.getName() diff --git a/ql/src/queries/variables/UnusedParameter.ql b/ql/src/queries/variables/UnusedParameter.ql new file mode 100644 index 00000000000..afad3df545b --- /dev/null +++ b/ql/src/queries/variables/UnusedParameter.ql @@ -0,0 +1,27 @@ +/** + * @name Unused parameter. + * @description A parameter that is not used later on, or whose value is always overwritten, + * can be removed. + * @kind problem + * @problem.severity warning + * @id rb/unused-parameter + * @tags maintainability + * external/cwe/cwe-563 + * @precision low + */ + +import ruby +import codeql_ruby.dataflow.SSA + +class RelevantParameterVariable extends LocalVariable { + RelevantParameterVariable() { + exists(Parameter p | + this = p.getAVariable() and + not this.getName().charAt(0) = "_" + ) + } +} + +from RelevantParameterVariable v +where not exists(Ssa::WriteDefinition def | def.getWriteAccess() = v.getDefiningAccess()) +select v, "Unused parameter." diff --git a/ql/test/library-tests/controlflow/graph/Cfg.expected b/ql/test/library-tests/controlflow/graph/Cfg.expected index 630d18bcf13..a84894326e5 100644 --- a/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -1,5 +1,5 @@ break_ensure.rb: -# 1| enter top-level +# 1| enter AstNode #-----| -> m1 # 1| enter m1 @@ -15,26 +15,26 @@ break_ensure.rb: #-----| -> elements case.rb: -# 1| enter top-level +# 1| enter AstNode #-----| -> if_in_case # 1| enter if_in_case #-----| -> case ... cfg.rb: -# 1| enter top-level +# 1| enter AstNode #-----| -> bar -# 15| enter BEGIN block +# 15| enter AstNode #-----| -> puts -# 19| enter END block +# 19| enter AstNode #-----| -> puts -# 25| enter block +# 25| enter { ... } #-----| -> x -# 29| enter block +# 29| enter { ... } #-----| -> x # 63| enter pattern @@ -46,7 +46,7 @@ cfg.rb: # 101| enter parameters #-----| -> value -# 120| enter lambda +# 120| enter -> { ... } #-----| -> x # 142| enter print @@ -61,11 +61,11 @@ cfg.rb: # 187| enter run_block #-----| -> call to yield -# 191| enter block +# 191| enter { ... } #-----| -> x exit.rb: -# 1| enter top-level +# 1| enter AstNode #-----| -> m1 # 1| enter m1 @@ -75,14 +75,14 @@ exit.rb: #-----| -> x heredoc.rb: -# 1| enter top-level +# 1| enter AstNode #-----| -> double_heredoc # 1| enter double_heredoc #-----| -> puts ifs.rb: -# 1| enter top-level +# 1| enter AstNode #-----| -> m1 # 1| enter m1 @@ -107,7 +107,7 @@ ifs.rb: #-----| -> true loops.rb: -# 1| enter top-level +# 1| enter AstNode #-----| -> m1 # 1| enter m1 @@ -119,11 +119,11 @@ loops.rb: # 24| enter m3 #-----| -> 1 -# 25| enter do block +# 25| enter do ... end #-----| -> x raise.rb: -# 1| enter top-level +# 1| enter AstNode #-----| -> Class # 7| enter m1 @@ -168,7 +168,7 @@ raise.rb: # 154| enter m14 #-----| -> element -# 155| enter block +# 155| enter { ... } #-----| -> elem break_ensure.rb: @@ -410,7 +410,7 @@ break_ensure.rb: #-----| -> call to puts # 44| m4 -#-----| -> exit top-level (normal) +#-----| -> exit AstNode (normal) # 44| m4 #-----| -> m4 @@ -496,7 +496,7 @@ break_ensure.rb: case.rb: # 1| if_in_case -#-----| -> exit top-level (normal) +#-----| -> exit AstNode (normal) # 1| if_in_case #-----| -> if_in_case @@ -617,7 +617,7 @@ cfg.rb: #-----| -> EndBlock # 16| call to puts -#-----| -> exit BEGIN block (normal) +#-----| -> exit AstNode (normal) # 16| puts #-----| -> hello @@ -629,7 +629,7 @@ cfg.rb: #-----| -> 41 # 20| call to puts -#-----| -> exit END block (normal) +#-----| -> exit AstNode (normal) # 20| puts #-----| -> world @@ -662,7 +662,7 @@ cfg.rb: #-----| -> puts # 25| call to puts -#-----| -> exit block (normal) +#-----| -> exit { ... } (normal) # 25| puts #-----| -> x @@ -698,7 +698,7 @@ cfg.rb: #-----| -> x # 29| call to call -#-----| -> exit block (normal) +#-----| -> exit { ... } (normal) # 29| x #-----| -> call @@ -1399,7 +1399,7 @@ cfg.rb: #-----| -> (..., ...) # 120| Array -#-----| -> exit lambda (normal) +#-----| -> exit -> { ... } (normal) # 120| y #-----| -> x @@ -2021,7 +2021,7 @@ cfg.rb: # 188| 42 # 191| call to run_block -#-----| -> exit top-level (normal) +#-----| -> exit AstNode (normal) # 191| run_block #-----| -> { ... } @@ -2033,7 +2033,7 @@ cfg.rb: #-----| -> puts # 191| call to puts -#-----| -> exit block (normal) +#-----| -> exit { ... } (normal) # 191| puts #-----| -> x @@ -2083,7 +2083,7 @@ exit.rb: #-----| -> call to puts # 8| m2 -#-----| -> exit top-level (normal) +#-----| -> exit AstNode (normal) # 8| m2 #-----| -> m2 @@ -2124,7 +2124,7 @@ exit.rb: heredoc.rb: # 1| double_heredoc -#-----| -> exit top-level (normal) +#-----| -> exit AstNode (normal) # 1| double_heredoc #-----| -> double_heredoc @@ -2501,7 +2501,7 @@ ifs.rb: #-----| -> ... == ... # 40| constant_condition -#-----| -> exit top-level (normal) +#-----| -> exit AstNode (normal) # 40| constant_condition #-----| -> constant_condition @@ -2663,7 +2663,7 @@ loops.rb: #-----| -> call to puts # 24| m3 -#-----| -> exit top-level (normal) +#-----| -> exit AstNode (normal) # 24| m3 #-----| -> m3 @@ -2693,7 +2693,7 @@ loops.rb: #-----| -> puts # 26| call to puts -#-----| -> exit do block (normal) +#-----| -> exit do ... end (normal) # 26| puts #-----| -> x @@ -3704,7 +3704,7 @@ raise.rb: #-----| -> exit m13 (normal) # 154| m14 -#-----| -> exit top-level (normal) +#-----| -> exit AstNode (normal) # 154| m14 #-----| -> m14 @@ -3728,10 +3728,10 @@ raise.rb: #-----| -> element # 155| ... if ... -#-----| -> exit block (normal) +#-----| -> exit { ... } (normal) # 155| call to raise -#-----| raise -> exit block (abnormal) +#-----| raise -> exit { ... } (abnormal) # 155| raise #-----| -> @@ -3750,7 +3750,7 @@ raise.rb: #-----| -> call to nil? break_ensure.rb: -# 1| exit top-level +# 1| exit AstNode # 1| exit m1 @@ -3761,20 +3761,20 @@ break_ensure.rb: # 44| exit m4 case.rb: -# 1| exit top-level +# 1| exit AstNode # 1| exit if_in_case cfg.rb: -# 1| exit top-level +# 1| exit AstNode -# 15| exit BEGIN block +# 15| exit AstNode -# 19| exit END block +# 19| exit AstNode -# 25| exit block +# 25| exit { ... } -# 29| exit block +# 29| exit { ... } # 63| exit pattern @@ -3782,7 +3782,7 @@ cfg.rb: # 101| exit parameters -# 120| exit lambda +# 120| exit -> { ... } # 142| exit print @@ -3790,22 +3790,22 @@ cfg.rb: # 153| exit two_parameters -# 191| exit block +# 191| exit { ... } exit.rb: -# 1| exit top-level +# 1| exit AstNode # 1| exit m1 # 8| exit m2 heredoc.rb: -# 1| exit top-level +# 1| exit AstNode # 1| exit double_heredoc ifs.rb: -# 1| exit top-level +# 1| exit AstNode # 1| exit m1 @@ -3822,7 +3822,7 @@ ifs.rb: # 40| exit constant_condition loops.rb: -# 1| exit top-level +# 1| exit AstNode # 1| exit m1 @@ -3830,10 +3830,10 @@ loops.rb: # 24| exit m3 -# 25| exit do block +# 25| exit do ... end raise.rb: -# 1| exit top-level +# 1| exit AstNode # 7| exit m1 @@ -3863,11 +3863,11 @@ raise.rb: # 154| exit m14 -# 155| exit block +# 155| exit { ... } break_ensure.rb: -# 1| exit top-level (normal) -#-----| -> exit top-level +# 1| exit AstNode (normal) +#-----| -> exit AstNode # 1| exit m1 (normal) #-----| -> exit m1 @@ -3885,27 +3885,27 @@ break_ensure.rb: #-----| -> exit m4 case.rb: -# 1| exit top-level (normal) -#-----| -> exit top-level +# 1| exit AstNode (normal) +#-----| -> exit AstNode # 1| exit if_in_case (normal) #-----| -> exit if_in_case cfg.rb: -# 1| exit top-level (normal) -#-----| -> exit top-level +# 1| exit AstNode (normal) +#-----| -> exit AstNode -# 15| exit BEGIN block (normal) -#-----| -> exit BEGIN block +# 15| exit AstNode (normal) +#-----| -> exit AstNode -# 19| exit END block (normal) -#-----| -> exit END block +# 19| exit AstNode (normal) +#-----| -> exit AstNode -# 25| exit block (normal) -#-----| -> exit block +# 25| exit { ... } (normal) +#-----| -> exit { ... } -# 29| exit block (normal) -#-----| -> exit block +# 29| exit { ... } (normal) +#-----| -> exit { ... } # 63| exit pattern (normal) #-----| -> exit pattern @@ -3916,8 +3916,8 @@ cfg.rb: # 101| exit parameters (normal) #-----| -> exit parameters -# 120| exit lambda (normal) -#-----| -> exit lambda +# 120| exit -> { ... } (normal) +#-----| -> exit -> { ... } # 142| exit print (normal) #-----| -> exit print @@ -3928,12 +3928,12 @@ cfg.rb: # 153| exit two_parameters (normal) #-----| -> exit two_parameters -# 191| exit block (normal) -#-----| -> exit block +# 191| exit { ... } (normal) +#-----| -> exit { ... } exit.rb: -# 1| exit top-level (normal) -#-----| -> exit top-level +# 1| exit AstNode (normal) +#-----| -> exit AstNode # 1| exit m1 (abnormal) #-----| -> exit m1 @@ -3948,15 +3948,15 @@ exit.rb: #-----| -> exit m2 heredoc.rb: -# 1| exit top-level (normal) -#-----| -> exit top-level +# 1| exit AstNode (normal) +#-----| -> exit AstNode # 1| exit double_heredoc (normal) #-----| -> exit double_heredoc ifs.rb: -# 1| exit top-level (normal) -#-----| -> exit top-level +# 1| exit AstNode (normal) +#-----| -> exit AstNode # 1| exit m1 (normal) #-----| -> exit m1 @@ -3980,8 +3980,8 @@ ifs.rb: #-----| -> exit constant_condition loops.rb: -# 1| exit top-level (normal) -#-----| -> exit top-level +# 1| exit AstNode (normal) +#-----| -> exit AstNode # 1| exit m1 (normal) #-----| -> exit m1 @@ -3992,12 +3992,12 @@ loops.rb: # 24| exit m3 (normal) #-----| -> exit m3 -# 25| exit do block (normal) -#-----| -> exit do block +# 25| exit do ... end (normal) +#-----| -> exit do ... end raise.rb: -# 1| exit top-level (normal) -#-----| -> exit top-level +# 1| exit AstNode (normal) +#-----| -> exit AstNode # 7| exit m1 (abnormal) #-----| -> exit m1 @@ -4065,8 +4065,8 @@ raise.rb: # 154| exit m14 (normal) #-----| -> exit m14 -# 155| exit block (abnormal) -#-----| -> exit block +# 155| exit { ... } (abnormal) +#-----| -> exit { ... } -# 155| exit block (normal) -#-----| -> exit block +# 155| exit { ... } (normal) +#-----| -> exit { ... } diff --git a/ql/test/library-tests/variables/parameter.expected b/ql/test/library-tests/variables/parameter.expected index 14ba3682887..959f8cfdc14 100644 --- a/ql/test/library-tests/variables/parameter.expected +++ b/ql/test/library-tests/variables/parameter.expected @@ -25,6 +25,15 @@ parameterVariable | parameters.rb:54:14:54:24 | y | parameters.rb:54:14:54:14 | y | | scopes.rb:2:14:2:14 | x | scopes.rb:2:14:2:14 | x | | scopes.rb:9:14:9:14 | x | scopes.rb:9:14:9:14 | x | +| ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | +| ssa.rb:18:8:18:8 | x | ssa.rb:18:8:18:8 | x | +| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | +| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | +| ssa.rb:44:8:44:8 | b | ssa.rb:44:8:44:8 | b | +| ssa.rb:49:9:49:20 | x | ssa.rb:49:9:49:9 | x | +| ssa.rb:53:8:53:10 | foo | ssa.rb:53:8:53:10 | foo | +| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | +| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | parameterNoVariable | parameters.rb:45:22:45:22 | _ | parameterVariableNoAccess @@ -32,3 +41,4 @@ parameterVariableNoAccess | nested_scopes.rb:18:26:18:26 | x | nested_scopes.rb:18:26:18:26 | x | | scopes.rb:2:14:2:14 | x | scopes.rb:2:14:2:14 | x | | scopes.rb:9:14:9:14 | x | scopes.rb:9:14:9:14 | x | +| ssa.rb:49:9:49:20 | x | ssa.rb:49:9:49:9 | x | diff --git a/ql/test/library-tests/variables/ssa.expected b/ql/test/library-tests/variables/ssa.expected new file mode 100644 index 00000000000..e58a577689c --- /dev/null +++ b/ql/test/library-tests/variables/ssa.expected @@ -0,0 +1,326 @@ +definition +| nested_scopes.rb:5:3:5:7 | ... = ... | nested_scopes.rb:5:3:5:3 | a | +| nested_scopes.rb:7:5:7:9 | ... = ... | nested_scopes.rb:7:5:7:5 | a | +| nested_scopes.rb:9:7:9:11 | ... = ... | nested_scopes.rb:9:7:9:7 | a | +| nested_scopes.rb:11:9:11:13 | ... = ... | nested_scopes.rb:11:9:11:9 | a | +| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | +| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a | +| nested_scopes.rb:17:15:17:19 | ... = ... | nested_scopes.rb:16:29:16:29 | a | +| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:16:29:16:29 | a | +| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a | +| nested_scopes.rb:31:11:31:16 | ... = ... | nested_scopes.rb:31:11:31:11 | a | +| nested_scopes.rb:40:1:40:18 | ... = ... | nested_scopes.rb:40:1:40:1 | d | +| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x | +| parameters.rb:2:4:2:8 | ... = ... | parameters.rb:1:18:1:18 | y | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | +| parameters.rb:15:17:15:19 | map | parameters.rb:15:17:15:19 | map | +| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key | +| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value | +| parameters.rb:21:17:21:21 | block | parameters.rb:21:17:21:21 | block | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | +| parameters.rb:25:33:25:36 | size | parameters.rb:25:33:25:36 | size | +| parameters.rb:30:15:30:19 | first | parameters.rb:30:15:30:19 | first | +| parameters.rb:30:24:30:29 | middle | parameters.rb:30:24:30:29 | middle | +| parameters.rb:30:36:30:39 | last | parameters.rb:30:36:30:39 | last | +| parameters.rb:35:1:38:3 | | parameters.rb:35:16:35:16 | b | +| parameters.rb:35:11:35:11 | a | parameters.rb:35:11:35:11 | a | +| parameters.rb:35:16:35:20 | ... = ... | parameters.rb:35:16:35:16 | b | +| parameters.rb:37:3:37:6 | phi | parameters.rb:35:16:35:16 | b | +| parameters.rb:40:1:43:3 | | parameters.rb:40:15:40:15 | e | +| parameters.rb:40:12:40:12 | d | parameters.rb:40:12:40:12 | d | +| parameters.rb:40:15:40:19 | ... = ... | parameters.rb:40:15:40:15 | e | +| parameters.rb:42:3:42:6 | phi | parameters.rb:40:15:40:15 | e | +| parameters.rb:45:20:45:20 | _ | parameters.rb:45:20:45:20 | _ | +| parameters.rb:49:13:49:13 | a | parameters.rb:49:13:49:13 | a | +| parameters.rb:49:15:49:15 | b | parameters.rb:49:15:49:15 | b | +| parameters.rb:53:1:53:6 | ... = ... | parameters.rb:53:1:53:1 | x | +| parameters.rb:54:9:57:3 | | parameters.rb:53:1:53:1 | x | +| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y | +| parameters.rb:54:19:54:23 | ... = ... | parameters.rb:53:1:53:1 | x | +| parameters.rb:55:4:55:7 | phi | parameters.rb:53:1:53:1 | x | +| scopes.rb:4:4:4:8 | ... = ... | scopes.rb:4:4:4:4 | a | +| scopes.rb:7:1:7:5 | ... = ... | scopes.rb:7:1:7:1 | a | +| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | +| scopes.rb:11:4:11:9 | ... += ... | scopes.rb:7:1:7:1 | a | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:7:1:7:1 | a | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:13:7:13:7 | b | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:13:11:13:11 | c | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:13:14:13:14 | d | +| ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | +| ssa.rb:2:3:2:7 | ... = ... | ssa.rb:2:3:2:3 | i | +| ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | +| ssa.rb:6:5:6:9 | ... = ... | ssa.rb:2:3:2:3 | i | +| ssa.rb:10:5:10:9 | ... = ... | ssa.rb:2:3:2:3 | i | +| ssa.rb:18:8:18:8 | x | ssa.rb:18:8:18:8 | x | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | +| ssa.rb:21:5:21:10 | ... -= ... | ssa.rb:18:8:18:8 | x | +| ssa.rb:25:1:30:3 | | ssa.rb:26:7:26:10 | elem | +| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | +| ssa.rb:26:3:28:5 | phi | ssa.rb:26:7:26:10 | elem | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | +| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | +| ssa.rb:40:3:40:9 | ... = ... | ssa.rb:40:3:40:4 | m3 | +| ssa.rb:44:1:47:3 | | ssa.rb:45:3:45:3 | x | +| ssa.rb:44:8:44:8 | b | ssa.rb:44:8:44:8 | b | +| ssa.rb:45:3:45:7 | ... = ... | ssa.rb:45:3:45:3 | x | +| ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | +| ssa.rb:49:1:51:3 | | ssa.rb:49:14:49:14 | y | +| ssa.rb:49:14:49:19 | ... = ... | ssa.rb:49:14:49:14 | y | +| ssa.rb:50:3:50:6 | phi | ssa.rb:49:14:49:14 | y | +| ssa.rb:53:8:53:10 | foo | ssa.rb:53:8:53:10 | foo | +| ssa.rb:54:3:54:11 | ... = ... | ssa.rb:54:3:54:3 | x | +| ssa.rb:59:3:59:8 | ... = ... | ssa.rb:59:3:59:3 | x | +| ssa.rb:60:3:60:9 | ... += ... | ssa.rb:59:3:59:3 | x | +| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | +| ssa.rb:65:3:65:15 | ... = ... | ssa.rb:65:3:65:10 | captured | +| ssa.rb:66:3:70:5 | call to times | ssa.rb:65:3:65:10 | captured | +| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | +| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | +| ssa.rb:69:5:69:17 | ... += ... | ssa.rb:65:3:65:10 | captured | +| ssa.rb:75:3:75:14 | ... = ... | ssa.rb:75:3:75:10 | captured | +| ssa.rb:76:7:78:5 | | ssa.rb:75:3:75:10 | captured | +| ssa.rb:82:3:82:14 | ... = ... | ssa.rb:82:3:82:10 | captured | +| ssa.rb:84:10:86:8 | | ssa.rb:82:3:82:10 | captured | +read +| nested_scopes.rb:5:3:5:7 | ... = ... | nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:38:8:38:8 | a | +| nested_scopes.rb:7:5:7:9 | ... = ... | nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:36:10:36:10 | a | +| nested_scopes.rb:9:7:9:11 | ... = ... | nested_scopes.rb:9:7:9:7 | a | nested_scopes.rb:34:12:34:12 | a | +| nested_scopes.rb:11:9:11:13 | ... = ... | nested_scopes.rb:11:9:11:9 | a | nested_scopes.rb:25:14:25:14 | a | +| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:14:16:14:16 | a | +| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:15:11:15:11 | a | +| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:16:13:16:13 | a | +| nested_scopes.rb:17:15:17:19 | ... = ... | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:15:18:15 | a | +| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:34:18:34 | a | +| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:23:16:23:16 | a | +| nested_scopes.rb:31:11:31:16 | ... = ... | nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:32:16:32:16 | a | +| nested_scopes.rb:40:1:40:18 | ... = ... | nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:41:1:41:1 | d | +| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x | parameters.rb:3:9:3:9 | x | +| parameters.rb:2:4:2:8 | ... = ... | parameters.rb:1:18:1:18 | y | parameters.rb:4:9:4:9 | y | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:9:25:9:30 | client | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:11:41:11:46 | client | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:8:6:8:11 | pizzas | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:11:14:11:19 | pizzas | +| parameters.rb:15:17:15:19 | map | parameters.rb:15:17:15:19 | map | parameters.rb:16:3:16:5 | map | +| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key | parameters.rb:17:13:17:15 | key | +| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value | parameters.rb:17:22:17:26 | value | +| parameters.rb:21:17:21:21 | block | parameters.rb:21:17:21:21 | block | parameters.rb:22:3:22:7 | block | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:25:40:25:43 | name | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:26:8:26:11 | name | +| parameters.rb:25:33:25:36 | size | parameters.rb:25:33:25:36 | size | parameters.rb:27:8:27:11 | size | +| parameters.rb:30:15:30:19 | first | parameters.rb:30:15:30:19 | first | parameters.rb:31:11:31:15 | first | +| parameters.rb:30:24:30:29 | middle | parameters.rb:30:24:30:29 | middle | parameters.rb:31:20:31:25 | middle | +| parameters.rb:30:36:30:39 | last | parameters.rb:30:36:30:39 | last | parameters.rb:31:30:31:33 | last | +| parameters.rb:35:11:35:11 | a | parameters.rb:35:11:35:11 | a | parameters.rb:37:11:37:11 | a | +| parameters.rb:37:3:37:6 | phi | parameters.rb:35:16:35:16 | b | parameters.rb:37:16:37:16 | b | +| parameters.rb:40:12:40:12 | d | parameters.rb:40:12:40:12 | d | parameters.rb:42:11:42:11 | d | +| parameters.rb:42:3:42:6 | phi | parameters.rb:40:15:40:15 | e | parameters.rb:42:16:42:16 | e | +| parameters.rb:45:20:45:20 | _ | parameters.rb:45:20:45:20 | _ | parameters.rb:46:8:46:8 | _ | +| parameters.rb:49:13:49:13 | a | parameters.rb:49:13:49:13 | a | parameters.rb:50:11:50:11 | a | +| parameters.rb:49:15:49:15 | b | parameters.rb:49:15:49:15 | b | parameters.rb:50:16:50:16 | b | +| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y | parameters.rb:56:9:56:9 | y | +| parameters.rb:55:4:55:7 | phi | parameters.rb:53:1:53:1 | x | parameters.rb:55:9:55:9 | x | +| scopes.rb:4:4:4:8 | ... = ... | scopes.rb:4:4:4:4 | a | scopes.rb:5:9:5:9 | a | +| scopes.rb:7:1:7:5 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:8:6:8:6 | a | +| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:10:9:10:9 | a | +| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:11:4:11:4 | a | +| scopes.rb:11:4:11:9 | ... += ... | scopes.rb:7:1:7:1 | a | scopes.rb:12:9:12:9 | a | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:14:9:14:9 | a | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:13:7:13:7 | b | scopes.rb:15:9:15:9 | b | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:13:11:13:11 | c | scopes.rb:16:9:16:9 | c | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:13:14:13:14 | d | scopes.rb:17:9:17:9 | d | +| ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | ssa.rb:5:6:5:6 | b | +| ssa.rb:2:3:2:7 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:3:8:3:8 | i | +| ssa.rb:2:3:2:7 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:4:8:4:8 | i | +| ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | ssa.rb:15:8:15:8 | i | +| ssa.rb:6:5:6:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:7:10:7:10 | i | +| ssa.rb:6:5:6:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:8:10:8:10 | i | +| ssa.rb:10:5:10:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:11:10:11:10 | i | +| ssa.rb:10:5:10:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:12:10:12:10 | i | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:19:9:19:9 | x | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:20:10:20:10 | x | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:21:5:21:5 | x | +| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:26:15:26:22 | elements | +| ssa.rb:26:3:28:5 | phi | ssa.rb:26:7:26:10 | elem | ssa.rb:29:8:29:11 | elem | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:27:10:27:13 | elem | +| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | ssa.rb:34:10:34:10 | x | +| ssa.rb:40:3:40:9 | ... = ... | ssa.rb:40:3:40:4 | m3 | ssa.rb:41:8:41:9 | m3 | +| ssa.rb:44:8:44:8 | b | ssa.rb:44:8:44:8 | b | ssa.rb:45:12:45:12 | b | +| ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | ssa.rb:46:8:46:8 | x | +| ssa.rb:50:3:50:6 | phi | ssa.rb:49:14:49:14 | y | ssa.rb:50:8:50:8 | y | +| ssa.rb:53:8:53:10 | foo | ssa.rb:53:8:53:10 | foo | ssa.rb:54:7:54:9 | foo | +| ssa.rb:54:3:54:11 | ... = ... | ssa.rb:54:3:54:3 | x | ssa.rb:55:8:55:8 | x | +| ssa.rb:59:3:59:8 | ... = ... | ssa.rb:59:3:59:3 | x | ssa.rb:60:3:60:3 | x | +| ssa.rb:60:3:60:9 | ... += ... | ssa.rb:59:3:59:3 | x | ssa.rb:61:8:61:8 | x | +| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | ssa.rb:66:3:66:3 | a | +| ssa.rb:66:3:70:5 | call to times | ssa.rb:65:3:65:10 | captured | ssa.rb:71:8:71:15 | captured | +| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | ssa.rb:68:10:68:17 | captured | +| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | ssa.rb:69:5:69:12 | captured | +| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | ssa.rb:67:10:67:10 | a | +| ssa.rb:76:7:78:5 | | ssa.rb:75:3:75:10 | captured | ssa.rb:77:15:77:22 | captured | +| ssa.rb:84:10:86:8 | | ssa.rb:82:3:82:10 | captured | ssa.rb:85:15:85:22 | captured | +firstRead +| nested_scopes.rb:5:3:5:7 | ... = ... | nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:38:8:38:8 | a | +| nested_scopes.rb:7:5:7:9 | ... = ... | nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:36:10:36:10 | a | +| nested_scopes.rb:9:7:9:11 | ... = ... | nested_scopes.rb:9:7:9:7 | a | nested_scopes.rb:34:12:34:12 | a | +| nested_scopes.rb:11:9:11:13 | ... = ... | nested_scopes.rb:11:9:11:9 | a | nested_scopes.rb:25:14:25:14 | a | +| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:14:16:14:16 | a | +| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:16:13:16:13 | a | +| nested_scopes.rb:17:15:17:19 | ... = ... | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:15:18:15 | a | +| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:34:18:34 | a | +| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:23:16:23:16 | a | +| nested_scopes.rb:31:11:31:16 | ... = ... | nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:32:16:32:16 | a | +| nested_scopes.rb:40:1:40:18 | ... = ... | nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:41:1:41:1 | d | +| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x | parameters.rb:3:9:3:9 | x | +| parameters.rb:2:4:2:8 | ... = ... | parameters.rb:1:18:1:18 | y | parameters.rb:4:9:4:9 | y | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:9:25:9:30 | client | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:11:41:11:46 | client | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:8:6:8:11 | pizzas | +| parameters.rb:15:17:15:19 | map | parameters.rb:15:17:15:19 | map | parameters.rb:16:3:16:5 | map | +| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key | parameters.rb:17:13:17:15 | key | +| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value | parameters.rb:17:22:17:26 | value | +| parameters.rb:21:17:21:21 | block | parameters.rb:21:17:21:21 | block | parameters.rb:22:3:22:7 | block | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:25:40:25:43 | name | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:26:8:26:11 | name | +| parameters.rb:25:33:25:36 | size | parameters.rb:25:33:25:36 | size | parameters.rb:27:8:27:11 | size | +| parameters.rb:30:15:30:19 | first | parameters.rb:30:15:30:19 | first | parameters.rb:31:11:31:15 | first | +| parameters.rb:30:24:30:29 | middle | parameters.rb:30:24:30:29 | middle | parameters.rb:31:20:31:25 | middle | +| parameters.rb:30:36:30:39 | last | parameters.rb:30:36:30:39 | last | parameters.rb:31:30:31:33 | last | +| parameters.rb:35:11:35:11 | a | parameters.rb:35:11:35:11 | a | parameters.rb:37:11:37:11 | a | +| parameters.rb:37:3:37:6 | phi | parameters.rb:35:16:35:16 | b | parameters.rb:37:16:37:16 | b | +| parameters.rb:40:12:40:12 | d | parameters.rb:40:12:40:12 | d | parameters.rb:42:11:42:11 | d | +| parameters.rb:42:3:42:6 | phi | parameters.rb:40:15:40:15 | e | parameters.rb:42:16:42:16 | e | +| parameters.rb:45:20:45:20 | _ | parameters.rb:45:20:45:20 | _ | parameters.rb:46:8:46:8 | _ | +| parameters.rb:49:13:49:13 | a | parameters.rb:49:13:49:13 | a | parameters.rb:50:11:50:11 | a | +| parameters.rb:49:15:49:15 | b | parameters.rb:49:15:49:15 | b | parameters.rb:50:16:50:16 | b | +| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y | parameters.rb:56:9:56:9 | y | +| parameters.rb:55:4:55:7 | phi | parameters.rb:53:1:53:1 | x | parameters.rb:55:9:55:9 | x | +| scopes.rb:4:4:4:8 | ... = ... | scopes.rb:4:4:4:4 | a | scopes.rb:5:9:5:9 | a | +| scopes.rb:7:1:7:5 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:8:6:8:6 | a | +| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:10:9:10:9 | a | +| scopes.rb:11:4:11:9 | ... += ... | scopes.rb:7:1:7:1 | a | scopes.rb:12:9:12:9 | a | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:14:9:14:9 | a | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:13:7:13:7 | b | scopes.rb:15:9:15:9 | b | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:13:11:13:11 | c | scopes.rb:16:9:16:9 | c | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:13:14:13:14 | d | scopes.rb:17:9:17:9 | d | +| ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | ssa.rb:5:6:5:6 | b | +| ssa.rb:2:3:2:7 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:3:8:3:8 | i | +| ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | ssa.rb:15:8:15:8 | i | +| ssa.rb:6:5:6:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:7:10:7:10 | i | +| ssa.rb:10:5:10:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:11:10:11:10 | i | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:19:9:19:9 | x | +| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:26:15:26:22 | elements | +| ssa.rb:26:3:28:5 | phi | ssa.rb:26:7:26:10 | elem | ssa.rb:29:8:29:11 | elem | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:27:10:27:13 | elem | +| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | ssa.rb:34:10:34:10 | x | +| ssa.rb:40:3:40:9 | ... = ... | ssa.rb:40:3:40:4 | m3 | ssa.rb:41:8:41:9 | m3 | +| ssa.rb:44:8:44:8 | b | ssa.rb:44:8:44:8 | b | ssa.rb:45:12:45:12 | b | +| ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | ssa.rb:46:8:46:8 | x | +| ssa.rb:50:3:50:6 | phi | ssa.rb:49:14:49:14 | y | ssa.rb:50:8:50:8 | y | +| ssa.rb:53:8:53:10 | foo | ssa.rb:53:8:53:10 | foo | ssa.rb:54:7:54:9 | foo | +| ssa.rb:54:3:54:11 | ... = ... | ssa.rb:54:3:54:3 | x | ssa.rb:55:8:55:8 | x | +| ssa.rb:59:3:59:8 | ... = ... | ssa.rb:59:3:59:3 | x | ssa.rb:60:3:60:3 | x | +| ssa.rb:60:3:60:9 | ... += ... | ssa.rb:59:3:59:3 | x | ssa.rb:61:8:61:8 | x | +| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | ssa.rb:66:3:66:3 | a | +| ssa.rb:66:3:70:5 | call to times | ssa.rb:65:3:65:10 | captured | ssa.rb:71:8:71:15 | captured | +| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | ssa.rb:68:10:68:17 | captured | +| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | ssa.rb:67:10:67:10 | a | +| ssa.rb:76:7:78:5 | | ssa.rb:75:3:75:10 | captured | ssa.rb:77:15:77:22 | captured | +| ssa.rb:84:10:86:8 | | ssa.rb:82:3:82:10 | captured | ssa.rb:85:15:85:22 | captured | +lastRead +| nested_scopes.rb:5:3:5:7 | ... = ... | nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:38:8:38:8 | a | +| nested_scopes.rb:7:5:7:9 | ... = ... | nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:36:10:36:10 | a | +| nested_scopes.rb:9:7:9:11 | ... = ... | nested_scopes.rb:9:7:9:7 | a | nested_scopes.rb:34:12:34:12 | a | +| nested_scopes.rb:11:9:11:13 | ... = ... | nested_scopes.rb:11:9:11:9 | a | nested_scopes.rb:25:14:25:14 | a | +| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:15:11:15:11 | a | +| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:16:13:16:13 | a | +| nested_scopes.rb:17:15:17:19 | ... = ... | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:15:18:15 | a | +| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:34:18:34 | a | +| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:23:16:23:16 | a | +| nested_scopes.rb:31:11:31:16 | ... = ... | nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:32:16:32:16 | a | +| nested_scopes.rb:40:1:40:18 | ... = ... | nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:41:1:41:1 | d | +| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x | parameters.rb:3:9:3:9 | x | +| parameters.rb:2:4:2:8 | ... = ... | parameters.rb:1:18:1:18 | y | parameters.rb:4:9:4:9 | y | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:9:25:9:30 | client | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:11:41:11:46 | client | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:8:6:8:11 | pizzas | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:11:14:11:19 | pizzas | +| parameters.rb:15:17:15:19 | map | parameters.rb:15:17:15:19 | map | parameters.rb:16:3:16:5 | map | +| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key | parameters.rb:17:13:17:15 | key | +| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value | parameters.rb:17:22:17:26 | value | +| parameters.rb:21:17:21:21 | block | parameters.rb:21:17:21:21 | block | parameters.rb:22:3:22:7 | block | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:26:8:26:11 | name | +| parameters.rb:25:33:25:36 | size | parameters.rb:25:33:25:36 | size | parameters.rb:27:8:27:11 | size | +| parameters.rb:30:15:30:19 | first | parameters.rb:30:15:30:19 | first | parameters.rb:31:11:31:15 | first | +| parameters.rb:30:24:30:29 | middle | parameters.rb:30:24:30:29 | middle | parameters.rb:31:20:31:25 | middle | +| parameters.rb:30:36:30:39 | last | parameters.rb:30:36:30:39 | last | parameters.rb:31:30:31:33 | last | +| parameters.rb:35:11:35:11 | a | parameters.rb:35:11:35:11 | a | parameters.rb:37:11:37:11 | a | +| parameters.rb:37:3:37:6 | phi | parameters.rb:35:16:35:16 | b | parameters.rb:37:16:37:16 | b | +| parameters.rb:40:12:40:12 | d | parameters.rb:40:12:40:12 | d | parameters.rb:42:11:42:11 | d | +| parameters.rb:42:3:42:6 | phi | parameters.rb:40:15:40:15 | e | parameters.rb:42:16:42:16 | e | +| parameters.rb:45:20:45:20 | _ | parameters.rb:45:20:45:20 | _ | parameters.rb:46:8:46:8 | _ | +| parameters.rb:49:13:49:13 | a | parameters.rb:49:13:49:13 | a | parameters.rb:50:11:50:11 | a | +| parameters.rb:49:15:49:15 | b | parameters.rb:49:15:49:15 | b | parameters.rb:50:16:50:16 | b | +| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y | parameters.rb:56:9:56:9 | y | +| parameters.rb:55:4:55:7 | phi | parameters.rb:53:1:53:1 | x | parameters.rb:55:9:55:9 | x | +| scopes.rb:4:4:4:8 | ... = ... | scopes.rb:4:4:4:4 | a | scopes.rb:5:9:5:9 | a | +| scopes.rb:7:1:7:5 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:8:6:8:6 | a | +| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:11:4:11:4 | a | +| scopes.rb:11:4:11:9 | ... += ... | scopes.rb:7:1:7:1 | a | scopes.rb:12:9:12:9 | a | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:14:9:14:9 | a | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:13:7:13:7 | b | scopes.rb:15:9:15:9 | b | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:13:11:13:11 | c | scopes.rb:16:9:16:9 | c | +| scopes.rb:13:4:13:32 | ... = ... | scopes.rb:13:14:13:14 | d | scopes.rb:17:9:17:9 | d | +| ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | ssa.rb:5:6:5:6 | b | +| ssa.rb:2:3:2:7 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:4:8:4:8 | i | +| ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | ssa.rb:15:8:15:8 | i | +| ssa.rb:6:5:6:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:8:10:8:10 | i | +| ssa.rb:10:5:10:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:12:10:12:10 | i | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:19:9:19:9 | x | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:21:5:21:5 | x | +| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:26:15:26:22 | elements | +| ssa.rb:26:3:28:5 | phi | ssa.rb:26:7:26:10 | elem | ssa.rb:29:8:29:11 | elem | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:27:10:27:13 | elem | +| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | ssa.rb:34:10:34:10 | x | +| ssa.rb:40:3:40:9 | ... = ... | ssa.rb:40:3:40:4 | m3 | ssa.rb:41:8:41:9 | m3 | +| ssa.rb:44:8:44:8 | b | ssa.rb:44:8:44:8 | b | ssa.rb:45:12:45:12 | b | +| ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | ssa.rb:46:8:46:8 | x | +| ssa.rb:50:3:50:6 | phi | ssa.rb:49:14:49:14 | y | ssa.rb:50:8:50:8 | y | +| ssa.rb:53:8:53:10 | foo | ssa.rb:53:8:53:10 | foo | ssa.rb:54:7:54:9 | foo | +| ssa.rb:54:3:54:11 | ... = ... | ssa.rb:54:3:54:3 | x | ssa.rb:55:8:55:8 | x | +| ssa.rb:59:3:59:8 | ... = ... | ssa.rb:59:3:59:3 | x | ssa.rb:60:3:60:3 | x | +| ssa.rb:60:3:60:9 | ... += ... | ssa.rb:59:3:59:3 | x | ssa.rb:61:8:61:8 | x | +| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | ssa.rb:66:3:66:3 | a | +| ssa.rb:66:3:70:5 | call to times | ssa.rb:65:3:65:10 | captured | ssa.rb:71:8:71:15 | captured | +| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | ssa.rb:69:5:69:12 | captured | +| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | ssa.rb:67:10:67:10 | a | +| ssa.rb:76:7:78:5 | | ssa.rb:75:3:75:10 | captured | ssa.rb:77:15:77:22 | captured | +| ssa.rb:84:10:86:8 | | ssa.rb:82:3:82:10 | captured | ssa.rb:85:15:85:22 | captured | +adjacentReads +| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:14:16:14:16 | a | nested_scopes.rb:15:11:15:11 | a | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:8:6:8:11 | pizzas | parameters.rb:11:14:11:19 | pizzas | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:25:40:25:43 | name | parameters.rb:26:8:26:11 | name | +| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:10:9:10:9 | a | scopes.rb:11:4:11:4 | a | +| ssa.rb:2:3:2:7 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:3:8:3:8 | i | ssa.rb:4:8:4:8 | i | +| ssa.rb:6:5:6:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:7:10:7:10 | i | ssa.rb:8:10:8:10 | i | +| ssa.rb:10:5:10:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:11:10:11:10 | i | ssa.rb:12:10:12:10 | i | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:19:9:19:9 | x | ssa.rb:20:10:20:10 | x | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:20:10:20:10 | x | ssa.rb:21:5:21:5 | x | +| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | ssa.rb:68:10:68:17 | captured | ssa.rb:69:5:69:12 | captured | +phi +| parameters.rb:37:3:37:6 | phi | parameters.rb:35:16:35:16 | b | parameters.rb:35:1:38:3 | | +| parameters.rb:37:3:37:6 | phi | parameters.rb:35:16:35:16 | b | parameters.rb:35:16:35:20 | ... = ... | +| parameters.rb:42:3:42:6 | phi | parameters.rb:40:15:40:15 | e | parameters.rb:40:1:43:3 | | +| parameters.rb:42:3:42:6 | phi | parameters.rb:40:15:40:15 | e | parameters.rb:40:15:40:19 | ... = ... | +| parameters.rb:55:4:55:7 | phi | parameters.rb:53:1:53:1 | x | parameters.rb:54:9:57:3 | | +| parameters.rb:55:4:55:7 | phi | parameters.rb:53:1:53:1 | x | parameters.rb:54:19:54:23 | ... = ... | +| ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | ssa.rb:6:5:6:9 | ... = ... | +| ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | ssa.rb:10:5:10:9 | ... = ... | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:18:8:18:8 | x | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:21:5:21:10 | ... -= ... | +| ssa.rb:26:3:28:5 | phi | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | | +| ssa.rb:26:3:28:5 | phi | ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | +| ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | ssa.rb:44:1:47:3 | | +| ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | ssa.rb:45:3:45:7 | ... = ... | +| ssa.rb:50:3:50:6 | phi | ssa.rb:49:14:49:14 | y | ssa.rb:49:1:51:3 | | +| ssa.rb:50:3:50:6 | phi | ssa.rb:49:14:49:14 | y | ssa.rb:49:14:49:19 | ... = ... | diff --git a/ql/test/library-tests/variables/ssa.ql b/ql/test/library-tests/variables/ssa.ql new file mode 100644 index 00000000000..8d600d2200a --- /dev/null +++ b/ql/test/library-tests/variables/ssa.ql @@ -0,0 +1,27 @@ +import codeql_ruby.AST +import codeql_ruby.CFG +import codeql_ruby.ast.Variable +import codeql_ruby.dataflow.SSA + +query predicate definition(Ssa::Definition def, Variable v) { def.getSourceVariable() = v } + +query predicate read(Ssa::Definition def, Variable v, CfgNode read) { + def.getSourceVariable() = v and read = def.getARead() +} + +query predicate firstRead(Ssa::Definition def, Variable v, CfgNode read) { + def.getSourceVariable() = v and read = def.getAFirstRead() +} + +query predicate lastRead(Ssa::Definition def, Variable v, CfgNode read) { + def.getSourceVariable() = v and read = def.getALastRead() +} + +query predicate adjacentReads(Ssa::Definition def, Variable v, CfgNode read1, CfgNode read2) { + def.getSourceVariable() = v and + def.hasAdjacentReads(read1, read2) +} + +query predicate phi(Ssa::PhiNode phi, Variable v, Ssa::Definition input) { + phi.getSourceVariable() = v and input = phi.getAnInput() +} diff --git a/ql/test/library-tests/variables/ssa.rb b/ql/test/library-tests/variables/ssa.rb new file mode 100644 index 00000000000..bb13dd6c4c9 --- /dev/null +++ b/ql/test/library-tests/variables/ssa.rb @@ -0,0 +1,88 @@ +def m b # defines b_0 + i = 0 # defines i_0 + puts i # reads i_0 (first read) + puts i + 1 # reads i_0 (last read) + if b # reads b_0 + i = 1 # defines i_1 + puts i # reads i_1 (first read) + puts i + 1 # reads i_1 (last read) + else + i = 2 # defines i_2 + puts i # reads i_2 (first read) + puts i + 1 # reads i_2 (last read) + end + # defines i_3 = phi(i_1, i_2) + puts i # reads i3 (first read and last read) +end + +def m1 x + while x >= 0 + puts x + x -= 1 + end +end + +def m2 elements + for elem in elements do + puts elem + end + puts elem +end + +def m3 + [1,2,3].each do |x| + puts x + end +end + +def m4 + puts m3 + m3 = 10 + puts m3 + 1 +end + +def m5 b + x = 0 if b + puts x +end + +def m6 (x = (y = 10)) + puts y +end + +def m7 foo + x = foo.x + puts x +end + +def m8 + x = 10 + x += 10 + puts x +end + +def m9 a + captured = 10 + a.times do |a| + puts a + puts captured + captured += 1 + end + puts captured +end + +def m10 + captured = 0 + foo do + bar(baz: captured) + end +end + +def m11 + captured = 0 + foo do + bar do + puts captured + end + end +end \ No newline at end of file diff --git a/ql/test/library-tests/variables/varaccess.expected b/ql/test/library-tests/variables/varaccess.expected index 538fca09713..8791c560a5d 100644 --- a/ql/test/library-tests/variables/varaccess.expected +++ b/ql/test/library-tests/variables/varaccess.expected @@ -94,6 +94,57 @@ variableAccess | scopes.rb:21:1:21:7 | $global | file://:0:0:0:0 | $global | file://:0:0:0:0 | global scope | | scopes.rb:24:1:24:6 | script | scopes.rb:24:1:24:6 | script | scopes.rb:1:1:24:12 | top-level scope | | scopes.rb:24:10:24:11 | $0 | file://:0:0:0:0 | $0 | file://:0:0:0:0 | global scope | +| ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | ssa.rb:1:1:16:3 | method scope | +| ssa.rb:2:3:2:3 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | method scope | +| ssa.rb:3:8:3:8 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | method scope | +| ssa.rb:4:8:4:8 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | method scope | +| ssa.rb:5:6:5:6 | b | ssa.rb:1:7:1:7 | b | ssa.rb:1:1:16:3 | method scope | +| ssa.rb:6:5:6:5 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | method scope | +| ssa.rb:7:10:7:10 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | method scope | +| ssa.rb:8:10:8:10 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | method scope | +| ssa.rb:10:5:10:5 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | method scope | +| ssa.rb:11:10:11:10 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | method scope | +| ssa.rb:12:10:12:10 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | method scope | +| ssa.rb:15:8:15:8 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | method scope | +| ssa.rb:18:8:18:8 | x | ssa.rb:18:8:18:8 | x | ssa.rb:18:1:23:3 | method scope | +| ssa.rb:19:9:19:9 | x | ssa.rb:18:8:18:8 | x | ssa.rb:18:1:23:3 | method scope | +| ssa.rb:20:10:20:10 | x | ssa.rb:18:8:18:8 | x | ssa.rb:18:1:23:3 | method scope | +| ssa.rb:21:5:21:5 | x | ssa.rb:18:8:18:8 | x | ssa.rb:18:1:23:3 | method scope | +| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:25:1:30:3 | method scope | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | method scope | +| ssa.rb:26:15:26:22 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:25:1:30:3 | method scope | +| ssa.rb:27:10:27:13 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | method scope | +| ssa.rb:29:8:29:11 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | method scope | +| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | ssa.rb:33:16:35:5 | block scope | +| ssa.rb:34:10:34:10 | x | ssa.rb:33:20:33:20 | x | ssa.rb:33:16:35:5 | block scope | +| ssa.rb:40:3:40:4 | m3 | ssa.rb:40:3:40:4 | m3 | ssa.rb:38:1:42:3 | method scope | +| ssa.rb:41:8:41:9 | m3 | ssa.rb:40:3:40:4 | m3 | ssa.rb:38:1:42:3 | method scope | +| ssa.rb:44:8:44:8 | b | ssa.rb:44:8:44:8 | b | ssa.rb:44:1:47:3 | method scope | +| ssa.rb:45:3:45:3 | x | ssa.rb:45:3:45:3 | x | ssa.rb:44:1:47:3 | method scope | +| ssa.rb:45:12:45:12 | b | ssa.rb:44:8:44:8 | b | ssa.rb:44:1:47:3 | method scope | +| ssa.rb:46:8:46:8 | x | ssa.rb:45:3:45:3 | x | ssa.rb:44:1:47:3 | method scope | +| ssa.rb:49:9:49:9 | x | ssa.rb:49:9:49:9 | x | ssa.rb:49:1:51:3 | method scope | +| ssa.rb:49:14:49:14 | y | ssa.rb:49:14:49:14 | y | ssa.rb:49:1:51:3 | method scope | +| ssa.rb:50:8:50:8 | y | ssa.rb:49:14:49:14 | y | ssa.rb:49:1:51:3 | method scope | +| ssa.rb:53:8:53:10 | foo | ssa.rb:53:8:53:10 | foo | ssa.rb:53:1:56:3 | method scope | +| ssa.rb:54:3:54:3 | x | ssa.rb:54:3:54:3 | x | ssa.rb:53:1:56:3 | method scope | +| ssa.rb:54:7:54:9 | foo | ssa.rb:53:8:53:10 | foo | ssa.rb:53:1:56:3 | method scope | +| ssa.rb:55:8:55:8 | x | ssa.rb:54:3:54:3 | x | ssa.rb:53:1:56:3 | method scope | +| ssa.rb:59:3:59:3 | x | ssa.rb:59:3:59:3 | x | ssa.rb:58:1:62:3 | method scope | +| ssa.rb:60:3:60:3 | x | ssa.rb:59:3:59:3 | x | ssa.rb:58:1:62:3 | method scope | +| ssa.rb:61:8:61:8 | x | ssa.rb:59:3:59:3 | x | ssa.rb:58:1:62:3 | method scope | +| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | ssa.rb:64:1:72:3 | method scope | +| ssa.rb:65:3:65:10 | captured | ssa.rb:65:3:65:10 | captured | ssa.rb:64:1:72:3 | method scope | +| ssa.rb:66:3:66:3 | a | ssa.rb:64:8:64:8 | a | ssa.rb:64:1:72:3 | method scope | +| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | ssa.rb:66:11:70:5 | block scope | +| ssa.rb:67:10:67:10 | a | ssa.rb:66:15:66:15 | a | ssa.rb:66:11:70:5 | block scope | +| ssa.rb:68:10:68:17 | captured | ssa.rb:65:3:65:10 | captured | ssa.rb:64:1:72:3 | method scope | +| ssa.rb:69:5:69:12 | captured | ssa.rb:65:3:65:10 | captured | ssa.rb:64:1:72:3 | method scope | +| ssa.rb:71:8:71:15 | captured | ssa.rb:65:3:65:10 | captured | ssa.rb:64:1:72:3 | method scope | +| ssa.rb:75:3:75:10 | captured | ssa.rb:75:3:75:10 | captured | ssa.rb:74:1:79:3 | method scope | +| ssa.rb:77:15:77:22 | captured | ssa.rb:75:3:75:10 | captured | ssa.rb:74:1:79:3 | method scope | +| ssa.rb:82:3:82:10 | captured | ssa.rb:82:3:82:10 | captured | ssa.rb:81:1:88:3 | method scope | +| ssa.rb:85:15:85:22 | captured | ssa.rb:82:3:82:10 | captured | ssa.rb:81:1:88:3 | method scope | explicitWrite | nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:5:3:5:7 | ... = ... | | nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:7:5:7:9 | ... = ... | @@ -118,6 +169,20 @@ explicitWrite | scopes.rb:13:14:13:14 | d | scopes.rb:13:4:13:32 | ... = ... | | scopes.rb:21:1:21:7 | $global | scopes.rb:21:1:21:12 | ... = ... | | scopes.rb:24:1:24:6 | script | scopes.rb:24:1:24:11 | ... = ... | +| ssa.rb:2:3:2:3 | i | ssa.rb:2:3:2:7 | ... = ... | +| ssa.rb:6:5:6:5 | i | ssa.rb:6:5:6:9 | ... = ... | +| ssa.rb:10:5:10:5 | i | ssa.rb:10:5:10:9 | ... = ... | +| ssa.rb:21:5:21:5 | x | ssa.rb:21:5:21:10 | ... -= ... | +| ssa.rb:40:3:40:4 | m3 | ssa.rb:40:3:40:9 | ... = ... | +| ssa.rb:45:3:45:3 | x | ssa.rb:45:3:45:7 | ... = ... | +| ssa.rb:49:14:49:14 | y | ssa.rb:49:14:49:19 | ... = ... | +| ssa.rb:54:3:54:3 | x | ssa.rb:54:3:54:11 | ... = ... | +| ssa.rb:59:3:59:3 | x | ssa.rb:59:3:59:8 | ... = ... | +| ssa.rb:60:3:60:3 | x | ssa.rb:60:3:60:9 | ... += ... | +| ssa.rb:65:3:65:10 | captured | ssa.rb:65:3:65:15 | ... = ... | +| ssa.rb:69:5:69:12 | captured | ssa.rb:69:5:69:17 | ... += ... | +| ssa.rb:75:3:75:10 | captured | ssa.rb:75:3:75:14 | ... = ... | +| ssa.rb:82:3:82:10 | captured | ssa.rb:82:3:82:14 | ... = ... | implicitWrite | nested_scopes.rb:15:23:15:23 | a | | nested_scopes.rb:16:26:16:26 | x | @@ -145,6 +210,16 @@ implicitWrite | parameters.rb:54:14:54:14 | y | | scopes.rb:2:14:2:14 | x | | scopes.rb:9:14:9:14 | x | +| ssa.rb:1:7:1:7 | b | +| ssa.rb:18:8:18:8 | x | +| ssa.rb:25:8:25:15 | elements | +| ssa.rb:26:7:26:10 | elem | +| ssa.rb:33:20:33:20 | x | +| ssa.rb:44:8:44:8 | b | +| ssa.rb:49:9:49:9 | x | +| ssa.rb:53:8:53:10 | foo | +| ssa.rb:64:8:64:8 | a | +| ssa.rb:66:15:66:15 | a | readAccess | nested_scopes.rb:14:16:14:16 | a | | nested_scopes.rb:15:11:15:11 | a | @@ -193,3 +268,33 @@ readAccess | scopes.rb:16:9:16:9 | c | | scopes.rb:17:9:17:9 | d | | scopes.rb:24:10:24:11 | $0 | +| ssa.rb:3:8:3:8 | i | +| ssa.rb:4:8:4:8 | i | +| ssa.rb:5:6:5:6 | b | +| ssa.rb:7:10:7:10 | i | +| ssa.rb:8:10:8:10 | i | +| ssa.rb:11:10:11:10 | i | +| ssa.rb:12:10:12:10 | i | +| ssa.rb:15:8:15:8 | i | +| ssa.rb:19:9:19:9 | x | +| ssa.rb:20:10:20:10 | x | +| ssa.rb:21:5:21:5 | x | +| ssa.rb:26:15:26:22 | elements | +| ssa.rb:27:10:27:13 | elem | +| ssa.rb:29:8:29:11 | elem | +| ssa.rb:34:10:34:10 | x | +| ssa.rb:41:8:41:9 | m3 | +| ssa.rb:45:12:45:12 | b | +| ssa.rb:46:8:46:8 | x | +| ssa.rb:50:8:50:8 | y | +| ssa.rb:54:7:54:9 | foo | +| ssa.rb:55:8:55:8 | x | +| ssa.rb:60:3:60:3 | x | +| ssa.rb:61:8:61:8 | x | +| ssa.rb:66:3:66:3 | a | +| ssa.rb:67:10:67:10 | a | +| ssa.rb:68:10:68:17 | captured | +| ssa.rb:69:5:69:12 | captured | +| ssa.rb:71:8:71:15 | captured | +| ssa.rb:77:15:77:22 | captured | +| ssa.rb:85:15:85:22 | captured | diff --git a/ql/test/library-tests/variables/variable.expected b/ql/test/library-tests/variables/variable.expected index 749df84cb0d..54600ae9da1 100644 --- a/ql/test/library-tests/variables/variable.expected +++ b/ql/test/library-tests/variables/variable.expected @@ -43,3 +43,22 @@ | scopes.rb:13:11:13:11 | c | | scopes.rb:13:14:13:14 | d | | scopes.rb:24:1:24:6 | script | +| ssa.rb:1:7:1:7 | b | +| ssa.rb:2:3:2:3 | i | +| ssa.rb:18:8:18:8 | x | +| ssa.rb:25:8:25:15 | elements | +| ssa.rb:26:7:26:10 | elem | +| ssa.rb:33:20:33:20 | x | +| ssa.rb:40:3:40:4 | m3 | +| ssa.rb:44:8:44:8 | b | +| ssa.rb:45:3:45:3 | x | +| ssa.rb:49:9:49:9 | x | +| ssa.rb:49:14:49:14 | y | +| ssa.rb:53:8:53:10 | foo | +| ssa.rb:54:3:54:3 | x | +| ssa.rb:59:3:59:3 | x | +| ssa.rb:64:8:64:8 | a | +| ssa.rb:65:3:65:10 | captured | +| ssa.rb:66:15:66:15 | a | +| ssa.rb:75:3:75:10 | captured | +| ssa.rb:82:3:82:10 | captured | diff --git a/ql/test/library-tests/variables/varscopes.expected b/ql/test/library-tests/variables/varscopes.expected index 03b586db730..104f0ded8b6 100644 --- a/ql/test/library-tests/variables/varscopes.expected +++ b/ql/test/library-tests/variables/varscopes.expected @@ -29,3 +29,21 @@ | scopes.rb:1:1:24:12 | top-level scope | | scopes.rb:2:9:6:3 | block scope | | scopes.rb:9:9:18:3 | block scope | +| ssa.rb:1:1:16:3 | method scope | +| ssa.rb:1:1:88:3 | top-level scope | +| ssa.rb:18:1:23:3 | method scope | +| ssa.rb:25:1:30:3 | method scope | +| ssa.rb:32:1:36:3 | method scope | +| ssa.rb:33:16:35:5 | block scope | +| ssa.rb:38:1:42:3 | method scope | +| ssa.rb:44:1:47:3 | method scope | +| ssa.rb:49:1:51:3 | method scope | +| ssa.rb:53:1:56:3 | method scope | +| ssa.rb:58:1:62:3 | method scope | +| ssa.rb:64:1:72:3 | method scope | +| ssa.rb:66:11:70:5 | block scope | +| ssa.rb:74:1:79:3 | method scope | +| ssa.rb:76:7:78:5 | block scope | +| ssa.rb:81:1:88:3 | method scope | +| ssa.rb:83:7:87:5 | block scope | +| ssa.rb:84:10:86:8 | block scope | diff --git a/scripts/identical-files.json b/scripts/identical-files.json index 9e26dfeeb6e..bc150dfa6ff 100644 --- a/scripts/identical-files.json +++ b/scripts/identical-files.json @@ -1 +1,6 @@ -{} \ No newline at end of file +{ + "SSA": [ + "codeql/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll", + "ql/src/codeql_ruby/dataflow/internal/SsaImplCommon.qll" + ] +} \ No newline at end of file