Files
codeql/cpp/ql/lib/semmle/code/cpp/controlflow/BasicBlocks.qll
Josh Soref bbc9931b05 spelling: primitive
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2022-10-14 15:08:44 -04:00

269 lines
9.6 KiB
Plaintext

/**
* Provides a library for reasoning about control flow at the granularity of basic blocks.
* This is usually much more efficient than reasoning directly at the level of `ControlFlowNode`s.
*/
import cpp
private import internal.PrimitiveBasicBlocks
private import internal.ConstantExprs
/*
* `BasicBlock`s are refinements of `PrimitiveBasicBlock`s, taking
* impossible CFG edges into account (using the `successors_adapted`
* relation). The refinement manifests itself in two changes:
*
* - The successor relation on `BasicBlock`s uses `successors_adapted`
* (instead of `successors_extended` used by `PrimitiveBasicBlock`s). Consequently,
* some edges between `BasicBlock`s may be removed. Example:
* ```
* x = 1; // s1
* if (true) { // s2
* x = 2; // s3
* } else {
* x = 3; // s4
* }
* ```
* The `BasicBlock` successor edge from the basic block containing `s1`
* and `s2` to the basic block containing `s4` is removed.
*
* - `PrimitiveBasicBlock`s may be split up into two or more
* `BasicBlock`s: Internal nodes of `PrimitiveBasicBlock`s whose
* predecessor edges have been removed (unreachable code) will be entry
* points of new `BasicBlock`s. Consequently, each entry point of a
* `PrimitiveBasicBlock` will also be an entry point of a `BasicBlock`,
* but the converse does not necessarily hold. Example:
* ```
* x = 1; // s5
* abort(); // s6
* x = 2; // s7
* ```
* `s5`-`s7` belong to the same `PrimitiveBasicBlock`, but the CFG edge
* from `s6` to `s7` is impossible, so `s7` will be the entry point of
* its own (unreachable) `BasicBlock`.
*
* Note that, although possible, two or more `PrimitiveBasicBlock`s are
* never merged to one `BasicBlock`: Consider the first example above;
* it would be possible to define a single `BasicBlock` consisting of
* `s1`-`s3`, however, the result would be counter-intuitive.
*/
private import Cached
cached
private module Cached {
/**
* Any node that is the entry point of a primitive basic block is
* also the entry point of a basic block. In addition, all nodes
* with a primitive successor, where the predecessor has been pruned
* (that is, `getAPredecessor()` does not exist while a predecessor
* using the primitive `successors_extended` relation does exist), is also
* considered a basic block entry node.
*/
cached
predicate basic_block_entry_node(ControlFlowNode node) {
primitive_basic_block_entry_node(node) or
non_primitive_basic_block_entry_node(node)
}
private predicate non_primitive_basic_block_entry_node(ControlFlowNode node) {
not primitive_basic_block_entry_node(node) and
not exists(node.getAPredecessor()) and
successors_extended(node, _)
}
/**
* Holds if basic block `bb` equals a primitive basic block.
*
* There are two situations in which this is *not* the case:
*
* - Either the entry node of `bb` does not correspond to an
* entry node of a primitive basic block, or
* - The primitive basic block with the same entry node contains
* a (non-entry) node which is the entry node of a non-primitive
* basic block (that is, the primitive basic block has been split
* up).
*
* This predicate is used for performance optimization only:
* Whenever a `BasicBlock` equals a `PrimitiveBasicBlock`, we can
* reuse predicates already computed for `PrimitiveBasicBlocks`.
*/
private predicate equalsPrimitiveBasicBlock(BasicBlock bb) {
primitive_basic_block_entry_node(bb) and
not exists(int i |
i > 0 and
non_primitive_basic_block_entry_node(bb.(PrimitiveBasicBlock).getNode(i))
)
}
/** Holds if `node` is the `pos`th control-flow node in basic block `bb`. */
cached
predicate basic_block_member(ControlFlowNode node, BasicBlock bb, int pos) {
equalsPrimitiveBasicBlock(bb) and primitive_basic_block_member(node, bb, pos) // reuse already computed relation
or
non_primitive_basic_block_member(node, bb, pos)
}
private predicate non_primitive_basic_block_member(ControlFlowNode node, BasicBlock bb, int pos) {
not equalsPrimitiveBasicBlock(bb) and node = bb and pos = 0
or
not node instanceof BasicBlock and
exists(ControlFlowNode pred | successors_extended(pred, node) |
non_primitive_basic_block_member(pred, bb, pos - 1)
)
}
/** Gets the number of control-flow nodes in the basic block `bb`. */
cached
int bb_length(BasicBlock bb) {
if equalsPrimitiveBasicBlock(bb)
then result = bb.(PrimitiveBasicBlock).length() // reuse already computed relation
else result = strictcount(ControlFlowNode node | basic_block_member(node, bb, _))
}
/** Successor relation for basic blocks. */
cached
predicate bb_successor_cached(BasicBlock pred, BasicBlock succ) {
exists(ControlFlowNode last |
basic_block_member(last, pred, bb_length(pred) - 1) and
last.getASuccessor() = succ
)
}
}
predicate bb_successor = bb_successor_cached/2;
/**
* A basic block in the C/C++ control-flow graph.
*
* A basic block is a simple sequence of control-flow nodes,
* connected to each other and nothing else:
*
* ```
* A - B - C - D ABCD is a basic block
* ```
*
* Any incoming or outgoing edges break the block into two:
*
* ```
* A - B > C - D AB is a basic block and CD is a basic block (C has two incoming edges)
*
*
* A - B < C - D AB is a basic block and CD is a basic block (B has two outgoing edges)
* ```
*/
class BasicBlock extends ControlFlowNodeBase {
BasicBlock() { basic_block_entry_node(this) }
/** Holds if this basic block contains `node`. */
predicate contains(ControlFlowNode node) { basic_block_member(node, this, _) }
/** Gets the `ControlFlowNode` at position `pos` in this basic block. */
ControlFlowNode getNode(int pos) { basic_block_member(result, this, pos) }
/** Gets a `ControlFlowNode` in this basic block. */
ControlFlowNode getANode() { basic_block_member(result, this, _) }
/** Gets a `BasicBlock` that is a direct successor of this basic block. */
BasicBlock getASuccessor() { bb_successor(this, result) }
/** Gets a `BasicBlock` that is a direct predecessor of this basic block. */
BasicBlock getAPredecessor() { bb_successor(result, this) }
/**
* Gets a `BasicBlock` such that the control-flow edge `(this, result)` may be taken
* when the outgoing edge of this basic block is an expression that is true.
*/
BasicBlock getATrueSuccessor() { result.getStart() = this.getEnd().getATrueSuccessor() }
/**
* Gets a `BasicBlock` such that the control-flow edge `(this, result)` may be taken
* when the outgoing edge of this basic block is an expression that is false.
*/
BasicBlock getAFalseSuccessor() { result.getStart() = this.getEnd().getAFalseSuccessor() }
/** Gets the final `ControlFlowNode` of this basic block. */
ControlFlowNode getEnd() { basic_block_member(result, this, bb_length(this) - 1) }
/** Gets the first `ControlFlowNode` of this basic block. */
ControlFlowNode getStart() { result = this }
/** Gets the number of `ControlFlowNode`s in this basic block. */
int length() { result = bb_length(this) }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*
* Yields no result if this basic block spans multiple source files.
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.hasLocationInfoInternal(filepath, startline, startcolumn, filepath, endline, endcolumn)
}
pragma[noinline]
private predicate hasLocationInfoInternal(
string file, int line, int col, string endf, int endl, int endc
) {
this.getStart().getLocation().hasLocationInfo(file, line, col, _, _) and
this.getEnd().getLocation().hasLocationInfo(endf, _, _, endl, endc)
}
/** Gets the function containing this basic block. */
Function getEnclosingFunction() { result = this.getStart().getControlFlowScope() }
/**
* Holds if this basic block is in a loop of the control-flow graph. This
* includes loops created by `goto` statements. This predicate may not hold
* even if this basic block is syntactically inside a `while` loop if the
* necessary back edges are unreachable.
*/
predicate inLoop() { this.getASuccessor+() = this }
/**
* Holds if control flow may reach this basic block from a function entry
* point or any handler of a reachable `try` statement.
*/
predicate isReachable() {
exists(Function f | f.getBlock() = this)
or
exists(TryStmt t, BasicBlock tryblock |
// a `Handler` precedes the `CatchBlock`, and is always the beginning
// of a new `BasicBlock` (see `primitive_basic_block_entry_node`).
this.(Handler).getTryStmt() = t and
tryblock.isReachable() and
tryblock.contains(t)
)
or
exists(BasicBlock pred | pred.getASuccessor() = this and pred.isReachable())
}
/** Means `not isReachable()`. */
predicate isUnreachable() { not this.isReachable() }
}
/** Correct relation for reachability of ControlFlowNodes. */
predicate unreachable(ControlFlowNode n) {
exists(BasicBlock bb | bb.contains(n) and bb.isUnreachable())
}
/**
* An entry point of a function.
*/
class EntryBasicBlock extends BasicBlock {
EntryBasicBlock() { exists(Function f | this = f.getEntryPoint()) }
}
/**
* A basic block whose last node is the exit point of a function.
*/
class ExitBasicBlock extends BasicBlock {
ExitBasicBlock() {
this.getEnd() instanceof Function or
aborting(this.getEnd())
}
}