[clang] [clang][bytecode] Add opaque pointers to support type-only pointers (PR #213017)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Wed Aug 5 02:07:27 PDT 2026
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/213017 at github.com>
================
@@ -0,0 +1,508 @@
+//===------------- 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:
+ 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) {
+ if (auto p = Ptr.computeLayoutOffset(ASTCtx))
+ return *p;
+ return 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;
+ }
+ }
+ }
+ }
+
+ // 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: {
+ int64_t Index = Entry.Index;
+ if (Index < 0)
+ OffsetIsNegative = true;
+ SurroundingArrayOffset = Offset;
+ if (!CurType->isArrayType()) {
+ Offset += Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
+ continue;
+ }
+ const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
+ assert(AT);
+ QualType ElemTy = AT->getElementType();
+ if (!validType(ElemTy))
+ return std::nullopt;
+ Offset += Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity();
+ CurType = AT->getElementType();
+ }
+ }
+ }
+
+ if (UseClosestSurroundingVariable && SurroundingArrayOffset)
+ return Offset - *SurroundingArrayOffset;
+
+ QualType Ty = CurType.getNonReferenceType();
+
+ if (UseClosestSurroundingVariable &&
+ (Ty->isIncompleteType() || Ty->isFunctionType()))
+ 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);
+ return isa<IncompleteArrayType>(FieldType);
+}
+
+static std::optional<unsigned>
+computeOpaqueSize(const ASTContext &ASTCtx, const Pointer &Ptr,
+ bool UseClosestSurroundingVariable) {
+ 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);
+ }
+
+ // Check if we need to add the flexible array member size.
+ const VarDecl *Base = dyn_cast<VarDecl>(OP.Base);
+ if (!Base || !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);
----------------
tbaederr wrote:
I can't, this is just the interface of the builtin.
https://github.com/llvm/llvm-project/pull/213017
More information about the cfe-commits
mailing list