C++: Similarly to the previous commit, we throw away the old memory-edges based way of doing read steps. Instead, we use the shared SSA library to transfer flow into a new ReadNode IPA branch, perform the necessary read steps, and then use the shared SSA library to transfer flow out of the ReadNode again.

This commit is contained in:
Mathias Vorreiter Pedersen
2021-10-20 15:18:08 +01:00
parent 5ebefe2d30
commit 8bef79502f
3 changed files with 148 additions and 120 deletions

View File

@@ -203,122 +203,15 @@ predicate storeStep(StoreNode node1, FieldContent f, StoreNode node2) {
* Thus, `node1` references an object with a field `f` whose value ends up in
* `node2`.
*/
private predicate fieldReadStep(Node node1, FieldContent f, Node node2) {
exists(LoadOperand operand |
node2.asOperand() = operand and
node1.asInstruction() = operand.getAnyDef() and
exists(Class c |
c = operand.getAnyDef().getResultType() and
exists(int startBit, int endBit |
operand.getUsedInterval(unbindInt(startBit), unbindInt(endBit)) and
f.hasOffset(c, startBit, endBit)
)
or
getLoadedField(operand.getUse(), f.getAField(), c) and
f.hasOffset(c, _, _)
)
predicate readStep(ReadNode node1, FieldContent f, ReadNode node2) {
exists(FieldAddressInstruction fai |
not fai.getObjectAddress().getResultType().stripType() instanceof Union and
node1.getInstruction() = fai.getObjectAddress() and
node2.getInstruction() = fai and
f.getField() = fai.getField()
)
}
/**
* When a store step happens in a function that looks like an array write such as:
* ```cpp
* void f(int* pa) {
* pa = source();
* }
* ```
* it can be a write to an array, but it can also happen that `f` is called as `f(&a.x)`. If that is
* the case, the `ArrayContent` that was written by the call to `f` should be popped off the access
* path, and a `FieldContent` containing `x` should be pushed instead.
* So this case pops `ArrayContent` off the access path, and the `fieldStoreStepAfterArraySuppression`
* predicate in `storeStep` ensures that we push the right `FieldContent` onto the access path.
*/
predicate suppressArrayRead(Node node1, ArrayContent a, Node node2) {
exists(a) and
exists(WriteSideEffectInstruction write, ChiInstruction chi |
node1.asInstruction() = write and
node2.asInstruction() = chi and
chi.getPartial() = write and
getWrittenField(write, _, _)
)
}
private class ArrayToPointerConvertInstruction extends ConvertInstruction {
ArrayToPointerConvertInstruction() {
this.getUnary().getResultType() instanceof ArrayType and
this.getResultType() instanceof PointerType
}
}
private Instruction skipOneCopyValueInstructionRec(CopyValueInstruction copy) {
copy.getUnary() = result and not result instanceof CopyValueInstruction
or
result = skipOneCopyValueInstructionRec(copy.getUnary())
}
private Instruction skipCopyValueInstructions(Operand op) {
not result instanceof CopyValueInstruction and result = op.getDef()
or
result = skipOneCopyValueInstructionRec(op.getDef())
}
private predicate arrayReadStep(Node node1, ArrayContent a, Node node2) {
exists(a) and
// Explicit dereferences such as `*p` or `p[i]` where `p` is a pointer or array.
exists(LoadOperand operand, Instruction address |
operand.isDefinitionInexact() and
node1.asInstruction() = operand.getAnyDef() and
operand = node2.asOperand() and
address = skipCopyValueInstructions(operand.getAddressOperand()) and
(
address instanceof LoadInstruction or
address instanceof ArrayToPointerConvertInstruction or
address instanceof PointerOffsetInstruction
)
)
}
/**
* In cases such as:
* ```cpp
* void f(int* pa) {
* *pa = source();
* }
* ...
* int x;
* f(&x);
* use(x);
* ```
* the load on `x` in `use(x)` will exactly overlap with its definition (in this case the definition
* is a `WriteSideEffect`). This predicate pops the `ArrayContent` (pushed by the store in `f`)
* from the access path.
*/
private predicate exactReadStep(Node node1, ArrayContent a, Node node2) {
exists(a) and
exists(WriteSideEffectInstruction write, ChiInstruction chi |
not chi.isResultConflated() and
chi.getPartial() = write and
node1.asInstruction() = write and
node2.asInstruction() = chi and
// To distinquish this case from the `arrayReadStep` case we require that the entire variable was
// overwritten by the `WriteSideEffectInstruction` (i.e., there is a load that reads the
// entire variable).
exists(LoadInstruction load | load.getSourceValue() = chi)
)
}
/**
* Holds if data can flow from `node1` to `node2` via a read of `f`.
* Thus, `node1` references an object with a field `f` whose value ends up in
* `node2`.
*/
predicate readStep(Node node1, Content f, Node node2) {
fieldReadStep(node1, f, node2) or
arrayReadStep(node1, f, node2) or
exactReadStep(node1, f, node2) or
suppressArrayRead(node1, f, node2)
}
/**
* Holds if values stored inside content `c` are cleared at node `n`.
*/

View File

@@ -21,7 +21,8 @@ private module Cached {
TOperandNode(Operand op) or
TVariableNode(Variable var) or
TStoreNodeInstr(Instruction i) { Ssa::explicitWrite(_, _, i) } or
TStoreNodeOperand(ArgumentOperand op) { Ssa::explicitWrite(_, _, op.getDef()) }
TStoreNodeOperand(ArgumentOperand op) { Ssa::explicitWrite(_, _, op.getDef()) } or
TReadNode(Instruction i) { needsPostReadNode(i) }
cached
predicate localFlowStepCached(Node nodeFrom, Node nodeTo) {
@@ -291,6 +292,61 @@ private class StoreNodeOperand extends StoreNode, TStoreNodeOperand {
override StoreNode getAPredecessor() { operand.getDef() = result.getInstruction() }
}
/**
* INTERNAL: do not use.
*
* A `ReadNode` is a node that has been (or is about to be) the
* source or target of a `readStep`.
*/
class ReadNode extends Node, TReadNode {
Instruction i;
ReadNode() { this = TReadNode(i) }
/** Gets the underlying instruction. */
Instruction getInstruction() { result = i }
override Declaration getEnclosingCallable() { result = this.getFunction() }
override Function getFunction() { result = this.getInstruction().getEnclosingFunction() }
override IRType getType() { result = this.getInstruction().getResultIRType() }
override Location getLocation() { result = this.getInstruction().getLocation() }
override string toString() {
result = instructionNode(this.getInstruction()).toString() + " [read]"
}
/** Gets a load instruction that uses the address computed by this read node. */
final Instruction getALoadInstruction() {
Ssa::addressFlowTC(this.getInstruction(), Ssa::getSourceAddress(result))
}
/**
* Gets a read node with an underlying instruction that is used by this
* underlying instruction to compute an address of a load instruction.
*/
final ReadNode getAPredecessor() {
Ssa::addressFlow(result.getInstruction(), this.getInstruction())
}
/** The inverse of `ReadNode.getAPredecessor`. */
final ReadNode getASuccessor() { result.getAPredecessor() = this }
/** Holds if this read node computes a value that will not be used for any future read nodes. */
final predicate isTerminal() {
not exists(this.getASuccessor()) and
not readStep(this, _, _)
}
/** Holds if this read node computes a value that has not yet been used for any read operations. */
final predicate isInitial() {
not exists(this.getAPredecessor()) and
not readStep(_, _, this)
}
}
/**
* An expression, viewed as a node in a data flow graph.
*/
@@ -731,6 +787,13 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
StoreNodeFlow::flowThrough(nodeFrom, nodeTo)
or
StoreNodeFlow::flowOutOf(nodeFrom, nodeTo)
or
// Flow into, through, and out of read nodes
ReadNodeFlow::flowInto(nodeFrom, nodeTo)
or
ReadNodeFlow::flowThrough(nodeFrom, nodeTo)
or
ReadNodeFlow::flowOutOf(nodeFrom, nodeTo)
}
pragma[noinline]
@@ -742,12 +805,65 @@ private predicate getFieldSizeOfClass(Class c, Type type, int size) {
)
}
private predicate isSingleFieldClass(Type type, Operand op) {
exists(int size, Class c |
c = op.getType().getUnderlyingType() and
c.getSize() = size and
getFieldSizeOfClass(c, type, size)
)
private module ReadNodeFlow {
/** Holds if the read node `nodeTo` should receive flow from `nodeFrom`. */
predicate flowInto(Node nodeFrom, ReadNode nodeTo) {
nodeTo.isInitial() and
(
// If we entered through an address operand.
nodeFrom.asOperand().getDef() = nodeTo.getInstruction()
or
// If we entered flow through a memory-producing instruction.
// This can happen if we have flow to an `InitializeParameterIndirection` through
// a `ReadSideEffectInstruction`.
exists(Instruction load, Instruction def |
def = nodeFrom.asInstruction() and
def = Ssa::getSourceValueOperand(load).getAnyDef() and
not def = any(StoreNode store).getStoreInstruction() and
pragma[only_bind_into](nodeTo).getALoadInstruction() = load
)
)
}
/** Holds if the read node `nodeTo` should receive flow from the read node `nodeFrom`. */
predicate flowThrough(ReadNode nodeFrom, ReadNode nodeTo) {
not readStep(nodeFrom, _, _) and
nodeFrom.getASuccessor() = nodeTo
}
/**
* Holds if flow should leave the read node `nFrom` and enter the node `nodeTo`.
* This happens either because there is use-use flow from one of the variables used in
* the read operation, or because we have traversed all the field dereferences in the
* read operation.
*/
predicate flowOutOf(ReadNode nFrom, Node nodeTo) {
// Use-use flow to another use of the same variable instruction
Ssa::ssaFlow(nFrom, nodeTo)
or
not exists(nFrom.getAPredecessor()) and
exists(Node store |
Ssa::explicitWrite(_, store.asInstruction(), nFrom.getInstruction()) and
Ssa::ssaFlow(store, nodeTo)
)
or
// Flow out of read nodes and into memory instructions if we cannot move any further through
// read nodes.
nFrom.isTerminal() and
(
exists(Instruction load |
load = nodeTo.asInstruction() and
Ssa::getSourceAddress(load) = nFrom.getInstruction()
)
or
exists(CallInstruction call, int i |
call.getArgument(i) = nodeTo.asInstruction() and
call.getArgument(i) = nFrom.getInstruction()
)
)
}
}
private module StoreNodeFlow {
/** Holds if the store node `nodeTo` should receive flow from `nodeFrom`. */
predicate flowInto(Node nodeFrom, StoreNode nodeTo) {

View File

@@ -326,6 +326,16 @@ private module Cached {
)
}
private predicate fromReadNode(ReadNode nodeFrom, Node nodeTo) {
exists(IRBlock bb1, int i1, IRBlock bb2, int i2, Use use1, Use use2 |
use1.hasRankInBlock(bb1, i1) and
use2.hasRankInBlock(bb2, i2) and
use1.getOperand().getDef() = nodeFrom.getInstruction() and
adjacentDefRead(_, bb1, i1, bb2, i2) and
flowOutOfAddressStep(use2.getOperand(), nodeTo)
)
}
/**
* Holds if `nodeFrom` is a read or write, and `nTo` is the next subsequent read of the variable
* written (or read) by `storeOrRead`.
@@ -337,9 +347,18 @@ private module Cached {
or
// Def-use flow from a `StoreNode` to an `OperandNode`.
fromStoreNode(nodeFrom, nodeTo)
or
// Use-use flow from a `ReadNode` to an `OperandNode`
fromReadNode(nodeFrom, nodeTo)
}
private predicate flowOutOfAddressStep(Operand operand, Node nTo) {
// Flow into a read node
exists(ReadNode readNode | readNode = nTo |
readNode.isInitial() and
operand.getDef() = readNode.getInstruction()
)
or
exists(StoreNode storeNode, Instruction def |
storeNode = nTo and
def = operand.getDef()