Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8bef795

Browse files
committed
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.
1 parent 5ebefe2 commit 8bef795

3 files changed

Lines changed: 148 additions & 120 deletions

File tree

cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll

Lines changed: 6 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -203,122 +203,15 @@ predicate storeStep(StoreNode node1, FieldContent f, StoreNode node2) {
203203
* Thus, `node1` references an object with a field `f` whose value ends up in
204204
* `node2`.
205205
*/
206-
private predicate fieldReadStep(Node node1, FieldContent f, Node node2) {
207-
exists(LoadOperand operand |
208-
node2.asOperand() = operand and
209-
node1.asInstruction() = operand.getAnyDef() and
210-
exists(Class c |
211-
c = operand.getAnyDef().getResultType() and
212-
exists(int startBit, int endBit |
213-
operand.getUsedInterval(unbindInt(startBit), unbindInt(endBit)) and
214-
f.hasOffset(c, startBit, endBit)
215-
)
216-
or
217-
getLoadedField(operand.getUse(), f.getAField(), c) and
218-
f.hasOffset(c, _, _)
219-
)
220-
)
221-
}
222-
223-
/**
224-
* When a store step happens in a function that looks like an array write such as:
225-
* ```cpp
226-
* void f(int* pa) {
227-
* pa = source();
228-
* }
229-
* ```
230-
* it can be a write to an array, but it can also happen that `f` is called as `f(&a.x)`. If that is
231-
* the case, the `ArrayContent` that was written by the call to `f` should be popped off the access
232-
* path, and a `FieldContent` containing `x` should be pushed instead.
233-
* So this case pops `ArrayContent` off the access path, and the `fieldStoreStepAfterArraySuppression`
234-
* predicate in `storeStep` ensures that we push the right `FieldContent` onto the access path.
235-
*/
236-
predicate suppressArrayRead(Node node1, ArrayContent a, Node node2) {
237-
exists(a) and
238-
exists(WriteSideEffectInstruction write, ChiInstruction chi |
239-
node1.asInstruction() = write and
240-
node2.asInstruction() = chi and
241-
chi.getPartial() = write and
242-
getWrittenField(write, _, _)
243-
)
244-
}
245-
246-
private class ArrayToPointerConvertInstruction extends ConvertInstruction {
247-
ArrayToPointerConvertInstruction() {
248-
this.getUnary().getResultType() instanceof ArrayType and
249-
this.getResultType() instanceof PointerType
250-
}
251-
}
252-
253-
private Instruction skipOneCopyValueInstructionRec(CopyValueInstruction copy) {
254-
copy.getUnary() = result and not result instanceof CopyValueInstruction
255-
or
256-
result = skipOneCopyValueInstructionRec(copy.getUnary())
257-
}
258-
259-
private Instruction skipCopyValueInstructions(Operand op) {
260-
not result instanceof CopyValueInstruction and result = op.getDef()
261-
or
262-
result = skipOneCopyValueInstructionRec(op.getDef())
263-
}
264-
265-
private predicate arrayReadStep(Node node1, ArrayContent a, Node node2) {
266-
exists(a) and
267-
// Explicit dereferences such as `*p` or `p[i]` where `p` is a pointer or array.
268-
exists(LoadOperand operand, Instruction address |
269-
operand.isDefinitionInexact() and
270-
node1.asInstruction() = operand.getAnyDef() and
271-
operand = node2.asOperand() and
272-
address = skipCopyValueInstructions(operand.getAddressOperand()) and
273-
(
274-
address instanceof LoadInstruction or
275-
address instanceof ArrayToPointerConvertInstruction or
276-
address instanceof PointerOffsetInstruction
277-
)
278-
)
279-
}
280-
281-
/**
282-
* In cases such as:
283-
* ```cpp
284-
* void f(int* pa) {
285-
* *pa = source();
286-
* }
287-
* ...
288-
* int x;
289-
* f(&x);
290-
* use(x);
291-
* ```
292-
* the load on `x` in `use(x)` will exactly overlap with its definition (in this case the definition
293-
* is a `WriteSideEffect`). This predicate pops the `ArrayContent` (pushed by the store in `f`)
294-
* from the access path.
295-
*/
296-
private predicate exactReadStep(Node node1, ArrayContent a, Node node2) {
297-
exists(a) and
298-
exists(WriteSideEffectInstruction write, ChiInstruction chi |
299-
not chi.isResultConflated() and
300-
chi.getPartial() = write and
301-
node1.asInstruction() = write and
302-
node2.asInstruction() = chi and
303-
// To distinquish this case from the `arrayReadStep` case we require that the entire variable was
304-
// overwritten by the `WriteSideEffectInstruction` (i.e., there is a load that reads the
305-
// entire variable).
306-
exists(LoadInstruction load | load.getSourceValue() = chi)
206+
predicate readStep(ReadNode node1, FieldContent f, ReadNode node2) {
207+
exists(FieldAddressInstruction fai |
208+
not fai.getObjectAddress().getResultType().stripType() instanceof Union and
209+
node1.getInstruction() = fai.getObjectAddress() and
210+
node2.getInstruction() = fai and
211+
f.getField() = fai.getField()
307212
)
308213
}
309214

310-
/**
311-
* Holds if data can flow from `node1` to `node2` via a read of `f`.
312-
* Thus, `node1` references an object with a field `f` whose value ends up in
313-
* `node2`.
314-
*/
315-
predicate readStep(Node node1, Content f, Node node2) {
316-
fieldReadStep(node1, f, node2) or
317-
arrayReadStep(node1, f, node2) or
318-
exactReadStep(node1, f, node2) or
319-
suppressArrayRead(node1, f, node2)
320-
}
321-
322215
/**
323216
* Holds if values stored inside content `c` are cleared at node `n`.
324217
*/

cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll

Lines changed: 123 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ private module Cached {
2121
TOperandNode(Operand op) or
2222
TVariableNode(Variable var) or
2323
TStoreNodeInstr(Instruction i) { Ssa::explicitWrite(_, _, i) } or
24-
TStoreNodeOperand(ArgumentOperand op) { Ssa::explicitWrite(_, _, op.getDef()) }
24+
TStoreNodeOperand(ArgumentOperand op) { Ssa::explicitWrite(_, _, op.getDef()) } or
25+
TReadNode(Instruction i) { needsPostReadNode(i) }
2526

2627
cached
2728
predicate localFlowStepCached(Node nodeFrom, Node nodeTo) {
@@ -291,6 +292,61 @@ private class StoreNodeOperand extends StoreNode, TStoreNodeOperand {
291292
override StoreNode getAPredecessor() { operand.getDef() = result.getInstruction() }
292293
}
293294

295+
/**
296+
* INTERNAL: do not use.
297+
*
298+
* A `ReadNode` is a node that has been (or is about to be) the
299+
* source or target of a `readStep`.
300+
*/
301+
class ReadNode extends Node, TReadNode {
302+
Instruction i;
303+
304+
ReadNode() { this = TReadNode(i) }
305+
306+
/** Gets the underlying instruction. */
307+
Instruction getInstruction() { result = i }
308+
309+
override Declaration getEnclosingCallable() { result = this.getFunction() }
310+
311+
override Function getFunction() { result = this.getInstruction().getEnclosingFunction() }
312+
313+
override IRType getType() { result = this.getInstruction().getResultIRType() }
314+
315+
override Location getLocation() { result = this.getInstruction().getLocation() }
316+
317+
override string toString() {
318+
result = instructionNode(this.getInstruction()).toString() + " [read]"
319+
}
320+
321+
/** Gets a load instruction that uses the address computed by this read node. */
322+
final Instruction getALoadInstruction() {
323+
Ssa::addressFlowTC(this.getInstruction(), Ssa::getSourceAddress(result))
324+
}
325+
326+
/**
327+
* Gets a read node with an underlying instruction that is used by this
328+
* underlying instruction to compute an address of a load instruction.
329+
*/
330+
final ReadNode getAPredecessor() {
331+
Ssa::addressFlow(result.getInstruction(), this.getInstruction())
332+
}
333+
334+
/** The inverse of `ReadNode.getAPredecessor`. */
335+
final ReadNode getASuccessor() { result.getAPredecessor() = this }
336+
337+
/** Holds if this read node computes a value that will not be used for any future read nodes. */
338+
final predicate isTerminal() {
339+
not exists(this.getASuccessor()) and
340+
not readStep(this, _, _)
341+
}
342+
343+
/** Holds if this read node computes a value that has not yet been used for any read operations. */
344+
final predicate isInitial() {
345+
not exists(this.getAPredecessor()) and
346+
not readStep(_, _, this)
347+
}
348+
}
349+
294350
/**
295351
* An expression, viewed as a node in a data flow graph.
296352
*/
@@ -731,6 +787,13 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
731787
StoreNodeFlow::flowThrough(nodeFrom, nodeTo)
732788
or
733789
StoreNodeFlow::flowOutOf(nodeFrom, nodeTo)
790+
or
791+
// Flow into, through, and out of read nodes
792+
ReadNodeFlow::flowInto(nodeFrom, nodeTo)
793+
or
794+
ReadNodeFlow::flowThrough(nodeFrom, nodeTo)
795+
or
796+
ReadNodeFlow::flowOutOf(nodeFrom, nodeTo)
734797
}
735798

736799
pragma[noinline]
@@ -742,12 +805,65 @@ private predicate getFieldSizeOfClass(Class c, Type type, int size) {
742805
)
743806
}
744807

745-
private predicate isSingleFieldClass(Type type, Operand op) {
746-
exists(int size, Class c |
747-
c = op.getType().getUnderlyingType() and
748-
c.getSize() = size and
749-
getFieldSizeOfClass(c, type, size)
750-
)
808+
private module ReadNodeFlow {
809+
/** Holds if the read node `nodeTo` should receive flow from `nodeFrom`. */
810+
predicate flowInto(Node nodeFrom, ReadNode nodeTo) {
811+
nodeTo.isInitial() and
812+
(
813+
// If we entered through an address operand.
814+
nodeFrom.asOperand().getDef() = nodeTo.getInstruction()
815+
or
816+
// If we entered flow through a memory-producing instruction.
817+
// This can happen if we have flow to an `InitializeParameterIndirection` through
818+
// a `ReadSideEffectInstruction`.
819+
exists(Instruction load, Instruction def |
820+
def = nodeFrom.asInstruction() and
821+
def = Ssa::getSourceValueOperand(load).getAnyDef() and
822+
not def = any(StoreNode store).getStoreInstruction() and
823+
pragma[only_bind_into](nodeTo).getALoadInstruction() = load
824+
)
825+
)
826+
}
827+
828+
/** Holds if the read node `nodeTo` should receive flow from the read node `nodeFrom`. */
829+
predicate flowThrough(ReadNode nodeFrom, ReadNode nodeTo) {
830+
not readStep(nodeFrom, _, _) and
831+
nodeFrom.getASuccessor() = nodeTo
832+
}
833+
834+
/**
835+
* Holds if flow should leave the read node `nFrom` and enter the node `nodeTo`.
836+
* This happens either because there is use-use flow from one of the variables used in
837+
* the read operation, or because we have traversed all the field dereferences in the
838+
* read operation.
839+
*/
840+
predicate flowOutOf(ReadNode nFrom, Node nodeTo) {
841+
// Use-use flow to another use of the same variable instruction
842+
Ssa::ssaFlow(nFrom, nodeTo)
843+
or
844+
not exists(nFrom.getAPredecessor()) and
845+
exists(Node store |
846+
Ssa::explicitWrite(_, store.asInstruction(), nFrom.getInstruction()) and
847+
Ssa::ssaFlow(store, nodeTo)
848+
)
849+
or
850+
// Flow out of read nodes and into memory instructions if we cannot move any further through
851+
// read nodes.
852+
nFrom.isTerminal() and
853+
(
854+
exists(Instruction load |
855+
load = nodeTo.asInstruction() and
856+
Ssa::getSourceAddress(load) = nFrom.getInstruction()
857+
)
858+
or
859+
exists(CallInstruction call, int i |
860+
call.getArgument(i) = nodeTo.asInstruction() and
861+
call.getArgument(i) = nFrom.getInstruction()
862+
)
863+
)
864+
}
865+
}
866+
751867
private module StoreNodeFlow {
752868
/** Holds if the store node `nodeTo` should receive flow from `nodeFrom`. */
753869
predicate flowInto(Node nodeFrom, StoreNode nodeTo) {

cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/Ssa.qll

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,16 @@ private module Cached {
326326
)
327327
}
328328

329+
private predicate fromReadNode(ReadNode nodeFrom, Node nodeTo) {
330+
exists(IRBlock bb1, int i1, IRBlock bb2, int i2, Use use1, Use use2 |
331+
use1.hasRankInBlock(bb1, i1) and
332+
use2.hasRankInBlock(bb2, i2) and
333+
use1.getOperand().getDef() = nodeFrom.getInstruction() and
334+
adjacentDefRead(_, bb1, i1, bb2, i2) and
335+
flowOutOfAddressStep(use2.getOperand(), nodeTo)
336+
)
337+
}
338+
329339
/**
330340
* Holds if `nodeFrom` is a read or write, and `nTo` is the next subsequent read of the variable
331341
* written (or read) by `storeOrRead`.
@@ -337,9 +347,18 @@ private module Cached {
337347
or
338348
// Def-use flow from a `StoreNode` to an `OperandNode`.
339349
fromStoreNode(nodeFrom, nodeTo)
350+
or
351+
// Use-use flow from a `ReadNode` to an `OperandNode`
352+
fromReadNode(nodeFrom, nodeTo)
340353
}
341354

342355
private predicate flowOutOfAddressStep(Operand operand, Node nTo) {
356+
// Flow into a read node
357+
exists(ReadNode readNode | readNode = nTo |
358+
readNode.isInitial() and
359+
operand.getDef() = readNode.getInstruction()
360+
)
361+
or
343362
exists(StoreNode storeNode, Instruction def |
344363
storeNode = nTo and
345364
def = operand.getDef()

0 commit comments

Comments
 (0)