blob: 41b347150886f854bba45ba03b4cf9bfec94e72e [file]
//===------------- InterpBuiltinObjectSize.cpp ------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// Implementation of the frontend part of the __builtin_object_size and
// __builtin_dynamic_object_size builtins.
#include "InterpHelpers.h"
#include "Pointer.h"
#include "Record.h"
#include "clang/AST/RecordLayout.h"
using namespace clang;
using namespace clang::interp;
enum : uint8_t {
Regular = 1 << 0,
IgnoreBaseCasts = 1 << 1,
SurroundingArray = 1 << 2,
};
// Helper to check if a Type can be passed to
// ASTContext::getRecordLayout().
static bool validType(QualType T) {
if (const RecordDecl *RD = T->getAsRecordDecl())
return ASTContext::hasLayout(RD);
return true;
}
static QualType computeFieldType(const ASTContext &ASTCtx,
const OpaquePointer &OP,
unsigned TypeModifier = 0) {
QualType CurType = OP.getObjectType();
unsigned Drop = 0;
if (TypeModifier & IgnoreBaseCasts && OP.PathLength != 0 &&
OP.path().back().Kind == PointerPathEntry::Base)
Drop = 1;
if (TypeModifier & SurroundingArray && OP.PathLength != 0 &&
OP.path().back().Kind == PointerPathEntry::Array)
Drop = 1;
for (const PointerPathEntry &Entry : OP.path().drop_back(Drop)) {
switch (Entry.Kind) {
case PointerPathEntry::Base:
CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer());
break;
case PointerPathEntry::Field:
CurType = Entry.FD->getType();
break;
case PointerPathEntry::Array:
case PointerPathEntry::NegativeArray:
if (!CurType->isArrayType())
continue;
CurType = CurType->getAsArrayTypeUnsafe()->getElementType();
}
}
return CurType;
}
static std::optional<unsigned> computeFullDescSize(const ASTContext &ASTCtx,
const Descriptor *Desc) {
if (Desc->isPrimitive() || Desc->isArray()) {
QualType T = Desc->getType();
if (!validType(T))
return std::nullopt;
return ASTCtx.getTypeSizeInChars(T).getQuantity();
}
if (Desc->isRecord()) {
// Can't use Descriptor::getType() as that may return a pointer type. Look
// at the decl directly.
const RecordDecl *RD = Desc->ElemRecord->getDecl();
if (!ASTContext::hasLayout(RD))
return std::nullopt;
return ASTCtx.getTypeSizeInChars(ASTCtx.getCanonicalTagType(RD))
.getQuantity();
}
return std::nullopt;
}
/// Compute the byte offset of \p Ptr in the full declaration.
static unsigned computePointerOffset(const ASTContext &ASTCtx,
const Pointer &Ptr) {
return Ptr.computeLayoutOffset(ASTCtx).value_or(0);
}
/// Does Ptr point to the last subobject?
static bool pointsToLastObject(const Pointer &Ptr) {
Pointer P = Ptr;
while (!P.isRoot()) {
if (P.isArrayElement()) {
P = P.expand().getArray();
continue;
}
if (P.isBaseClass()) {
if (P.getRecord()->getNumFields() > 0)
return false;
P = P.getBase();
continue;
}
Pointer Base = P.getBase();
if (const Record *R = Base.getRecord()) {
assert(P.getField());
if (P.getField()->getFieldIndex() != R->getNumFields() - 1)
return false;
}
P = Base;
}
return true;
}
/// Does Ptr point to the last object AND to a flexible array member?
static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const Pointer &Ptr,
bool InvalidBase) {
auto isFlexibleArrayMember = [&](const Descriptor *FieldDesc) {
using FAMKind = LangOptions::StrictFlexArraysLevelKind;
FAMKind StrictFlexArraysLevel =
Ctx.getLangOpts().getStrictFlexArraysLevel();
if (StrictFlexArraysLevel == FAMKind::Default)
return true;
unsigned NumElems = FieldDesc->getNumElems();
if (NumElems == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly)
return true;
if (NumElems == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete)
return true;
return false;
};
const Descriptor *FieldDesc = Ptr.getFieldDesc();
if (!FieldDesc->isArray())
return false;
return InvalidBase && pointsToLastObject(Ptr) &&
isFlexibleArrayMember(FieldDesc);
}
static bool isUserWritingOffTheEnd(const ASTContext &ASTCtx,
const OpaquePointer &OP) {
if (OP.PathLength == 0)
return false;
QualType CurType = OP.getObjectType();
for (unsigned I = 0; I != OP.PathLength; ++I) {
const PointerPathEntry &Entry = OP.Path[I];
switch (Entry.Kind) {
case PointerPathEntry::Base:
return false;
case PointerPathEntry::Field: {
const FieldDecl *FD = OP.Path[I].FD;
if (!FD->getParent()->isUnion() &&
FD->getFieldIndex() != FD->getParent()->getNumFields() - 1)
return false;
CurType = FD->getType();
} break;
case PointerPathEntry::Array: {
if (I == OP.PathLength - 1)
break;
if (!CurType->isArrayType())
break;
unsigned Index = OP.Path[I].Index;
const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
assert(AT);
if (const auto *CAT = dyn_cast<ConstantArrayType>(AT)) {
if (Index != CAT->getLimitedSize() - 1)
return false;
CurType = CAT->getElementType();
} else {
return false;
}
} break;
case PointerPathEntry::NegativeArray:
return false;
}
}
// We're pointing to the last field in the full object.
// CurType is now the most derived type.
if (!CurType->isArrayType())
return false;
if (isa<IncompleteArrayType>(CurType))
return true;
const auto *CAT = dyn_cast<ConstantArrayType>(CurType);
if (!CAT)
return false;
using FAMKind = LangOptions::StrictFlexArraysLevelKind;
FAMKind StrictFlexArraysLevel =
ASTCtx.getLangOpts().getStrictFlexArraysLevel();
if (StrictFlexArraysLevel == FAMKind::Default)
return true;
unsigned Size = CAT->getZExtSize();
if (Size == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly)
return true;
if (Size == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete)
return true;
return false;
}
/// Determine the offset of the given pointer. Depending on \c
/// UseClosestSurroundingVariable, the offset is either relative to the full
/// object or to the closest surrounding field or array.
static std::optional<uint64_t>
computeOpaquePtrOffset(const ASTContext &ASTCtx, const Pointer &Ptr,
bool UseClosestSurroundingVariable,
bool &OffsetIsNegative) {
const OpaquePointer &OP = Ptr.asOpaquePointer();
uint64_t Offset = 0;
std::optional<uint64_t> SurroundingArrayOffset;
QualType CurType = OP.getObjectType();
for (const PointerPathEntry &Entry : OP.path()) {
switch (Entry.Kind) {
case PointerPathEntry::Base: {
const RecordDecl *RD = CurType->getAsRecordDecl();
if (!ASTContext::hasLayout(RD))
return std::nullopt;
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
Offset += Layout.getBaseClassOffset(Entry.RD.getPointer()).getQuantity();
CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer());
} break;
case PointerPathEntry::Field: {
const FieldDecl *FD = Entry.FD;
const RecordDecl *RD = FD->getParent();
if (!ASTContext::hasLayout(RD))
return std::nullopt;
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
Offset +=
ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex()))
.getQuantity();
CurType = FD->getType();
} break;
case PointerPathEntry::Array:
case PointerPathEntry::NegativeArray: {
bool Add = (Entry.Kind == PointerPathEntry::Array);
uint64_t Index = Entry.Index;
if (!Add) {
// NegativeArray is always > 0.
OffsetIsNegative = true;
}
SurroundingArrayOffset = Offset;
if (!CurType->isArrayType()) {
if (Add)
Offset += Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
else
Offset -= Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
continue;
}
const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
assert(AT);
QualType ElemTy = AT->getElementType();
if (!validType(ElemTy) || isa<VariableArrayType>(AT))
return std::nullopt;
if (Add)
Offset += Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity();
else
Offset -= Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity();
CurType = AT->getElementType();
} break;
}
}
if (UseClosestSurroundingVariable && SurroundingArrayOffset)
return Offset - *SurroundingArrayOffset;
QualType Ty = CurType.getNonReferenceType();
if (UseClosestSurroundingVariable &&
(Ty->isIncompleteType() || Ty->isFunctionType()))
return std::nullopt;
if (isa<VariableArrayType>(Ty))
return std::nullopt;
if (OP.PathLength == 1 && OP.path().back().Kind == PointerPathEntry::Field &&
isa<IncompleteArrayType>(CurType)) {
return Offset;
}
if (UseClosestSurroundingVariable)
return 0;
return Offset;
}
/// Check if the given pointer points to the complete object, i.e. either to the
/// very beginning or after the end (into the flexible array member) of the
/// object.
static bool pointsToCompleteObject(const ASTContext &ASTCtx,
const Pointer &Ptr) {
const OpaquePointer &OP = Ptr.asOpaquePointer();
if (OP.PathLength == 0)
return true;
QualType FieldType = computeFieldType(ASTCtx, OP);
if (OP.isArrayElement())
FieldType = OP.getSurroundingArray();
return isa<IncompleteArrayType>(FieldType);
}
static std::optional<unsigned>
computeOpaqueSize(const ASTContext &ASTCtx, const Pointer &Ptr,
bool UseClosestSurroundingVariable, bool WritingOffTheEnd,
bool DetermineForCompleteObject) {
const OpaquePointer &OP = Ptr.asOpaquePointer();
CharUnits TypeSize;
// NOTE: Clang does not consider base casts. GCC does.
if (UseClosestSurroundingVariable) {
QualType FieldTy =
computeFieldType(ASTCtx, OP, SurroundingArray | IgnoreBaseCasts);
if (!validType(FieldTy))
return std::nullopt;
TypeSize = ASTCtx.getTypeSizeInChars(FieldTy);
} else {
QualType ObjectTy = OP.getObjectType();
if (!validType(ObjectTy))
return std::nullopt;
TypeSize = ASTCtx.getTypeSizeInChars(ObjectTy);
}
// The Flexible array member should only be checked if we're pointing to the
// object as a whole, or if we're looking for the whole object size.
if (!WritingOffTheEnd && !DetermineForCompleteObject)
return TypeSize.getQuantity();
// Check if we need to add the flexible array member size.
const VarDecl *Base = OP.getBaseVarDecl();
if (!Base)
return TypeSize.getQuantity();
// If the base type is an incomplete array type (not a flexible array member
// of a struct), and we're looking for the complete object... we can't.
if (DetermineForCompleteObject && isa<IncompleteArrayType>(Base->getType()))
return std::nullopt;
if (!Base->getType()->isRecordType())
return TypeSize.getQuantity();
if (!Base->hasInit())
return TypeSize.getQuantity();
CharUnits FlexibleArraySize = Base->getFlexibleArrayInitChars(ASTCtx);
return (TypeSize + FlexibleArraySize).getQuantity();
}
namespace clang {
namespace interp {
/// Evaluate __builtin_object_size or __builtin_dynamic_object_size for the
/// given pointer and Kind.
///
/// When computing the final result, the most important variable is
/// UseClosestSurroundingVariable. If it is true, we will use the field the
/// pointer points to, or the parent array of the element.
/// UseClosestSurroundingVariable is true for Kind 1 and 3.
UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx,
unsigned Kind, Pointer &Ptr,
const Expr *E, bool IsDynamic) {
if (Ptr.isZero())
return std::nullopt;
bool InvalidBase = false;
if (Ptr.isOpaquePointer()) {
bool UseClosestSurroundingVariable = (Kind == 1) || (Kind == 3);
const OpaquePointer &OP = Ptr.asOpaquePointer();
InvalidBase = OP.Base.getType()->isPointerType();
bool DetermineForCompleteObject = pointsToCompleteObject(ASTCtx, Ptr);
bool WritingOffTheEnd = isUserWritingOffTheEnd(ASTCtx, OP);
// Either the size of the full variable (Kind = 0 or 2) or the size of the
// closest surrounding variable (Kind = 1 or 3).
std::optional<unsigned> FullSize =
computeOpaqueSize(ASTCtx, Ptr, UseClosestSurroundingVariable,
WritingOffTheEnd, DetermineForCompleteObject);
if (!FullSize)
return std::nullopt;
// Similar to the FullSize above, the offset is relative either to the full
// variable or to the closest surrounding variable.
bool OffsetIsNegative = false;
std::optional<uint64_t> Offset = computeOpaquePtrOffset(
ASTCtx, Ptr, UseClosestSurroundingVariable, OffsetIsNegative);
if (!Offset)
return std::nullopt;
if (OffsetIsNegative)
return 0u;
// For __builtin_dynamic_object_size on a counted_by-annotated flexible
// array member, defer to IR generation (emitCountedBySize in CGBuiltin):
// its runtime computation uses the live 'count' field and is more accurate
// than the layout/initializer-derived size we'd produce here. Use the same
// findStructFieldAccess form-recognition CGBuiltin does, so we refuse to
// fold on exactly the shapes that path handles (and, importantly, *not*
// on '&af.fam' which designates the array-as-a-whole and stays on the
// layout-derived path to match GCC).
if (IsDynamic) {
const auto *ME =
dyn_cast_if_present<MemberExpr>(findStructFieldAccess(E));
const auto *FD = ME ? dyn_cast<FieldDecl>(ME->getMemberDecl()) : nullptr;
if (FD && FD->getType()->isCountAttributedType())
return std::nullopt;
}
if (!UseClosestSurroundingVariable || DetermineForCompleteObject) {
// Kind=3 wants a lower bound, so we can't fall back to this.
if (Kind == 3 && !DetermineForCompleteObject)
return std::nullopt;
if (InvalidBase)
return std::nullopt;
QualType ObjectTy = OP.getObjectType();
if (ObjectTy->isIncompleteType() || isa<VariableArrayType>(ObjectTy) ||
ObjectTy->isFunctionType())
return std::nullopt;
}
*Offset += (Ptr.getByteOffset() *
ASTCtx.getTypeSizeInChars(OP.getFieldType()).getQuantity());
if (*Offset > *FullSize)
return 0u;
if (Kind == 1 && InvalidBase && WritingOffTheEnd)
return std::nullopt;
assert(*Offset <= *FullSize);
return static_cast<unsigned>(*FullSize - *Offset);
}
// ----------------------------------------------------------------------------------------------------
if (Ptr.isDummy() && Ptr.getType()->isPointerType())
return std::nullopt;
if (!Ptr.isBlockPointer())
return std::nullopt;
if (Ptr.isDummy()) {
if (const VarDecl *VD = Ptr.getRootVarDecl();
VD && VD->getType()->isPointerType())
InvalidBase = true;
}
bool UseFieldDesc = (Kind & 1u);
bool ReportMinimum = (Kind & 2u);
// According to the GCC documentation, we want the size of the subobject
// denoted by the pointer. But that's not quite right -- what we actually
// want is the size of the immediately-enclosing array, if there is one.
if (Ptr.isArrayElement())
Ptr = Ptr.expand();
bool DetermineForCompleteObject = Ptr.getFieldDesc() == Ptr.getDeclDesc();
const Descriptor *DeclDesc = Ptr.getDeclDesc();
assert(DeclDesc);
if (!UseFieldDesc || DetermineForCompleteObject) {
// Can't read beyond the pointer decl desc.
if (!ReportMinimum && DeclDesc->getDataType(ASTCtx)->isPointerType())
return std::nullopt;
if (InvalidBase)
return std::nullopt;
} else {
if (isUserWritingOffTheEnd(ASTCtx, Ptr, InvalidBase)) {
// If we cannot determine the size of the initial allocation, then we
// can't given an accurate upper-bound. However, we are still able to give
// conservative lower-bounds for Type=3.
if (Kind == 1)
return std::nullopt;
}
}
// The "closest surrounding subobject" is NOT a base class,
// so strip the base class casts.
if (UseFieldDesc && Ptr.isBaseClass())
Ptr = Ptr.stripBaseCasts();
const Descriptor *Desc = UseFieldDesc ? Ptr.getFieldDesc() : DeclDesc;
assert(Desc);
std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, Desc);
if (!FullSize)
return std::nullopt;
unsigned ByteOffset;
if (UseFieldDesc) {
if (Ptr.isBaseClass()) {
assert(computePointerOffset(ASTCtx, Ptr.getBase()) <=
computePointerOffset(ASTCtx, Ptr));
ByteOffset = computePointerOffset(ASTCtx, Ptr.getBase()) -
computePointerOffset(ASTCtx, Ptr);
} else {
if (Ptr.inArray())
ByteOffset =
computePointerOffset(ASTCtx, Ptr) -
computePointerOffset(ASTCtx, Ptr.expand().atIndex(0).narrow());
else
ByteOffset = 0;
}
} else
ByteOffset = computePointerOffset(ASTCtx, Ptr);
assert(ByteOffset <= *FullSize);
return *FullSize - ByteOffset;
}
} // namespace interp
} // namespace clang