-
Notifications
You must be signed in to change notification settings - Fork 1.8k
C++: Wire up param/arg indirections in data flow #3123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b622d62
08c53d4
95f116e
3b76509
e37aab5
0743c42
3a89f43
48d2bd6
250e12a
038bea2
1018eaf
3cd377e
5f0d283
771fd0b
10dfa49
8f702d4
ad292d8
e292eee
df96f8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,7 +54,7 @@ class Node extends TIRDataFlowNode { | |
/** Gets the argument that defines this `DefinitionByReferenceNode`, if any. */ | ||
Expr asDefiningArgument() { result = this.(DefinitionByReferenceNode).getArgument() } | ||
|
||
/** Gets the parameter corresponding to this node, if any. */ | ||
/** Gets the positional parameter corresponding to this node, if any. */ | ||
Parameter asParameter() { result = this.(ExplicitParameterNode).getParameter() } | ||
|
||
/** | ||
|
@@ -156,44 +156,90 @@ class ExprNode extends InstructionNode { | |
} | ||
|
||
/** | ||
* A node representing a `Parameter`. This includes both explicit parameters such | ||
* as `x` in `f(x)` and implicit parameters such as `this` in `x.f()` | ||
* INTERNAL: do not use. Translates a parameter/argument index into a negative | ||
* number that denotes the index of its side effect (pointer indirection). | ||
*/ | ||
bindingset[index] | ||
int getArgumentPosOfSideEffect(int index) { | ||
// -1 -> -2 | ||
// 0 -> -3 | ||
// 1 -> -4 | ||
// ... | ||
result = -3 - index | ||
} | ||
|
||
/** | ||
* The value of a parameter at function entry, viewed as a node in a data | ||
* flow graph. This includes both explicit parameters such as `x` in `f(x)` | ||
* and implicit parameters such as `this` in `x.f()`. | ||
* | ||
* To match a specific kind of parameter, consider using one of the subclasses | ||
* `ExplicitParameterNode`, `ThisParameterNode`, or | ||
* `ParameterIndirectionNode`. | ||
*/ | ||
class ParameterNode extends InstructionNode { | ||
override InitializeParameterInstruction instr; | ||
ParameterNode() { | ||
// To avoid making this class abstract, we enumerate its values here | ||
instr instanceof InitializeParameterInstruction | ||
or | ||
instr instanceof InitializeIndirectionInstruction | ||
} | ||
|
||
/** | ||
* Holds if this node is the parameter of `c` at the specified (zero-based) | ||
* position. The implicit `this` parameter is considered to have index `-1`. | ||
* Holds if this node is the parameter of `f` at the specified position. The | ||
* implicit `this` parameter is considered to have position `-1`, and | ||
* pointer-indirection parameters are at further negative positions. | ||
*/ | ||
predicate isParameterOf(Function f, int i) { none() } // overriden by subclasses | ||
predicate isParameterOf(Function f, int pos) { none() } // overridden by subclasses | ||
} | ||
|
||
/** | ||
* The value of a parameter at function entry, viewed as a node in a data | ||
* flow graph. | ||
*/ | ||
/** An explicit positional parameter, not including `this` or `...`. */ | ||
private class ExplicitParameterNode extends ParameterNode { | ||
override InitializeParameterInstruction instr; | ||
|
||
ExplicitParameterNode() { exists(instr.getParameter()) } | ||
|
||
override predicate isParameterOf(Function f, int i) { f.getParameter(i) = instr.getParameter() } | ||
override predicate isParameterOf(Function f, int pos) { | ||
f.getParameter(pos) = instr.getParameter() | ||
} | ||
|
||
/** Gets the parameter corresponding to this node. */ | ||
/** Gets the `Parameter` associated with this node. */ | ||
Parameter getParameter() { result = instr.getParameter() } | ||
|
||
override string toString() { result = instr.getParameter().toString() } | ||
} | ||
|
||
private class ThisParameterNode extends ParameterNode { | ||
/** An implicit `this` parameter. */ | ||
class ThisParameterNode extends ParameterNode { | ||
override InitializeParameterInstruction instr; | ||
|
||
ThisParameterNode() { instr.getIRVariable() instanceof IRThisVariable } | ||
|
||
override predicate isParameterOf(Function f, int i) { | ||
i = -1 and instr.getEnclosingFunction() = f | ||
override predicate isParameterOf(Function f, int pos) { | ||
pos = -1 and instr.getEnclosingFunction() = f | ||
} | ||
|
||
override string toString() { result = "this" } | ||
} | ||
|
||
/** A synthetic parameter to model the pointed-to object of a pointer parameter. */ | ||
class ParameterIndirectionNode extends ParameterNode { | ||
override InitializeIndirectionInstruction instr; | ||
|
||
override predicate isParameterOf(Function f, int pos) { | ||
exists(int index | | ||
f.getParameter(index) = instr.getParameter() | ||
or | ||
index = -1 and | ||
instr.getIRVariable().(IRThisVariable).getEnclosingFunction() = f | ||
| | ||
pos = getArgumentPosOfSideEffect(index) | ||
) | ||
} | ||
|
||
override string toString() { result = "*" + instr.getIRVariable().toString() } | ||
} | ||
|
||
/** | ||
* DEPRECATED: Data flow was never an accurate way to determine what | ||
* expressions might be uninitialized. It errs on the side of saying that | ||
|
@@ -341,6 +387,18 @@ class DefinitionByReferenceNode extends InstructionNode { | |
} | ||
} | ||
|
||
/** | ||
* A node representing the memory pointed to by a function argument. | ||
* | ||
* This class exists only in order to override `toString`, which would | ||
* otherwise be the default implementation inherited from `InstructionNode`. | ||
*/ | ||
private class ArgumentIndirectionNode extends InstructionNode { | ||
override ReadSideEffectInstruction instr; | ||
|
||
override string toString() { result = "Argument " + instr.getIndex() + " indirection" } | ||
} | ||
|
||
/** | ||
* A `Node` corresponding to a variable in the program, as opposed to the | ||
* value of that variable at some particular point. This can be used for | ||
|
@@ -442,6 +500,31 @@ private predicate simpleInstructionLocalFlowStep(Instruction iFrom, Instruction | |
or | ||
iTo.(PhiInstruction).getAnOperand().getDef() = iFrom | ||
or | ||
// A read side effect is almost never exact since we don't know exactly how | ||
// much memory the callee will read. | ||
iTo.(ReadSideEffectInstruction).getSideEffectOperand().getAnyDef() = iFrom and | ||
not iFrom.isResultConflated() | ||
or | ||
// Loading a single `int` from an `int *` parameter is not an exact load since | ||
// the parameter may point to an entire array rather than a single `int`. The | ||
// following rule ensures that any flow going into the | ||
// `InitializeIndirectionInstruction`, even if it's for a different array | ||
// element, will propagate to a load of the first element. | ||
// | ||
// Since we're linking `InitializeIndirectionInstruction` and | ||
// `LoadInstruction` together directly, this rule will break if there's any | ||
// reassignment of the parameter indirection, including a conditional one that | ||
// leads to a phi node. | ||
exists(InitializeIndirectionInstruction init | | ||
iFrom = init and | ||
iTo.(LoadInstruction).getSourceValueOperand().getAnyDef() = init and | ||
// Check that the types match. Otherwise we can get flow from an object to | ||
// its fields, which leads to field conflation when there's flow from other | ||
// fields to the object elsewhere. | ||
init.getParameter().getType().getUnspecifiedType().(DerivedType).getBaseType() = | ||
iTo.getResultType().getUnspecifiedType() | ||
Comment on lines
+521
to
+525
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we still get flow from object to field when the type of the field is equal to the parameter type? I'm thinking about a situation with a binary tree like this: struct Tree { Tree *left, *right; };
Tree source();
void sink(Tree*);
void read_left_subtree(Tree* tree) {
sink(tree->left);
}
...
Tree tree = source();
read_left_subtree(&tree); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. I'll add a test to find out. But even if the answer is that such conflation is possible, does that mean it's undesirable? If we want conflation between array indexes, don't we then also want conflation between entries in a tree or a linked list? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right. It's probably reasonable to have object to field flow in such situations. |
||
) | ||
or | ||
// Treat all conversions as flow, even conversions between different numeric types. | ||
iTo.(ConvertInstruction).getUnary() = iFrom | ||
or | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this rule. It sends flow into a
ReadSideEffectInstruction
, which actually means there is flow to the result value of that instruction. But it's an instruction without a result!The rule is here because we need a node to be the
ArgumentNode
inDataFlowPrivate.qll
, and I've chosen the node for theReadSideEffectInstruction
. The alternatives I can see are:iFrom
) as theArgumentNode
. Then a node could be the argument for multiple calls, leading to confusing path explanations.Instruction
andOperand
nodes. The list of good reasons to do this is starting to get long.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can see the effect on path explanations in
argvLocal.expected
, where many new nodes with the awkward nameBufferReadSideEffect
are now there.