Files
codeql/ql/src/codeql_ruby/dataflow/SSA.qll
2021-06-18 10:42:32 +02:00

386 lines
11 KiB
Plaintext

/**
* 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() { result = SsaImpl::getARead(this) }
/**
* 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 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 getAPhiInputOrPriorDefinition() {
result = this.(PhiNode).getAnInput() or
result = this.(CapturedCallDefinition).getPriorDefinition()
}
/**
* 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 Definition getAnUltimateDefinition() {
result = this.getAPhiInputOrPriorDefinition*() and
not result instanceof PhiNode
}
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 }
/**
* Holds if this SSA definition represents a direct assignment of `value`
* to the underlying variable.
*/
predicate assigns(CfgNodes::ExprCfgNode value) {
exists(CfgNodes::ExprNodes::AssignExprCfgNode a, BasicBlock bb, int i |
this.definesAt(_, bb, i) and
a = bb.getNode(i) and
value = a.getRhs()
)
}
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 = "<uninitialized>" }
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 = "<captured>" }
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)
)
}
/**
* Gets the immediately preceding definition. Since this update is uncertain,
* the value from the preceding definition might still be valid.
*/
final Definition getPriorDefinition() { result = SsaImpl::uncertainWriteDefinitionInput(this) }
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 Definition getAnInput() { this.hasInputFromBlock(result, _) }
/** Holds if `inp` is an input to this phi node along the edge originating in `bb`. */
predicate hasInputFromBlock(Definition inp, BasicBlock bb) {
inp = SsaImpl::phiHasInputFromBlock(this, bb)
}
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()
}
}
}