Skip to content

Commit 1bbc875

Browse files
author
Dave Bartolomeo
committed
C++/C#: Parameterize alias analysis based on AliasConfiguration
Instead of tracking `IRVariable`s directly, alias analysis now tracks instances of the `Allocation` type provided by its `Configuration` parameter. For unaliased SSA, an `Allocation` is just an `IRAutomaticVariable`. For aliased SSA, an `Allocation` is either an `IRVariable` or the memory pointed to by an indirect parameter.
1 parent b15dd82 commit 1bbc875

12 files changed

Lines changed: 342 additions & 132 deletions

File tree

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

Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -246,61 +246,86 @@ private predicate resultEscapesNonReturn(Instruction instr) {
246246
}
247247

248248
/**
249-
* Holds if the address of the specified local variable or parameter escapes the
250-
* domain of the analysis.
249+
* Holds if the address of `allocation` escapes outside the domain of the analysis. This can occur
250+
* either because the allocation's address is taken within the function and escapes, or because the
251+
* allocation is marked as always escaping via `alwaysEscapes()`.
251252
*/
252-
private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) {
253-
// The variable's address escapes if the result of any
254-
// VariableAddressInstruction that computes the variable's address escapes.
255-
exists(VariableAddressInstruction instr |
256-
instr.getIRVariable() = var and
257-
resultEscapesNonReturn(instr)
253+
predicate allocationEscapes(Configuration::Allocation allocation) {
254+
allocation.alwaysEscapes()
255+
or
256+
exists(IREscapeAnalysisConfiguration config |
257+
config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction())
258258
)
259259
}
260260

261261
/**
262-
* Holds if the address of the specified variable escapes the domain of the
263-
* analysis.
262+
* Equivalent to `operandIsPropagated()`, but includes interprocedural propagation.
264263
*/
265-
predicate variableAddressEscapes(IRVariable var) {
266-
exists(IREscapeAnalysisConfiguration config |
267-
config.useSoundEscapeAnalysis() and
268-
automaticVariableAddressEscapes(var.(IRAutomaticVariable))
269-
)
264+
private predicate operandIsPropagatedIncludingByCall(Operand operand, IntValue bitOffset) {
265+
operandIsPropagated(operand, bitOffset)
270266
or
271-
// All variables with static storage duration have their address escape, even when escape analysis
272-
// is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global
273-
// variable. Normally, we rely on `AliasedDefinition` to handle that.
274-
not var instanceof IRAutomaticVariable
267+
exists(CallInstruction call, Instruction init |
268+
isArgumentForParameter(call, operand, init) and
269+
resultReturned(init, bitOffset)
270+
)
275271
}
276272

277273
/**
278-
* Holds if the result of instruction `instr` points within variable `var`, at
279-
* bit offset `bitOffset` within the variable. If the result points within
280-
* `var`, but at an unknown or non-constant offset, then `bitOffset` is unknown.
274+
* Holds if `addrOperand` is at offset `bitOffset` from the value of instruction `base`. The offset
275+
* may be `unknown()`.
281276
*/
282-
predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset) {
283-
// The address of a variable points to that variable, at offset 0.
284-
instr.(VariableAddressInstruction).getIRVariable() = var and
285-
bitOffset = 0
286-
or
287-
// A string literal is just a special read-only global variable.
288-
instr.(StringConstantInstruction).getIRVariable() = var and
289-
bitOffset = 0
277+
private predicate hasBaseAndOffset(AddressOperand addrOperand, Instruction base, IntValue bitOffset) {
278+
base = addrOperand.getDef() and bitOffset = 0 // Base case
290279
or
291-
exists(Operand operand, IntValue originalBitOffset, IntValue propagatedBitOffset |
292-
operand = instr.getAnOperand() and
293-
// If an operand is propagated, then the result points to the same variable,
294-
// offset by the bit offset from the propagation.
295-
resultPointsTo(operand.getAnyDef(), var, originalBitOffset) and
296-
(
297-
operandIsPropagated(operand, propagatedBitOffset)
298-
or
299-
exists(CallInstruction ci, Instruction init |
300-
isArgumentForParameter(ci, operand, init) and
301-
resultReturned(init, propagatedBitOffset)
302-
)
303-
) and
304-
bitOffset = Ints::add(originalBitOffset, propagatedBitOffset)
280+
exists(
281+
Instruction middle, int previousBitOffset, Operand middleOperand, IntValue additionalBitOffset
282+
|
283+
// We already have an offset from `middle`.
284+
hasBaseAndOffset(addrOperand, middle, previousBitOffset) and
285+
// `middle` is propagated from `base`.
286+
middleOperand = middle.getAnOperand() and
287+
operandIsPropagatedIncludingByCall(middleOperand, additionalBitOffset) and
288+
base = middleOperand.getDef() and
289+
bitOffset = Ints::add(previousBitOffset, additionalBitOffset)
290+
)
291+
}
292+
293+
/**
294+
* Holds if `addrOperand` is at constant offset `bitOffset` from the value of instruction `base`.
295+
* Only holds for the `base` with the longest chain of propagation to `addrOperand`.
296+
*/
297+
predicate addressOperandBaseAndConstantOffset(
298+
AddressOperand addrOperand, Instruction base, int bitOffset
299+
) {
300+
hasBaseAndOffset(addrOperand, base, bitOffset) and
301+
Ints::hasValue(bitOffset) and
302+
not exists(Instruction previousBase, int previousBitOffset |
303+
hasBaseAndOffset(addrOperand, previousBase, previousBitOffset) and
304+
previousBase = base.getAnOperand().getDef() and
305+
Ints::hasValue(previousBitOffset)
306+
)
307+
}
308+
309+
/**
310+
* Gets the allocation into which `addrOperand` points, if known.
311+
*/
312+
Configuration::Allocation getAddressOperandAllocation(AddressOperand addrOperand) {
313+
addressOperandAllocationAndOffset(addrOperand, result, _)
314+
}
315+
316+
/**
317+
* Holds if `addrOperand` is at offset `bitOffset` from a base instruction of `allocation`. The
318+
* offset may be `unknown()`.
319+
*/
320+
predicate addressOperandAllocationAndOffset(
321+
AddressOperand addrOperand, Configuration::Allocation allocation, IntValue bitOffset
322+
) {
323+
exists(Instruction base |
324+
allocation.getABaseInstruction() = base and
325+
hasBaseAndOffset(addrOperand, base, bitOffset) and
326+
not exists(Instruction previousBase |
327+
hasBaseAndOffset(addrOperand, previousBase, _) and
328+
previousBase = base.getAnOperand().getDef()
329+
)
305330
)
306331
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
import semmle.code.cpp.ir.internal.IRCppLanguage as Language
22
import semmle.code.cpp.ir.implementation.unaliased_ssa.IR as InputIR
3+
import AliasConfiguration as Configuration
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
private import AliasConfigurationInternal
2+
private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR
3+
private import cpp
4+
private import AliasAnalysis
5+
6+
private newtype TAllocation =
7+
TVariableAllocation(IRVariable var) or
8+
TIndirectParameterAllocation(IRAutomaticUserVariable var) {
9+
exists(InitializeIndirectionInstruction instr | instr.getIRVariable() = var)
10+
}
11+
12+
/**
13+
* A memory allocation that can be tracked by the AliasedSSA alias analysis.
14+
*/
15+
abstract class Allocation extends TAllocation {
16+
abstract string toString();
17+
18+
final string getAllocationString() { result = toString() }
19+
20+
abstract Instruction getABaseInstruction();
21+
22+
abstract IRFunction getEnclosingIRFunction();
23+
24+
abstract Language::Location getLocation();
25+
26+
abstract string getUniqueId();
27+
28+
abstract IRType getIRType();
29+
30+
abstract predicate isReadOnly();
31+
32+
abstract predicate alwaysEscapes();
33+
34+
abstract predicate isAlwaysAllocatedOnStack();
35+
36+
final predicate isUnaliased() { not allocationEscapes(this) }
37+
}
38+
39+
class VariableAllocation extends Allocation, TVariableAllocation {
40+
IRVariable var;
41+
42+
VariableAllocation() { this = TVariableAllocation(var) }
43+
44+
final override string toString() { result = var.toString() }
45+
46+
final override VariableInstruction getABaseInstruction() {
47+
result.getIRVariable() = var and
48+
(result instanceof VariableAddressInstruction or result instanceof StringConstantInstruction)
49+
}
50+
51+
final override IRFunction getEnclosingIRFunction() { result = var.getEnclosingIRFunction() }
52+
53+
final override Language::Location getLocation() { result = var.getLocation() }
54+
55+
final override string getUniqueId() { result = var.getUniqueId() }
56+
57+
final override IRType getIRType() { result = var.getIRType() }
58+
59+
final override predicate isReadOnly() { var.isReadOnly() }
60+
61+
final override predicate isAlwaysAllocatedOnStack() { var instanceof IRAutomaticVariable }
62+
63+
final override predicate alwaysEscapes() {
64+
// All variables with static storage duration have their address escape, even when escape analysis
65+
// is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global
66+
// variable. Normally, we rely on `AliasedDefinition` to handle that.
67+
not var instanceof IRAutomaticVariable
68+
}
69+
70+
final IRVariable getIRVariable() { result = var }
71+
}
72+
73+
class IndirectParameterAllocation extends Allocation, TIndirectParameterAllocation {
74+
IRAutomaticUserVariable var;
75+
76+
IndirectParameterAllocation() { this = TIndirectParameterAllocation(var) }
77+
78+
final override string toString() { result = "*" + var.toString() }
79+
80+
final override InitializeParameterInstruction getABaseInstruction() {
81+
result.getIRVariable() = var
82+
}
83+
84+
final override IRFunction getEnclosingIRFunction() { result = var.getEnclosingIRFunction() }
85+
86+
final override Language::Location getLocation() { result = var.getLocation() }
87+
88+
final override string getUniqueId() { result = var.getUniqueId() }
89+
90+
final override IRType getIRType() { result = var.getIRType() }
91+
92+
final override predicate isReadOnly() { none() }
93+
94+
final override predicate isAlwaysAllocatedOnStack() { none() }
95+
96+
final override predicate alwaysEscapes() { none() }
97+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import semmle.code.cpp.ir.internal.IRCppLanguage as Language

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

Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -246,61 +246,86 @@ private predicate resultEscapesNonReturn(Instruction instr) {
246246
}
247247

248248
/**
249-
* Holds if the address of the specified local variable or parameter escapes the
250-
* domain of the analysis.
249+
* Holds if the address of `allocation` escapes outside the domain of the analysis. This can occur
250+
* either because the allocation's address is taken within the function and escapes, or because the
251+
* allocation is marked as always escaping via `alwaysEscapes()`.
251252
*/
252-
private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) {
253-
// The variable's address escapes if the result of any
254-
// VariableAddressInstruction that computes the variable's address escapes.
255-
exists(VariableAddressInstruction instr |
256-
instr.getIRVariable() = var and
257-
resultEscapesNonReturn(instr)
253+
predicate allocationEscapes(Configuration::Allocation allocation) {
254+
allocation.alwaysEscapes()
255+
or
256+
exists(IREscapeAnalysisConfiguration config |
257+
config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction())
258258
)
259259
}
260260

261261
/**
262-
* Holds if the address of the specified variable escapes the domain of the
263-
* analysis.
262+
* Equivalent to `operandIsPropagated()`, but includes interprocedural propagation.
264263
*/
265-
predicate variableAddressEscapes(IRVariable var) {
266-
exists(IREscapeAnalysisConfiguration config |
267-
config.useSoundEscapeAnalysis() and
268-
automaticVariableAddressEscapes(var.(IRAutomaticVariable))
269-
)
264+
private predicate operandIsPropagatedIncludingByCall(Operand operand, IntValue bitOffset) {
265+
operandIsPropagated(operand, bitOffset)
270266
or
271-
// All variables with static storage duration have their address escape, even when escape analysis
272-
// is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global
273-
// variable. Normally, we rely on `AliasedDefinition` to handle that.
274-
not var instanceof IRAutomaticVariable
267+
exists(CallInstruction call, Instruction init |
268+
isArgumentForParameter(call, operand, init) and
269+
resultReturned(init, bitOffset)
270+
)
275271
}
276272

277273
/**
278-
* Holds if the result of instruction `instr` points within variable `var`, at
279-
* bit offset `bitOffset` within the variable. If the result points within
280-
* `var`, but at an unknown or non-constant offset, then `bitOffset` is unknown.
274+
* Holds if `addrOperand` is at offset `bitOffset` from the value of instruction `base`. The offset
275+
* may be `unknown()`.
281276
*/
282-
predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset) {
283-
// The address of a variable points to that variable, at offset 0.
284-
instr.(VariableAddressInstruction).getIRVariable() = var and
285-
bitOffset = 0
286-
or
287-
// A string literal is just a special read-only global variable.
288-
instr.(StringConstantInstruction).getIRVariable() = var and
289-
bitOffset = 0
277+
private predicate hasBaseAndOffset(AddressOperand addrOperand, Instruction base, IntValue bitOffset) {
278+
base = addrOperand.getDef() and bitOffset = 0 // Base case
290279
or
291-
exists(Operand operand, IntValue originalBitOffset, IntValue propagatedBitOffset |
292-
operand = instr.getAnOperand() and
293-
// If an operand is propagated, then the result points to the same variable,
294-
// offset by the bit offset from the propagation.
295-
resultPointsTo(operand.getAnyDef(), var, originalBitOffset) and
296-
(
297-
operandIsPropagated(operand, propagatedBitOffset)
298-
or
299-
exists(CallInstruction ci, Instruction init |
300-
isArgumentForParameter(ci, operand, init) and
301-
resultReturned(init, propagatedBitOffset)
302-
)
303-
) and
304-
bitOffset = Ints::add(originalBitOffset, propagatedBitOffset)
280+
exists(
281+
Instruction middle, int previousBitOffset, Operand middleOperand, IntValue additionalBitOffset
282+
|
283+
// We already have an offset from `middle`.
284+
hasBaseAndOffset(addrOperand, middle, previousBitOffset) and
285+
// `middle` is propagated from `base`.
286+
middleOperand = middle.getAnOperand() and
287+
operandIsPropagatedIncludingByCall(middleOperand, additionalBitOffset) and
288+
base = middleOperand.getDef() and
289+
bitOffset = Ints::add(previousBitOffset, additionalBitOffset)
290+
)
291+
}
292+
293+
/**
294+
* Holds if `addrOperand` is at constant offset `bitOffset` from the value of instruction `base`.
295+
* Only holds for the `base` with the longest chain of propagation to `addrOperand`.
296+
*/
297+
predicate addressOperandBaseAndConstantOffset(
298+
AddressOperand addrOperand, Instruction base, int bitOffset
299+
) {
300+
hasBaseAndOffset(addrOperand, base, bitOffset) and
301+
Ints::hasValue(bitOffset) and
302+
not exists(Instruction previousBase, int previousBitOffset |
303+
hasBaseAndOffset(addrOperand, previousBase, previousBitOffset) and
304+
previousBase = base.getAnOperand().getDef() and
305+
Ints::hasValue(previousBitOffset)
306+
)
307+
}
308+
309+
/**
310+
* Gets the allocation into which `addrOperand` points, if known.
311+
*/
312+
Configuration::Allocation getAddressOperandAllocation(AddressOperand addrOperand) {
313+
addressOperandAllocationAndOffset(addrOperand, result, _)
314+
}
315+
316+
/**
317+
* Holds if `addrOperand` is at offset `bitOffset` from a base instruction of `allocation`. The
318+
* offset may be `unknown()`.
319+
*/
320+
predicate addressOperandAllocationAndOffset(
321+
AddressOperand addrOperand, Configuration::Allocation allocation, IntValue bitOffset
322+
) {
323+
exists(Instruction base |
324+
allocation.getABaseInstruction() = base and
325+
hasBaseAndOffset(addrOperand, base, bitOffset) and
326+
not exists(Instruction previousBase |
327+
hasBaseAndOffset(addrOperand, previousBase, _) and
328+
previousBase = base.getAnOperand().getDef()
329+
)
305330
)
306331
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
import semmle.code.cpp.ir.internal.IRCppLanguage as Language
22
import semmle.code.cpp.ir.implementation.raw.IR as InputIR
3+
import AliasConfiguration as Configuration
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
private import AliasConfigurationImports
2+
3+
/**
4+
* A memory allocation that can be tracked by the SimpleSSA alias analysis.
5+
* All automatic variables are tracked.
6+
*/
7+
class Allocation extends IRAutomaticVariable {
8+
VariableAddressInstruction getABaseInstruction() { result.getIRVariable() = this }
9+
10+
final string getAllocationString() { result = toString() }
11+
12+
predicate alwaysEscapes() {
13+
// An automatic variable only escapes if its address is taken and escapes.
14+
none()
15+
}
16+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import semmle.code.cpp.ir.implementation.raw.IR

0 commit comments

Comments
 (0)