mirror of
https://github.com/github/codeql.git
synced 2026-04-30 03:05:15 +02:00
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.
This commit is contained in:
@@ -246,61 +246,86 @@ private predicate resultEscapesNonReturn(Instruction instr) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the address of the specified local variable or parameter escapes the
|
||||
* domain of the analysis.
|
||||
* Holds if the address of `allocation` escapes outside the domain of the analysis. This can occur
|
||||
* either because the allocation's address is taken within the function and escapes, or because the
|
||||
* allocation is marked as always escaping via `alwaysEscapes()`.
|
||||
*/
|
||||
private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) {
|
||||
// The variable's address escapes if the result of any
|
||||
// VariableAddressInstruction that computes the variable's address escapes.
|
||||
exists(VariableAddressInstruction instr |
|
||||
instr.getIRVariable() = var and
|
||||
resultEscapesNonReturn(instr)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the address of the specified variable escapes the domain of the
|
||||
* analysis.
|
||||
*/
|
||||
predicate variableAddressEscapes(IRVariable var) {
|
||||
predicate allocationEscapes(Configuration::Allocation allocation) {
|
||||
allocation.alwaysEscapes()
|
||||
or
|
||||
exists(IREscapeAnalysisConfiguration config |
|
||||
config.useSoundEscapeAnalysis() and
|
||||
automaticVariableAddressEscapes(var.(IRAutomaticVariable))
|
||||
config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction())
|
||||
)
|
||||
or
|
||||
// All variables with static storage duration have their address escape, even when escape analysis
|
||||
// is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global
|
||||
// variable. Normally, we rely on `AliasedDefinition` to handle that.
|
||||
not var instanceof IRAutomaticVariable
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the result of instruction `instr` points within variable `var`, at
|
||||
* bit offset `bitOffset` within the variable. If the result points within
|
||||
* `var`, but at an unknown or non-constant offset, then `bitOffset` is unknown.
|
||||
* Equivalent to `operandIsPropagated()`, but includes interprocedural propagation.
|
||||
*/
|
||||
predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset) {
|
||||
// The address of a variable points to that variable, at offset 0.
|
||||
instr.(VariableAddressInstruction).getIRVariable() = var and
|
||||
bitOffset = 0
|
||||
private predicate operandIsPropagatedIncludingByCall(Operand operand, IntValue bitOffset) {
|
||||
operandIsPropagated(operand, bitOffset)
|
||||
or
|
||||
// A string literal is just a special read-only global variable.
|
||||
instr.(StringConstantInstruction).getIRVariable() = var and
|
||||
bitOffset = 0
|
||||
or
|
||||
exists(Operand operand, IntValue originalBitOffset, IntValue propagatedBitOffset |
|
||||
operand = instr.getAnOperand() and
|
||||
// If an operand is propagated, then the result points to the same variable,
|
||||
// offset by the bit offset from the propagation.
|
||||
resultPointsTo(operand.getAnyDef(), var, originalBitOffset) and
|
||||
(
|
||||
operandIsPropagated(operand, propagatedBitOffset)
|
||||
or
|
||||
exists(CallInstruction ci, Instruction init |
|
||||
isArgumentForParameter(ci, operand, init) and
|
||||
resultReturned(init, propagatedBitOffset)
|
||||
)
|
||||
) and
|
||||
bitOffset = Ints::add(originalBitOffset, propagatedBitOffset)
|
||||
exists(CallInstruction call, Instruction init |
|
||||
isArgumentForParameter(call, operand, init) and
|
||||
resultReturned(init, bitOffset)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `addrOperand` is at offset `bitOffset` from the value of instruction `base`. The offset
|
||||
* may be `unknown()`.
|
||||
*/
|
||||
private predicate hasBaseAndOffset(AddressOperand addrOperand, Instruction base, IntValue bitOffset) {
|
||||
base = addrOperand.getDef() and bitOffset = 0 // Base case
|
||||
or
|
||||
exists(
|
||||
Instruction middle, int previousBitOffset, Operand middleOperand, IntValue additionalBitOffset
|
||||
|
|
||||
// We already have an offset from `middle`.
|
||||
hasBaseAndOffset(addrOperand, middle, previousBitOffset) and
|
||||
// `middle` is propagated from `base`.
|
||||
middleOperand = middle.getAnOperand() and
|
||||
operandIsPropagatedIncludingByCall(middleOperand, additionalBitOffset) and
|
||||
base = middleOperand.getDef() and
|
||||
bitOffset = Ints::add(previousBitOffset, additionalBitOffset)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `addrOperand` is at constant offset `bitOffset` from the value of instruction `base`.
|
||||
* Only holds for the `base` with the longest chain of propagation to `addrOperand`.
|
||||
*/
|
||||
predicate addressOperandBaseAndConstantOffset(
|
||||
AddressOperand addrOperand, Instruction base, int bitOffset
|
||||
) {
|
||||
hasBaseAndOffset(addrOperand, base, bitOffset) and
|
||||
Ints::hasValue(bitOffset) and
|
||||
not exists(Instruction previousBase, int previousBitOffset |
|
||||
hasBaseAndOffset(addrOperand, previousBase, previousBitOffset) and
|
||||
previousBase = base.getAnOperand().getDef() and
|
||||
Ints::hasValue(previousBitOffset)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the allocation into which `addrOperand` points, if known.
|
||||
*/
|
||||
Configuration::Allocation getAddressOperandAllocation(AddressOperand addrOperand) {
|
||||
addressOperandAllocationAndOffset(addrOperand, result, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `addrOperand` is at offset `bitOffset` from a base instruction of `allocation`. The
|
||||
* offset may be `unknown()`.
|
||||
*/
|
||||
predicate addressOperandAllocationAndOffset(
|
||||
AddressOperand addrOperand, Configuration::Allocation allocation, IntValue bitOffset
|
||||
) {
|
||||
exists(Instruction base |
|
||||
allocation.getABaseInstruction() = base and
|
||||
hasBaseAndOffset(addrOperand, base, bitOffset) and
|
||||
not exists(Instruction previousBase |
|
||||
hasBaseAndOffset(addrOperand, previousBase, _) and
|
||||
previousBase = base.getAnOperand().getDef()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
import semmle.code.cpp.ir.internal.IRCppLanguage as Language
|
||||
import semmle.code.cpp.ir.implementation.unaliased_ssa.IR as InputIR
|
||||
import AliasConfiguration as Configuration
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
private import AliasConfigurationInternal
|
||||
private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR
|
||||
private import cpp
|
||||
private import AliasAnalysis
|
||||
|
||||
private newtype TAllocation =
|
||||
TVariableAllocation(IRVariable var) or
|
||||
TIndirectParameterAllocation(IRAutomaticUserVariable var) {
|
||||
exists(InitializeIndirectionInstruction instr | instr.getIRVariable() = var)
|
||||
}
|
||||
|
||||
/**
|
||||
* A memory allocation that can be tracked by the AliasedSSA alias analysis.
|
||||
*/
|
||||
abstract class Allocation extends TAllocation {
|
||||
abstract string toString();
|
||||
|
||||
final string getAllocationString() { result = toString() }
|
||||
|
||||
abstract Instruction getABaseInstruction();
|
||||
|
||||
abstract IRFunction getEnclosingIRFunction();
|
||||
|
||||
abstract Language::Location getLocation();
|
||||
|
||||
abstract string getUniqueId();
|
||||
|
||||
abstract IRType getIRType();
|
||||
|
||||
abstract predicate isReadOnly();
|
||||
|
||||
abstract predicate alwaysEscapes();
|
||||
|
||||
abstract predicate isAlwaysAllocatedOnStack();
|
||||
|
||||
final predicate isUnaliased() { not allocationEscapes(this) }
|
||||
}
|
||||
|
||||
class VariableAllocation extends Allocation, TVariableAllocation {
|
||||
IRVariable var;
|
||||
|
||||
VariableAllocation() { this = TVariableAllocation(var) }
|
||||
|
||||
final override string toString() { result = var.toString() }
|
||||
|
||||
final override VariableInstruction getABaseInstruction() {
|
||||
result.getIRVariable() = var and
|
||||
(result instanceof VariableAddressInstruction or result instanceof StringConstantInstruction)
|
||||
}
|
||||
|
||||
final override IRFunction getEnclosingIRFunction() { result = var.getEnclosingIRFunction() }
|
||||
|
||||
final override Language::Location getLocation() { result = var.getLocation() }
|
||||
|
||||
final override string getUniqueId() { result = var.getUniqueId() }
|
||||
|
||||
final override IRType getIRType() { result = var.getIRType() }
|
||||
|
||||
final override predicate isReadOnly() { var.isReadOnly() }
|
||||
|
||||
final override predicate isAlwaysAllocatedOnStack() { var instanceof IRAutomaticVariable }
|
||||
|
||||
final override predicate alwaysEscapes() {
|
||||
// All variables with static storage duration have their address escape, even when escape analysis
|
||||
// is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global
|
||||
// variable. Normally, we rely on `AliasedDefinition` to handle that.
|
||||
not var instanceof IRAutomaticVariable
|
||||
}
|
||||
|
||||
final IRVariable getIRVariable() { result = var }
|
||||
}
|
||||
|
||||
class IndirectParameterAllocation extends Allocation, TIndirectParameterAllocation {
|
||||
IRAutomaticUserVariable var;
|
||||
|
||||
IndirectParameterAllocation() { this = TIndirectParameterAllocation(var) }
|
||||
|
||||
final override string toString() { result = "*" + var.toString() }
|
||||
|
||||
final override InitializeParameterInstruction getABaseInstruction() {
|
||||
result.getIRVariable() = var
|
||||
}
|
||||
|
||||
final override IRFunction getEnclosingIRFunction() { result = var.getEnclosingIRFunction() }
|
||||
|
||||
final override Language::Location getLocation() { result = var.getLocation() }
|
||||
|
||||
final override string getUniqueId() { result = var.getUniqueId() }
|
||||
|
||||
final override IRType getIRType() { result = var.getIRType() }
|
||||
|
||||
final override predicate isReadOnly() { none() }
|
||||
|
||||
final override predicate isAlwaysAllocatedOnStack() { none() }
|
||||
|
||||
final override predicate alwaysEscapes() { none() }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import semmle.code.cpp.ir.internal.IRCppLanguage as Language
|
||||
@@ -246,61 +246,86 @@ private predicate resultEscapesNonReturn(Instruction instr) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the address of the specified local variable or parameter escapes the
|
||||
* domain of the analysis.
|
||||
* Holds if the address of `allocation` escapes outside the domain of the analysis. This can occur
|
||||
* either because the allocation's address is taken within the function and escapes, or because the
|
||||
* allocation is marked as always escaping via `alwaysEscapes()`.
|
||||
*/
|
||||
private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) {
|
||||
// The variable's address escapes if the result of any
|
||||
// VariableAddressInstruction that computes the variable's address escapes.
|
||||
exists(VariableAddressInstruction instr |
|
||||
instr.getIRVariable() = var and
|
||||
resultEscapesNonReturn(instr)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the address of the specified variable escapes the domain of the
|
||||
* analysis.
|
||||
*/
|
||||
predicate variableAddressEscapes(IRVariable var) {
|
||||
predicate allocationEscapes(Configuration::Allocation allocation) {
|
||||
allocation.alwaysEscapes()
|
||||
or
|
||||
exists(IREscapeAnalysisConfiguration config |
|
||||
config.useSoundEscapeAnalysis() and
|
||||
automaticVariableAddressEscapes(var.(IRAutomaticVariable))
|
||||
config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction())
|
||||
)
|
||||
or
|
||||
// All variables with static storage duration have their address escape, even when escape analysis
|
||||
// is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global
|
||||
// variable. Normally, we rely on `AliasedDefinition` to handle that.
|
||||
not var instanceof IRAutomaticVariable
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the result of instruction `instr` points within variable `var`, at
|
||||
* bit offset `bitOffset` within the variable. If the result points within
|
||||
* `var`, but at an unknown or non-constant offset, then `bitOffset` is unknown.
|
||||
* Equivalent to `operandIsPropagated()`, but includes interprocedural propagation.
|
||||
*/
|
||||
predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset) {
|
||||
// The address of a variable points to that variable, at offset 0.
|
||||
instr.(VariableAddressInstruction).getIRVariable() = var and
|
||||
bitOffset = 0
|
||||
private predicate operandIsPropagatedIncludingByCall(Operand operand, IntValue bitOffset) {
|
||||
operandIsPropagated(operand, bitOffset)
|
||||
or
|
||||
// A string literal is just a special read-only global variable.
|
||||
instr.(StringConstantInstruction).getIRVariable() = var and
|
||||
bitOffset = 0
|
||||
or
|
||||
exists(Operand operand, IntValue originalBitOffset, IntValue propagatedBitOffset |
|
||||
operand = instr.getAnOperand() and
|
||||
// If an operand is propagated, then the result points to the same variable,
|
||||
// offset by the bit offset from the propagation.
|
||||
resultPointsTo(operand.getAnyDef(), var, originalBitOffset) and
|
||||
(
|
||||
operandIsPropagated(operand, propagatedBitOffset)
|
||||
or
|
||||
exists(CallInstruction ci, Instruction init |
|
||||
isArgumentForParameter(ci, operand, init) and
|
||||
resultReturned(init, propagatedBitOffset)
|
||||
)
|
||||
) and
|
||||
bitOffset = Ints::add(originalBitOffset, propagatedBitOffset)
|
||||
exists(CallInstruction call, Instruction init |
|
||||
isArgumentForParameter(call, operand, init) and
|
||||
resultReturned(init, bitOffset)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `addrOperand` is at offset `bitOffset` from the value of instruction `base`. The offset
|
||||
* may be `unknown()`.
|
||||
*/
|
||||
private predicate hasBaseAndOffset(AddressOperand addrOperand, Instruction base, IntValue bitOffset) {
|
||||
base = addrOperand.getDef() and bitOffset = 0 // Base case
|
||||
or
|
||||
exists(
|
||||
Instruction middle, int previousBitOffset, Operand middleOperand, IntValue additionalBitOffset
|
||||
|
|
||||
// We already have an offset from `middle`.
|
||||
hasBaseAndOffset(addrOperand, middle, previousBitOffset) and
|
||||
// `middle` is propagated from `base`.
|
||||
middleOperand = middle.getAnOperand() and
|
||||
operandIsPropagatedIncludingByCall(middleOperand, additionalBitOffset) and
|
||||
base = middleOperand.getDef() and
|
||||
bitOffset = Ints::add(previousBitOffset, additionalBitOffset)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `addrOperand` is at constant offset `bitOffset` from the value of instruction `base`.
|
||||
* Only holds for the `base` with the longest chain of propagation to `addrOperand`.
|
||||
*/
|
||||
predicate addressOperandBaseAndConstantOffset(
|
||||
AddressOperand addrOperand, Instruction base, int bitOffset
|
||||
) {
|
||||
hasBaseAndOffset(addrOperand, base, bitOffset) and
|
||||
Ints::hasValue(bitOffset) and
|
||||
not exists(Instruction previousBase, int previousBitOffset |
|
||||
hasBaseAndOffset(addrOperand, previousBase, previousBitOffset) and
|
||||
previousBase = base.getAnOperand().getDef() and
|
||||
Ints::hasValue(previousBitOffset)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the allocation into which `addrOperand` points, if known.
|
||||
*/
|
||||
Configuration::Allocation getAddressOperandAllocation(AddressOperand addrOperand) {
|
||||
addressOperandAllocationAndOffset(addrOperand, result, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `addrOperand` is at offset `bitOffset` from a base instruction of `allocation`. The
|
||||
* offset may be `unknown()`.
|
||||
*/
|
||||
predicate addressOperandAllocationAndOffset(
|
||||
AddressOperand addrOperand, Configuration::Allocation allocation, IntValue bitOffset
|
||||
) {
|
||||
exists(Instruction base |
|
||||
allocation.getABaseInstruction() = base and
|
||||
hasBaseAndOffset(addrOperand, base, bitOffset) and
|
||||
not exists(Instruction previousBase |
|
||||
hasBaseAndOffset(addrOperand, previousBase, _) and
|
||||
previousBase = base.getAnOperand().getDef()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
import semmle.code.cpp.ir.internal.IRCppLanguage as Language
|
||||
import semmle.code.cpp.ir.implementation.raw.IR as InputIR
|
||||
import AliasConfiguration as Configuration
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
private import AliasConfigurationImports
|
||||
|
||||
/**
|
||||
* A memory allocation that can be tracked by the SimpleSSA alias analysis.
|
||||
* All automatic variables are tracked.
|
||||
*/
|
||||
class Allocation extends IRAutomaticVariable {
|
||||
VariableAddressInstruction getABaseInstruction() { result.getIRVariable() = this }
|
||||
|
||||
final string getAllocationString() { result = toString() }
|
||||
|
||||
predicate alwaysEscapes() {
|
||||
// An automatic variable only escapes if its address is taken and escapes.
|
||||
none()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import semmle.code.cpp.ir.implementation.raw.IR
|
||||
Reference in New Issue
Block a user