Files
codeql/cpp/ql/lib/semmle/code/cpp/padding/Padding.qll
2022-01-20 10:41:35 +01:00

684 lines
23 KiB
Plaintext

import cpp
/**
* Align the specified offset up to the specified alignment boundary.
* The result is the smallest integer `i` such that `(i % alignment) = 0`
* and `(i >= offset)`.
*/
bindingset[offset, alignment]
private int alignUp(int offset, int alignment) {
result = (offset.(float) / alignment).ceil() * alignment
}
private Type stripSpecifiers(Type t) {
result = t.getUnspecifiedType()
or
result = t and not exists(t.getUnspecifiedType())
}
/**
* A string that represents an architecture.
* An "architecture" defines the sizes of variable-sized types and
* the properties of alignment for fields of various
* types. Two are provided out-of-the-box: ILP32 and LP64,
* corresponding to gcc's behavior on x86 and amd64.
*/
abstract class Architecture extends string {
bindingset[this]
Architecture() { any() }
/** Gets the size of a pointer, in bits. */
abstract int pointerSize();
/** Gets the size of a `long int`, in bits. */
abstract int longSize();
/** Gets the size of a `long double`, in bits. */
abstract int longDoubleSize();
/** Gets the size of a `long long`, in bits. */
abstract int longLongSize();
/** Gets the size of a `wchar_t`, in bits. */
abstract int wideCharSize();
/** Gets the alignment boundary for doubles, in bits. */
abstract int doubleAlign();
/** Gets the alignment boundary for long doubles, in bits. */
abstract int longDoubleAlign();
/** Gets the alignment boundary for long longs, in bits. */
abstract int longLongAlign();
/**
* Holds if this architecture allow bitfields with declared types of different sizes
* to be packed together.
*/
abstract predicate allowHeterogeneousBitfields();
/**
* Gets the bit size of class `cd.getBaseClass()` when used as a base class of
* class `cd.getDerivedClass()`.
*/
abstract int baseClassSize(ClassDerivation cd);
/**
* Gets the bit size of type `t`. Only holds if `t` is an integral or enum type.
*/
cached
int integralBitSize(Type t) {
t instanceof BoolType and result = 8
or
t instanceof CharType and result = 8
or
t instanceof WideCharType and result = this.wideCharSize()
or
t instanceof Char8Type and result = 8
or
t instanceof Char16Type and result = 16
or
t instanceof Char32Type and result = 32
or
t instanceof ShortType and result = 16
or
t instanceof IntType and result = 32
or
t instanceof LongType and result = this.longSize()
or
t instanceof LongLongType and result = this.longLongSize()
or
result = this.enumBitSize(t)
or
result = this.integralBitSize(t.(SpecifiedType).getBaseType())
or
result = this.integralBitSize(t.(TypedefType).getBaseType())
}
/**
* Gets the bit size of enum type `e`.
*/
int enumBitSize(Enum e) {
result = this.integralBitSize(e.getExplicitUnderlyingType())
or
not exists(e.getExplicitUnderlyingType()) and result = 32
}
/**
* Gets the alignment of enum type `e`.
*/
int enumAlignment(Enum e) {
result = this.alignment(e.getExplicitUnderlyingType())
or
not exists(e.getExplicitUnderlyingType()) and result = 32
}
/**
* Gets the bit size of type `t`; that is, the number of bits a value
* with type `t` takes up on this architecture, without any trailing
* padding on structs and unions.
*/
cached
int bitSize(Type t) {
result = this.integralBitSize(t)
or
t instanceof FloatType and result = 32
or
t instanceof DoubleType and result = 64
or
t instanceof LongDoubleType and result = this.longDoubleSize()
or
t instanceof PointerType and result = this.pointerSize()
or
t instanceof ReferenceType and result = this.pointerSize()
or
t instanceof FunctionPointerType and result = this.pointerSize()
or
result = this.bitSize(t.(SpecifiedType).getBaseType())
or
result = this.bitSize(t.(TypedefType).getBaseType())
or
exists(ArrayType array | array = t |
result = array.getArraySize() * this.paddedSize(array.getBaseType())
)
or
result = t.(PaddedType).typeBitSize(this)
}
/**
* Gets the desired alignment boundary of type `t` as a struct field
* on this architecture, in bits.
*/
cached
int alignment(Type t) {
t instanceof BoolType and result = 8
or
t instanceof CharType and result = 8
or
t instanceof WideCharType and result = this.wideCharSize()
or
t instanceof Char8Type and result = 8
or
t instanceof Char16Type and result = 16
or
t instanceof Char32Type and result = 32
or
t instanceof ShortType and result = 16
or
t instanceof IntType and result = 32
or
t instanceof FloatType and result = 32
or
t instanceof DoubleType and result = this.doubleAlign()
or
t instanceof LongType and result = this.longSize()
or
t instanceof LongDoubleType and result = this.longDoubleAlign()
or
t instanceof LongLongType and result = this.longLongAlign()
or
t instanceof PointerType and result = this.pointerSize()
or
t instanceof FunctionPointerType and result = this.pointerSize()
or
t instanceof ReferenceType and result = this.pointerSize()
or
result = this.enumAlignment(t)
or
result = this.alignment(t.(SpecifiedType).getBaseType())
or
result = this.alignment(t.(TypedefType).getBaseType())
or
result = this.alignment(t.(ArrayType).getBaseType())
or
result = t.(PaddedType).typeAlignment(this)
}
/**
* Gets the padded size of type `t` on this architecture; that is,
* the number of bits that 'sizeof' should return, taking into account
* any trailing padding on top of the bit size.
*/
int paddedSize(Type t) {
exists(Type realType | realType = stripSpecifiers(t) |
if realType instanceof PaddedType
then result = realType.(PaddedType).paddedSize(this)
else result = this.bitSize(realType)
)
}
/**
* Gets the wasted space of type `t`; that is, the number of bits
* spent on padding. This is zero for primitive types, and depends on
* struct fields and their alignment otherwise. Trailing padding is
* counted.
*/
int wastedSpace(Type t) {
if t instanceof PaddedType then result = t.(PaddedType).wastedSpace(this) else result = 0
}
}
/**
* Gets an initial field of type `t`. If `t` is not a union, an initial field is
* either the first field declared in type `t`, or an initial field of the type
* of the first field declared in `t`. If `t` is a union, an initial field is
* either any field declared in type `t`, or an initial field of the type of any
* field declared in `t`.
*/
private Field getAnInitialField(PaddedType t) {
if t instanceof Union
then
// Any field of the union is an initial field
result = t.getAField()
or
// Initial field of the type of a field of the union
result = getAnInitialField(t.getAField().getUnspecifiedType())
else
exists(Field firstField | t.fieldIndex(firstField) = 1 |
// The first field of `t`
result = firstField
or
// Initial field of the first field of `t`
result = getAnInitialField(firstField.getUnspecifiedType())
)
}
/**
* Base class for architectures that follow the Itanium ABI. This includes
* pretty much everything except Windows, so we'll refer to this as
* "UnixArchitecture" to avoid any confusion due to the use of the name
* "Itanium".
*/
abstract class UnixArchitecture extends Architecture {
bindingset[this]
UnixArchitecture() { any() }
override int baseClassSize(ClassDerivation cd) {
if
not exists(cd.getBaseClass().getABaseClass*().getAField()) and
not exists(PaddedType fieldType |
fieldType = getAnInitialField(cd.getDerivedClass()).getUnspecifiedType() and
(
// Check if the type of the field is a base type of the class, or
// vice versa. This is an approximation of the actual rule, which is
// that the field type and the class must not share a common
// ancestor. This approximation should be sufficient for the vast
// majority of cases.
fieldType.getABaseClass*() = cd.getBaseClass() or
fieldType = cd.getBaseClass().getABaseClass*()
)
)
then
// No fields in this class or any base classes.
result = 0
else result = cd.getBaseClass().(PaddedType).paddedSize(this)
}
override int longLongSize() { result = 64 }
override int wideCharSize() { result = 32 }
override predicate allowHeterogeneousBitfields() { any() }
}
/**
* The ILP32 architecture has ints, longs and pointers
* of 32 bits.
*/
class ILP32 extends UnixArchitecture {
ILP32() { this = "ILP32" }
override int pointerSize() { result = 32 }
override int longSize() { result = 32 }
override int longDoubleSize() { result = 96 }
override int doubleAlign() { result = 32 }
override int longLongAlign() { result = 32 }
override int longDoubleAlign() { result = 32 }
}
/**
* The LP64 architecture has longs and pointers of 64 bits.
*/
class LP64 extends UnixArchitecture {
LP64() { this = "LP64" }
override int pointerSize() { result = 64 }
override int longSize() { result = 64 }
override int longDoubleSize() { result = 128 }
override int doubleAlign() { result = 64 }
override int longLongAlign() { result = 64 }
override int longDoubleAlign() { result = 128 }
}
/**
* Base class for Windows architectures.
*/
abstract class WindowsArchitecture extends Architecture {
bindingset[this]
WindowsArchitecture() { any() }
override int baseClassSize(ClassDerivation cd) {
if not exists(cd.getBaseClass().getABaseClass*().getAField())
then
// No fields in this class or any base classes.
result = 0
else result = cd.getBaseClass().(PaddedType).paddedSize(this)
}
override int longSize() { result = 32 }
override int longDoubleSize() { result = 64 }
override int longLongSize() { result = 64 }
override int wideCharSize() { result = 16 }
override int doubleAlign() { result = 64 }
override int longLongAlign() { result = 64 }
override int longDoubleAlign() { result = 64 }
override predicate allowHeterogeneousBitfields() { none() }
}
/**
* The ILP32_MS architecture is essentially the same as the
* ILP32 architecture, except that long doubles are 64 bits.
*/
class ILP32_MS extends WindowsArchitecture {
ILP32_MS() { this = "ILP32_MS" }
override int pointerSize() { result = 32 }
}
/**
* The LLP64_MS architecture has pointers of 64 bits, but both
* long and int are still 32 bits.
*/
class LLP64_MS extends WindowsArchitecture {
LLP64_MS() { this = "LLP64_MS" }
override int pointerSize() { result = 64 }
}
/**
* A class that is subject to padding by the compiler, and hence can
* introduce waste. Does not include types with virtual member functions,
* virtual base classes, or multiple base classes. These are excluded due
* to the complexity of the implementation.
*/
class PaddedType extends Class {
PaddedType() {
// We can't talk about bit size of template types.
not this instanceof TemplateClass and
// If the class has any virtual functions, the layout will be more
// complicated due to the presence of a virtual function table pointer.
not exists(MemberFunction f | f = this.getAMemberFunction() and f.isVirtual()) and
not exists(ClassDerivation cd | cd = this.getADerivation() |
// If the class has any virtual functions, the layout will be more
// complicated due to the presence of a virtual base table pointer.
cd.hasSpecifier("virtual")
or
// If one of the base classes was not a PaddedType, then we should not
// attempt to lay out the derived class, either.
not cd.getBaseClass() instanceof PaddedType
) and
// Support only single inheritance for now. If multiple inheritance is
// supported, be sure to fix up the calls to getABaseClass*() to correctly
// handle the presence of multiple base class subojects with the same type.
not exists(this.getDerivation(1))
}
/**
* Holds if, for each architecture, a single padded size is
* calculated for this type.
* This is normally the case, but sometimes the same type can be
* defined in different compilations with different sizes, normally
* due to use of the preprocessor in its definition.
*/
predicate isPrecise() { forex(Architecture arch | 1 = strictcount(arch.paddedSize(this))) }
/**
* Gets the padded size of this type on architecture `arch`, in bits.
* This is its `bitSize`, rounded up to the next multiple of its
* `alignment`.
*/
int paddedSize(Architecture arch) {
// Struct padding is weird: It needs to be such that struct arrays
// can be allocated contiguously. That means that the trailing padding
// has to bring the alignment up to the smallest common multiple of
// the alignment values of all fields. In practice, since valid
// alignment values are 1, 2, 4, 8 and 16, this means "the largest
// alignment value".
// If the class is empty, the size is rounded up to one byte.
result = alignUp(arch.bitSize(this), arch.alignment(this)).maximum(8)
}
/**
* Gets the number of bits wasted by padding at the end of this
* struct.
*/
int trailingPadding(Architecture arch) { result = this.paddedSize(arch) - arch.bitSize(this) }
/**
* Gets the number of bits wasted in this struct definition; that is.
* the waste between fields plus any waste from trailing padding.
* Only the space wasted directly in this type is counted, not space
* wasted in nested structs. Note that for unions, the wasted space
* is simply the amount of trailing padding, as other fields are not
* laid out one after another, and hence there is no padding between
* them.
*/
int wastedSpace(Architecture arch) { result = arch.paddedSize(this) - this.dataSize(arch) }
/**
* Gets the total size of all fields declared in this class, not including any
* padding between fields.
*/
private int fieldDataSize(Architecture arch) {
if this instanceof Union
then result = max(Field f | f = this.getAMember() | this.fieldSize(f, arch))
else result = sum(Field f | f = this.getAMember() | this.fieldSize(f, arch))
}
/**
* Gets the data size of this type on architecture `arch`; that is,
* the number of bits taken up by data, rather than any kind of
* padding. Padding of fields that have struct type is
* considered "data" for the purposes of this definition, since
* removing it requires reorganizing other parts of the code.
*/
int dataSize(Architecture arch) {
result = sum(PaddedType c | c = this.getABaseClass*() | c.fieldDataSize(arch))
}
/**
* Gets the optimal size of this type on architecture `arch`, that is,
* the sum of the sizes of all fields, ignoring padding
* between them, but adding any trailing padding required to align
* the type properly. This is a lower bound on the actual size that
* can be achieved just by reordering fields, and without
* reorganizing member structs' field layouts.
*/
int optimalSize(Architecture arch) {
result = alignUp(this.dataSize(arch), arch.alignment(this)).maximum(8)
}
/**
* Gets the bit size of this type on architecture `arch`, that is, the
* size its fields and required padding take up, without including
* any trailing padding that is necessary.
*/
int typeBitSize(Architecture arch) {
if this instanceof Union
then
// A correct implementation for unions would be:
// ```
// result = max(fieldSize(_, arch))
// ```
// but that uses a recursive aggregate, which isn't supported in
// QL. We therefore use this slightly more complex implementation
// instead.
result = this.biggestFieldSizeUpTo(this.lastFieldIndex(), arch)
else
// If we're not a union type, the size is the padded
// sum of field sizes, padded.
result = this.fieldEnd(this.lastFieldIndex(), arch)
}
/**
* Gets the alignment, in bits, of the entire struct/union type for
* architecture `arch`.
*/
language[monotonicAggregates]
int typeAlignment(Architecture arch) {
// The alignment of the type is the largest alignment of any of its fields,
// including fields from base class subobjects.
result =
max(PaddedType c |
c = this.getABaseClass*()
|
c.biggestAlignmentUpTo(c.lastFieldIndex(), arch)
)
}
/**
* Gets the largest size, in bits, of the size of a field with
* (1-based) index less than or equal to `index` on architecture
* `arch`.
*/
int biggestFieldSizeUpTo(int index, Architecture arch) {
if index = 0
then result = 0
else
exists(Field f, int fSize | index = this.fieldIndex(f) and fSize = this.fieldSize(f, arch) |
result = fSize.maximum(this.biggestFieldSizeUpTo(index - 1, arch))
)
}
/**
* Gets the largest alignment boundary, in bits, required by a field
* with (1-based) index less than or equal to `index` on architecture
* `arch`.
*/
int biggestAlignmentUpTo(int index, Architecture arch) {
if index = 0
then result = 1 // Minimum possible alignment
else
exists(Field f, int fAlign |
index = this.fieldIndex(f) and fAlign = arch.alignment(f.getType())
|
result = fAlign.maximum(this.biggestAlignmentUpTo(index - 1, arch))
)
}
/**
* Gets the 1-based index for each field.
*/
int fieldIndex(Field f) {
this.memberIndex(f) =
rank[result](Field field, int index | this.memberIndex(field) = index | index)
}
private int memberIndex(Field f) { result = min(int i | this.getCanonicalMember(i) = f) }
/**
* Gets the 1-based index for the last field.
*/
int lastFieldIndex() {
if exists(this.lastField())
then result = this.fieldIndex(this.lastField())
else
// Field indices are 1-based, so return 0 to represent the lack of fields.
result = 0
}
/**
* Gets the size, in bits, of field `f` on architecture
* `arch`.
*/
int fieldSize(Field f, Architecture arch) {
exists(this.fieldIndex(f)) and
if f instanceof BitField
then result = f.(BitField).getNumBits()
else result = arch.paddedSize(f.getType())
}
/** Gets the last field of this type. */
Field lastField() { this.fieldIndex(result) = max(Field other | | this.fieldIndex(other)) }
/**
* Gets the offset, in bits, of the end of the class' last base class
* subobject, or zero if the class has no base classes.
*/
int baseClassEnd(Architecture arch) {
if exists(this.getABaseClass())
then result = arch.baseClassSize(this.getADerivation())
else result = 0
}
/** Gets the bitfield at field index `index`, if that field is a bitfield. */
private BitField bitFieldAt(int index) { this.fieldIndex(result) = index }
/**
* Gets the 0-based offset, in bits, of the first free bit after
* field `f` (which is the `index`th field of
* this type), taking padding into account, on architecture `arch`.
*/
int fieldEnd(int index, Architecture arch) {
if index = 0
then
// Base case: No fields seen yet, so return the offset of the end of the
// base class subojects.
result = this.baseClassEnd(arch)
else
exists(Field f | index = this.fieldIndex(f) |
exists(int fSize | fSize = this.fieldSize(f, arch) |
// Recursive case: Take previous field's end point, pad and add
// this field's size
exists(int firstFree | firstFree = this.fieldEnd(index - 1, arch) |
if f instanceof BitField
then
// Bitfield packing:
// (1) A struct containing a bitfield with declared type T (e.g. T bf : 7) will be aligned as if it
// contained an actual field of type T. Thus, a struct containing a bitfield 'unsigned int bf : 8'
// will have an alignment of at least alignof(unsigned int), even though the bitfield was only 8 bits.
// (2) If a bitfield with declared type T would straddle a sizeof(T) boundary, padding is inserted
// before the bitfield to align it on an alignof(T) boundary. Note the subtle distinction between alignof
// and sizeof. This matters for 32-bit Linux, where sizeof(long long) == 8, but alignof(long long) == 4.
// (3) [MSVC only!] If a bitfield with declared type T immediately follows another bitfield with declared type P,
// and sizeof(P) != sizeof(T), padding will be inserted to align the new bitfield to a boundary of
// max(alignof(P), alignof(T)).
exists(int nextSizeofBoundary, int nextAlignofBoundary |
nextSizeofBoundary = alignUp(firstFree, arch.bitSize(f.getType())) and
nextAlignofBoundary = alignUp(firstFree, arch.alignment(f.getType()))
|
if arch.allowHeterogeneousBitfields()
then (
if nextSizeofBoundary < (firstFree + fSize)
then
// Straddles a sizeof(T) boundary, so pad for alignment.
result = nextAlignofBoundary + fSize
else
// No additional restrictions, so just pack it in with no padding.
result = firstFree + fSize
) else (
if exists(this.bitFieldAt(index - 1))
then
exists(BitField previousBitField |
previousBitField = this.bitFieldAt(index - 1)
|
// Previous field was a bitfield.
if
nextSizeofBoundary >= (firstFree + fSize) and
arch.integralBitSize(previousBitField.getType()) =
arch.integralBitSize(f.getType())
then
// The new bitfield can be stored in the same allocation unit as the previous one,
// so we can avoid padding.
result = firstFree + fSize
else
// Either we switched types, or we would overlap a sizeof(T) boundary, so we have to insert padding.
// Note that we have to align to max(alignof(T), alignof(P)), where P is the type of the previous
// bitfield. Without the alignof(P) term, we'll get the wrong layout for:
// struct S {
// unsigned int x : 7;
// unsigned short y : 1;
// };
// If we only aligned to sizeof(T), we'd align 'y' to a 2-byte boundary. This is incorrect. The allocation
// unit that started with 'x' has to consume an entire unsigned int (4 bytes).
result =
max(int boundary |
boundary = nextAlignofBoundary or
boundary =
alignUp(firstFree, arch.alignment(previousBitField.getType()))
|
boundary
) + fSize
)
else
// Previous field was not a bitfield. Align up to an
// alignof(T) boundary.
result = nextSizeofBoundary + fSize
)
)
else
// Normal case: Pad as necessary, then add the field.
result = alignUp(firstFree, arch.alignment(f.getType())) + fSize
)
)
)
}
}