[clang] [clang][bytecode] Add opaque pointers to support type-only pointers (PR #213017)
Oliver Hunt via cfe-commits
cfe-commits at lists.llvm.org
Wed Aug 19 23:16:04 PDT 2026
================
@@ -0,0 +1,544 @@
+//===------------- 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 RecordDecl can be passed to
+// ASTContext::getRecordLayout().
+static bool validRecordDecl(const RecordDecl *D) {
+ D = D->getDefinition();
+ return D && !D->isInvalidDecl() && D->isCompleteDefinition();
+}
+
+// Same but for types.
+static bool validType(QualType T) {
+ if (const RecordDecl *RD = T->getAsRecordDecl())
+ return validRecordDecl(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 (!validRecordDecl(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 (!validRecordDecl(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 (!validRecordDecl(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 = dyn_cast<VarDecl>(OP.Base);
+ 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();
----------------
ojhunt wrote:
I _think_ it isn't. To get an overflow you'd need to have a pointer to an object large enough to cause an overflow and I don't think we're really in a position to construct a 2^64 byte object consteval object :D
https://github.com/llvm/llvm-project/pull/213017
More information about the cfe-commits
mailing list