Skip to content

Commit 0f141ed

Browse files
authored
Merge pull request #5737 from dbartol/dbartol/smart-pointers/work
C++: IR Alias Analysis for smart pointers
2 parents 3737764 + 3b04bed commit 0f141ed

File tree

21 files changed

+1151
-291
lines changed

21 files changed

+1151
-291
lines changed

cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,12 @@ private predicate exprToExprStep_nocfg(Expr fromExpr, Expr toExpr) {
694694
fromExpr = call.getQualifier()
695695
) and
696696
call.getTarget() = f and
697-
outModel.isReturnValue()
697+
// AST dataflow treats a reference as if it were the referred-to object, while the dataflow
698+
// models treat references as pointers. If the return type of the call is a reference, then
699+
// look for data flow the the referred-to object, rather than the reference itself.
700+
if call.getType().getUnspecifiedType() instanceof ReferenceType
701+
then outModel.isReturnValueDeref()
702+
else outModel.isReturnValue()
698703
)
699704
)
700705
}

cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,19 @@ class CallInstruction extends Instruction {
16451645
* Gets the number of arguments of the call, including the `this` pointer, if any.
16461646
*/
16471647
final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) }
1648+
1649+
/**
1650+
* Holds if the result is a side effect for the argument at the specified index, or `this` if
1651+
* `index` is `-1`.
1652+
*
1653+
* This helper predicate makes it easy to join on both of these columns at once, avoiding
1654+
* pathological join orders in case the argument index should get joined first.
1655+
*/
1656+
pragma[noinline]
1657+
final SideEffectInstruction getAParameterSideEffect(int index) {
1658+
this = result.getPrimaryInstruction() and
1659+
index = result.(IndexedInstruction).getIndex()
1660+
}
16481661
}
16491662

16501663
/**

cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll

Lines changed: 136 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,71 @@ private import AliasAnalysisImports
44

55
private class IntValue = Ints::IntValue;
66

7+
/**
8+
* If `instr` is a `SideEffectInstruction`, gets the primary `CallInstruction` that caused the side
9+
* effect. If `instr` is a `CallInstruction`, gets that same `CallInstruction`.
10+
*/
11+
private CallInstruction getPrimaryCall(Instruction instr) {
12+
result = instr
13+
or
14+
result = instr.(SideEffectInstruction).getPrimaryInstruction()
15+
}
16+
17+
/**
18+
* Holds if `operand` serves as an input argument (or indirection) to `call`, in the position
19+
* specified by `input`.
20+
*/
21+
private predicate isCallInput(
22+
CallInstruction call, Operand operand, AliasModels::FunctionInput input
23+
) {
24+
call = getPrimaryCall(operand.getUse()) and
25+
(
26+
exists(int index |
27+
input.isParameterOrQualifierAddress(index) and
28+
operand = call.getArgumentOperand(index)
29+
)
30+
or
31+
exists(int index, ReadSideEffectInstruction read |
32+
input.isParameterDerefOrQualifierObject(index) and
33+
read = call.getAParameterSideEffect(index) and
34+
operand = read.getSideEffectOperand()
35+
)
36+
)
37+
}
38+
39+
/**
40+
* Holds if `instr` serves as a return value or output argument indirection for `call`, in the
41+
* position specified by `output`.
42+
*/
43+
private predicate isCallOutput(
44+
CallInstruction call, Instruction instr, AliasModels::FunctionOutput output
45+
) {
46+
call = getPrimaryCall(instr) and
47+
(
48+
output.isReturnValue() and instr = call
49+
or
50+
exists(int index, WriteSideEffectInstruction write |
51+
output.isParameterDerefOrQualifierObject(index) and
52+
write = call.getAParameterSideEffect(index) and
53+
instr = write
54+
)
55+
)
56+
}
57+
58+
/**
59+
* Holds if the address in `operand` flows directly to the result of `resultInstr` due to modeled
60+
* address flow through a function call.
61+
*/
62+
private predicate hasAddressFlowThroughCall(Operand operand, Instruction resultInstr) {
63+
exists(
64+
CallInstruction call, AliasModels::FunctionInput input, AliasModels::FunctionOutput output
65+
|
66+
call.getStaticCallTarget().(AliasModels::AliasFunction).hasAddressFlow(input, output) and
67+
isCallInput(call, operand, input) and
68+
isCallOutput(call, resultInstr, output)
69+
)
70+
}
71+
772
/**
873
* Holds if the operand `tag` of instruction `instr` is used in a way that does
974
* not result in any address held in that operand from escaping beyond the
@@ -34,7 +99,7 @@ private predicate operandIsConsumedWithoutEscaping(Operand operand) {
3499

35100
private predicate operandEscapesDomain(Operand operand) {
36101
not operandIsConsumedWithoutEscaping(operand) and
37-
not operandIsPropagated(operand, _) and
102+
not operandIsPropagated(operand, _, _) and
38103
not isArgumentForParameter(_, operand, _) and
39104
not isOnlyEscapesViaReturnArgument(operand) and
40105
not operand.getUse() instanceof ReturnValueInstruction and
@@ -69,67 +134,67 @@ IntValue getPointerBitOffset(PointerOffsetInstruction instr) {
69134
}
70135

71136
/**
72-
* Holds if any address held in operand `tag` of instruction `instr` is
73-
* propagated to the result of `instr`, offset by the number of bits in
74-
* `bitOffset`. If the address is propagated, but the offset is not known to be
75-
* a constant, then `bitOffset` is unknown.
137+
* Holds if any address held in operand `operand` is propagated to the result of `instr`, offset by
138+
* the number of bits in `bitOffset`. If the address is propagated, but the offset is not known to
139+
* be a constant, then `bitOffset` is `unknown()`.
76140
*/
77-
private predicate operandIsPropagated(Operand operand, IntValue bitOffset) {
78-
exists(Instruction instr |
79-
instr = operand.getUse() and
80-
(
81-
// Converting to a non-virtual base class adds the offset of the base class.
82-
exists(ConvertToNonVirtualBaseInstruction convert |
83-
convert = instr and
84-
bitOffset = Ints::mul(convert.getDerivation().getByteOffset(), 8)
85-
)
86-
or
87-
// Conversion using dynamic_cast results in an unknown offset
88-
instr instanceof CheckedConvertOrNullInstruction and
89-
bitOffset = Ints::unknown()
90-
or
91-
// Converting to a derived class subtracts the offset of the base class.
92-
exists(ConvertToDerivedInstruction convert |
93-
convert = instr and
94-
bitOffset = Ints::neg(Ints::mul(convert.getDerivation().getByteOffset(), 8))
95-
)
96-
or
97-
// Converting to a virtual base class adds an unknown offset.
98-
instr instanceof ConvertToVirtualBaseInstruction and
99-
bitOffset = Ints::unknown()
100-
or
101-
// Conversion to another pointer type propagates the source address.
102-
exists(ConvertInstruction convert, IRType resultType |
103-
convert = instr and
104-
resultType = convert.getResultIRType() and
105-
resultType instanceof IRAddressType and
106-
bitOffset = 0
107-
)
108-
or
109-
// Adding an integer to or subtracting an integer from a pointer propagates
110-
// the address with an offset.
111-
exists(PointerOffsetInstruction ptrOffset |
112-
ptrOffset = instr and
113-
operand = ptrOffset.getLeftOperand() and
114-
bitOffset = getPointerBitOffset(ptrOffset)
115-
)
116-
or
117-
// Computing a field address from a pointer propagates the address plus the
118-
// offset of the field.
119-
bitOffset = Language::getFieldBitOffset(instr.(FieldAddressInstruction).getField())
120-
or
121-
// A copy propagates the source value.
122-
operand = instr.(CopyInstruction).getSourceValueOperand() and bitOffset = 0
123-
or
124-
// Some functions are known to propagate an argument
125-
isAlwaysReturnedArgument(operand) and bitOffset = 0
141+
private predicate operandIsPropagated(Operand operand, IntValue bitOffset, Instruction instr) {
142+
// Some functions are known to propagate an argument
143+
hasAddressFlowThroughCall(operand, instr) and
144+
bitOffset = 0
145+
or
146+
instr = operand.getUse() and
147+
(
148+
// Converting to a non-virtual base class adds the offset of the base class.
149+
exists(ConvertToNonVirtualBaseInstruction convert |
150+
convert = instr and
151+
bitOffset = Ints::mul(convert.getDerivation().getByteOffset(), 8)
152+
)
153+
or
154+
// Conversion using dynamic_cast results in an unknown offset
155+
instr instanceof CheckedConvertOrNullInstruction and
156+
bitOffset = Ints::unknown()
157+
or
158+
// Converting to a derived class subtracts the offset of the base class.
159+
exists(ConvertToDerivedInstruction convert |
160+
convert = instr and
161+
bitOffset = Ints::neg(Ints::mul(convert.getDerivation().getByteOffset(), 8))
162+
)
163+
or
164+
// Converting to a virtual base class adds an unknown offset.
165+
instr instanceof ConvertToVirtualBaseInstruction and
166+
bitOffset = Ints::unknown()
167+
or
168+
// Conversion to another pointer type propagates the source address.
169+
exists(ConvertInstruction convert, IRType resultType |
170+
convert = instr and
171+
resultType = convert.getResultIRType() and
172+
resultType instanceof IRAddressType and
173+
bitOffset = 0
126174
)
175+
or
176+
// Adding an integer to or subtracting an integer from a pointer propagates
177+
// the address with an offset.
178+
exists(PointerOffsetInstruction ptrOffset |
179+
ptrOffset = instr and
180+
operand = ptrOffset.getLeftOperand() and
181+
bitOffset = getPointerBitOffset(ptrOffset)
182+
)
183+
or
184+
// Computing a field address from a pointer propagates the address plus the
185+
// offset of the field.
186+
bitOffset = Language::getFieldBitOffset(instr.(FieldAddressInstruction).getField())
187+
or
188+
// A copy propagates the source value.
189+
operand = instr.(CopyInstruction).getSourceValueOperand() and bitOffset = 0
127190
)
128191
}
129192

130193
private predicate operandEscapesNonReturn(Operand operand) {
131-
// The address is propagated to the result of the instruction, and that result itself is returned
132-
operandIsPropagated(operand, _) and resultEscapesNonReturn(operand.getUse())
194+
exists(Instruction instr |
195+
// The address is propagated to the result of the instruction, and that result itself is returned
196+
operandIsPropagated(operand, _, instr) and resultEscapesNonReturn(instr)
197+
)
133198
or
134199
// The operand is used in a function call which returns it, and the return value is then returned
135200
exists(CallInstruction ci, Instruction init |
@@ -151,9 +216,11 @@ private predicate operandEscapesNonReturn(Operand operand) {
151216
}
152217

153218
private predicate operandMayReachReturn(Operand operand) {
154-
// The address is propagated to the result of the instruction, and that result itself is returned
155-
operandIsPropagated(operand, _) and
156-
resultMayReachReturn(operand.getUse())
219+
exists(Instruction instr |
220+
// The address is propagated to the result of the instruction, and that result itself is returned
221+
operandIsPropagated(operand, _, instr) and
222+
resultMayReachReturn(instr)
223+
)
157224
or
158225
// The operand is used in a function call which returns it, and the return value is then returned
159226
exists(CallInstruction ci, Instruction init |
@@ -173,9 +240,9 @@ private predicate operandMayReachReturn(Operand operand) {
173240

174241
private predicate operandReturned(Operand operand, IntValue bitOffset) {
175242
// The address is propagated to the result of the instruction, and that result itself is returned
176-
exists(IntValue bitOffset1, IntValue bitOffset2 |
177-
operandIsPropagated(operand, bitOffset1) and
178-
resultReturned(operand.getUse(), bitOffset2) and
243+
exists(Instruction instr, IntValue bitOffset1, IntValue bitOffset2 |
244+
operandIsPropagated(operand, bitOffset1, instr) and
245+
resultReturned(instr, bitOffset2) and
179246
bitOffset = Ints::add(bitOffset1, bitOffset2)
180247
)
181248
or
@@ -214,13 +281,6 @@ private predicate isArgumentForParameter(
214281
)
215282
}
216283

217-
private predicate isAlwaysReturnedArgument(Operand operand) {
218-
exists(AliasModels::AliasFunction f |
219-
f = operand.getUse().(CallInstruction).getStaticCallTarget() and
220-
f.parameterIsAlwaysReturned(operand.(PositionalArgumentOperand).getIndex())
221-
)
222-
}
223-
224284
private predicate isOnlyEscapesViaReturnArgument(Operand operand) {
225285
exists(AliasModels::AliasFunction f |
226286
f = operand.getUse().(CallInstruction).getStaticCallTarget() and
@@ -270,12 +330,15 @@ predicate allocationEscapes(Configuration::Allocation allocation) {
270330
/**
271331
* Equivalent to `operandIsPropagated()`, but includes interprocedural propagation.
272332
*/
273-
private predicate operandIsPropagatedIncludingByCall(Operand operand, IntValue bitOffset) {
274-
operandIsPropagated(operand, bitOffset)
333+
private predicate operandIsPropagatedIncludingByCall(
334+
Operand operand, IntValue bitOffset, Instruction instr
335+
) {
336+
operandIsPropagated(operand, bitOffset, instr)
275337
or
276338
exists(CallInstruction call, Instruction init |
277339
isArgumentForParameter(call, operand, init) and
278-
resultReturned(init, bitOffset)
340+
resultReturned(init, bitOffset) and
341+
instr = call
279342
)
280343
}
281344

@@ -292,8 +355,7 @@ private predicate hasBaseAndOffset(AddressOperand addrOperand, Instruction base,
292355
// We already have an offset from `middle`.
293356
hasBaseAndOffset(addrOperand, middle, previousBitOffset) and
294357
// `middle` is propagated from `base`.
295-
middleOperand = middle.getAnOperand() and
296-
operandIsPropagatedIncludingByCall(middleOperand, additionalBitOffset) and
358+
operandIsPropagatedIncludingByCall(middleOperand, additionalBitOffset, middle) and
297359
base = middleOperand.getDef() and
298360
bitOffset = Ints::add(previousBitOffset, additionalBitOffset)
299361
)

cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,21 @@ class DynamicAllocation extends Allocation, TDynamicAllocation {
105105
DynamicAllocation() { this = TDynamicAllocation(call) }
106106

107107
final override string toString() {
108-
result = call.toString() + " at " + call.getLocation() // This isn't performant, but it's only used in test/dump code right now.
108+
// This isn't performant, but it's only used in test/dump code right now.
109+
// Dynamic allocations within a function are numbered in the order by start
110+
// line number. This keeps them stable when the function moves within the
111+
// file, or when non-allocating lines are added and removed within the
112+
// function.
113+
exists(int i |
114+
result = "dynamic{" + i.toString() + "}" and
115+
call =
116+
rank[i](CallInstruction rangeCall |
117+
exists(TDynamicAllocation(rangeCall)) and
118+
rangeCall.getEnclosingIRFunction() = call.getEnclosingIRFunction()
119+
|
120+
rangeCall order by rangeCall.getLocation().getStartLine()
121+
)
122+
)
109123
}
110124

111125
final override CallInstruction getABaseInstruction() { result = call }

cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,19 @@ class CallInstruction extends Instruction {
16451645
* Gets the number of arguments of the call, including the `this` pointer, if any.
16461646
*/
16471647
final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) }
1648+
1649+
/**
1650+
* Holds if the result is a side effect for the argument at the specified index, or `this` if
1651+
* `index` is `-1`.
1652+
*
1653+
* This helper predicate makes it easy to join on both of these columns at once, avoiding
1654+
* pathological join orders in case the argument index should get joined first.
1655+
*/
1656+
pragma[noinline]
1657+
final SideEffectInstruction getAParameterSideEffect(int index) {
1658+
this = result.getPrimaryInstruction() and
1659+
index = result.(IndexedInstruction).getIndex()
1660+
}
16481661
}
16491662

16501663
/**

cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,19 @@ class CallInstruction extends Instruction {
16451645
* Gets the number of arguments of the call, including the `this` pointer, if any.
16461646
*/
16471647
final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) }
1648+
1649+
/**
1650+
* Holds if the result is a side effect for the argument at the specified index, or `this` if
1651+
* `index` is `-1`.
1652+
*
1653+
* This helper predicate makes it easy to join on both of these columns at once, avoiding
1654+
* pathological join orders in case the argument index should get joined first.
1655+
*/
1656+
pragma[noinline]
1657+
final SideEffectInstruction getAParameterSideEffect(int index) {
1658+
this = result.getPrimaryInstruction() and
1659+
index = result.(IndexedInstruction).getIndex()
1660+
}
16481661
}
16491662

16501663
/**

0 commit comments

Comments
 (0)