Author: keinflue Date: 2026-10-07T10:53:28Z New Revision: abbf6958bb8449ea38dca4c6b843aad76e82670f
URL: https://github.com/llvm/llvm-project/commit/abbf6958bb8449ea38dca4c6b843aad76e82670f DIFF: https://github.com/llvm/llvm-project/commit/abbf6958bb8449ea38dca4c6b843aad76e82670f.diff LOG: [clang] Implement constexpr heap allocation alignments (#174549) Previously the old constant interpreter would crash on `__builtin_assume_aligned` applied to dynamic allocations and the new interpreter would incorrectly always assume an alignment of 8. This implements computation of the alignments of dynamic allocations for the builtin by using the minimum alignment guarantees made in the standard for each kind of possible dynamic allocation during constant evaluation. The kind of allocation depends on the source expression causing it to be created, but the alignment also depends on the size of the allocation which can be retrieved from the allocated type, but is decided dynamically. For the old constant expression evaluator, the allocated type is always available from the LValue, but the source expression is only available from the `DynAlloc` representing the allocation. The `DynAlloc` object does not continue to exist when the lifetime of the dynamic allocation ends. Because `__builtin_assume_aligned` should still be valid out-of-lifetime, it is therefore not possible to retrieve the source expression from the `DynAlloc` object. Instead this keeps track of the allocation kind during the creation of the allocation and stores it as part of the `LValueBase` by appropriating 3 bits of the allocation identifier. The new constant expression interpreter does not need additional state to store the alignment, because it already has both the source expression and allocated size available. Fixes #173767 --------- Co-authored-by: Nikolas Klauser <[email protected]> Added: Modified: clang/docs/ReleaseNotes.md clang/include/clang/AST/APValue.h clang/include/clang/AST/PropertiesBase.td clang/include/clang/Serialization/ASTRecordReader.h clang/include/clang/Serialization/ASTRecordWriter.h clang/lib/AST/ByteCode/Descriptor.cpp clang/lib/AST/ByteCode/Descriptor.h clang/lib/AST/ByteCode/Interp.cpp clang/lib/AST/ByteCode/Interp.h clang/lib/AST/ByteCode/InterpBlock.h clang/lib/AST/ByteCode/InterpBuiltin.cpp clang/lib/AST/ByteCode/Pointer.cpp clang/lib/AST/ExprConstShared.h clang/lib/AST/ExprConstant.cpp clang/lib/Sema/SemaExprCXX.cpp clang/test/AST/ByteCode/new-delete.cpp clang/test/SemaCXX/builtin-assume-aligned.cpp clang/test/SemaCXX/constant-expression-cxx2a.cpp Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 08442f74879af..efbcc198a28e3 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -642,8 +642,10 @@ features cannot lower the translation-unit ABI level; reference to a vector type; `vec_step` (in C++ for OpenCL) and `__builtin_ptrauth_type_discriminator` similarly no longer accept reference types that their evaluation silently mishandled. (#GH216997) +- Fix a crash when using `__builtin_assume_aligned` with dynamic allocations + during constant evaluation. (#GH173767) - Fixed a crash when constant-evaluating `__builtin_align_up`, `__builtin_align_down`, - or `__builtin_is_aligned` with pointers without an underlying object. Null pointers + or `__builtin_is_aligned` with pointers without an underlying object. Null pointers are handled as aligned values, while other base-less pointers are rejected during constant evaluation. @@ -844,9 +846,9 @@ features cannot lower the translation-unit ABI level; - Fixed ambiguous overload where two non-static member functions with diff erent signatures could be incorrectly considered equivalent. (#GH224499) -- Fixed an assertion failure when explicitly instantiating a nested member with - an ill-formed template argument. Clang now checks for a failed declaration - lookup before asserting that the name is not dependent, avoiding an assertion +- Fixed an assertion failure when explicitly instantiating a nested member with + an ill-formed template argument. Clang now checks for a failed declaration + lookup before asserting that the name is not dependent, avoiding an assertion after an earlier diagnostic has caused the declaration to be unavailable. (#GH220525) - Fixed a crash in constant evaluation when a new-expression selects a diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h index 0a338f805a665..35576a4b6403e 100644 --- a/clang/include/clang/AST/APValue.h +++ b/clang/include/clang/AST/APValue.h @@ -22,6 +22,7 @@ #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/bit.h" #include "llvm/Support/AlignOf.h" #include "llvm/Support/Compiler.h" @@ -63,33 +64,65 @@ class TypeInfoLValue { void print(llvm::raw_ostream &Out, const PrintingPolicy &Policy) const; }; +/// Kind of source for a dynamic allocation. +enum class DynAllocKind { + New, // new expression + ArrayNew, // new[] expression + StdAllocator, // std::allocator::allocate call + None, // not a dynamic allocation + BuiltinOperatorNew, // __operator_builtin_new call + ALLOC_KIND_MAX = BuiltinOperatorNew +}; + /// Symbolic representation of a dynamic allocation. class DynamicAllocLValue { - unsigned Index; +public: + static constexpr int NumLowBitsAvailable = 2; + static constexpr int NumAllocKindBits = 3; + static_assert((1 << NumAllocKindBits) - 1 >= + static_cast<int>(DynAllocKind::ALLOC_KIND_MAX)); + +private: + // lower NumAlignmentBits: alignment exponent + // remaining bits: allocation index incremented by one + // value of zero indicates distinct empty state + LLVM_PREFERRED_TYPE(DynAllocKind) + uintptr_t AllocKind : NumAllocKindBits; + uintptr_t Index : sizeof(uintptr_t) * CHAR_BIT - NumAllocKindBits; public: - DynamicAllocLValue() : Index(0) {} - explicit DynamicAllocLValue(unsigned Index) : Index(Index + 1) {} - unsigned getIndex() { return Index - 1; } + DynamicAllocLValue() : AllocKind(0), Index(0) {} + explicit DynamicAllocLValue(unsigned Idx, DynAllocKind AllocKind) + : AllocKind(llvm::to_underlying(AllocKind)), Index(Idx + 1) { + assert(Idx <= getMaxIndex() && "Index is out of range"); + } + unsigned getIndex() const { return Index - 1; } + DynAllocKind getAllocKind() const { + return static_cast<DynAllocKind>(AllocKind); + } explicit operator bool() const { return Index != 0; } const void *getOpaqueValue() const { - return reinterpret_cast<const void *>(static_cast<uintptr_t>(Index) - << NumLowBitsAvailable); + return reinterpret_cast<const void *>( + (Index << NumAllocKindBits | AllocKind) << NumLowBitsAvailable); } static DynamicAllocLValue getFromOpaqueValue(const void *Value) { DynamicAllocLValue V; - V.Index = reinterpret_cast<uintptr_t>(Value) >> NumLowBitsAvailable; + uintptr_t Combined = + reinterpret_cast<uintptr_t>(Value) >> NumLowBitsAvailable; + V.AllocKind = Combined & (1 << NumAllocKindBits) - 1; + V.Index = Combined >> NumAllocKindBits; return V; } - static unsigned getMaxIndex() { - return (std::numeric_limits<unsigned>::max() >> NumLowBitsAvailable) - 1; + static uintptr_t getMaxIndex() { + return (std::numeric_limits<uintptr_t>::max() >> + (NumLowBitsAvailable + NumAllocKindBits)) - + 1; } - - static constexpr int NumLowBitsAvailable = 3; }; +static_assert(sizeof(DynamicAllocLValue) == sizeof(uintptr_t)); } namespace llvm { diff --git a/clang/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td index 88ece140e826a..bb572d40d6c8c 100644 --- a/clang/include/clang/AST/PropertiesBase.td +++ b/clang/include/clang/AST/PropertiesBase.td @@ -77,6 +77,7 @@ def APValueKind : EnumPropertyType<"APValue::ValueKind">; def ArraySizeModifier : EnumPropertyType<"ArraySizeModifier">; def AttrKind : EnumPropertyType<"attr::Kind">; def Attr : PropertyType<"const Attr *">; +def DynAllocKind : PropertyType<"DynAllocKind">; def DeducedKind : EnumPropertyType; def AutoTypeKeyword : EnumPropertyType; def Bool : PropertyType<"bool">; @@ -544,6 +545,10 @@ let Class = PropertyTypeCase<APValue, "LValue"> in { let Conditional = [{ hasBase && isDynamicAlloc }]; let Read = [{ node.getLValueBase().get<DynamicAllocLValue>().getIndex() }]; } + def : Property<"dynamicAlign", DynAllocKind> { + let Conditional = [{ hasBase && isDynamicAlloc }]; + let Read = [{ node.getLValueBase().get<DynamicAllocLValue>().getAllocKind() }]; + } def : Property<"type", QualType> { let Conditional = [{ hasBase && (isTypeInfo || isDynamicAlloc) }]; let Read = [{ @@ -586,7 +591,7 @@ let Class = PropertyTypeCase<APValue, "LValue"> in { TypeInfoLValue(typeInfo->getTypePtr()), *type); } else if (isDynamicAlloc) { base = APValue::LValueBase::getDynamicAlloc( - DynamicAllocLValue(*dynamicAlloc), *type); + DynamicAllocLValue(*dynamicAlloc, *dynamicAlign), *type); } else if (isExpr) { base = APValue::LValueBase(cast<Expr>(*stmt), *callIndex, *version); diff --git a/clang/include/clang/Serialization/ASTRecordReader.h b/clang/include/clang/Serialization/ASTRecordReader.h index aed1b7d309001..c3482f146374f 100644 --- a/clang/include/clang/Serialization/ASTRecordReader.h +++ b/clang/include/clang/Serialization/ASTRecordReader.h @@ -320,6 +320,10 @@ class ASTRecordReader return readInt(); } + DynAllocKind readDynAllocKind() { + return static_cast<DynAllocKind>(readInt()); + } + UnsignedOrNone readUnsignedOrNone() { return UnsignedOrNone::fromInternalRepresentation(unsigned(readInt())); } diff --git a/clang/include/clang/Serialization/ASTRecordWriter.h b/clang/include/clang/Serialization/ASTRecordWriter.h index 9849ea6b395ab..8fc60338c4c72 100644 --- a/clang/include/clang/Serialization/ASTRecordWriter.h +++ b/clang/include/clang/Serialization/ASTRecordWriter.h @@ -189,6 +189,10 @@ class ASTRecordWriter Record->push_back(Value); } + void writeDynAllocKind(DynAllocKind Value) { + Record->push_back(llvm::to_underlying(Value)); + } + void writeUnsignedOrNone(UnsignedOrNone Value) { Record->push_back(Value.toInternalRepresentation()); } diff --git a/clang/lib/AST/ByteCode/Descriptor.cpp b/clang/lib/AST/ByteCode/Descriptor.cpp index 0137adf003a30..2c8e3c0da7f9d 100644 --- a/clang/lib/AST/ByteCode/Descriptor.cpp +++ b/clang/lib/AST/ByteCode/Descriptor.cpp @@ -9,6 +9,7 @@ #include "Descriptor.h" #include "Boolean.h" #include "Char.h" +#include "ExprConstShared.h" #include "FixedPoint.h" #include "Floating.h" #include "Integral.h" @@ -20,6 +21,8 @@ #include "Reflect.h" #include "Source.h" #include "clang/AST/ExprCXX.h" +#include "clang/Basic/TargetInfo.h" +#include "llvm/Support/ErrorHandling.h" using namespace clang; using namespace clang::interp; @@ -456,17 +459,8 @@ QualType Descriptor::getDataType(const ASTContext &Ctx) const { return ElemType; }; - if (const auto *E = asExpr()) { - if (isa<CXXNewExpr>(E)) - return MakeArrayType(E->getType()->getPointeeType()); - - // std::allocator.allocate() call. - if (const auto *ME = dyn_cast<CXXMemberCallExpr>(E); - ME && ME->getRecordDecl()->getName() == "allocator" && - ME->getMethodDecl()->getName() == "allocate") - return MakeArrayType(E->getType()->getPointeeType()); - return E->getType(); - } + if (isDynAlloc()) + return MakeArrayType(asExpr()->getType()->getPointeeType()); return getType(); } @@ -505,3 +499,23 @@ unsigned Descriptor::getElemDataSize() const { } return ElemSize; } + +DynAllocKind Descriptor::getDynAllocKindForExpr(const Expr *E) { + // new or new[] expression + if (const auto *NE = dyn_cast<CXXNewExpr>(E)) + return NE->isArray() ? DynAllocKind::ArrayNew : DynAllocKind::New; + // std::allocator::allocate call + if (const auto *ME = dyn_cast<CXXMemberCallExpr>(E); + ME && ME->getRecordDecl()->getName() == "allocator" && + ME->getMethodDecl()->getName() == "allocate") + return DynAllocKind::StdAllocator; + // __builtin_operator_new call + if (const auto *CE = dyn_cast<CallExpr>(E); + CE && CE->getBuiltinCallee() == Builtin::BI__builtin_operator_new) + return DynAllocKind::BuiltinOperatorNew; + return DynAllocKind::None; +} + +CharUnits Descriptor::computeAlignForDynamicAlloc(const ASTContext &Ctx) const { + return GetAlignOfDynamicAlloc(Ctx, getDataType(Ctx), getDynAllocKind()); +} diff --git a/clang/lib/AST/ByteCode/Descriptor.h b/clang/lib/AST/ByteCode/Descriptor.h index bec7ca653e055..567d08c4507d7 100644 --- a/clang/lib/AST/ByteCode/Descriptor.h +++ b/clang/lib/AST/ByteCode/Descriptor.h @@ -13,6 +13,7 @@ #ifndef LLVM_CLANG_AST_INTERP_DESCRIPTOR_H #define LLVM_CLANG_AST_INTERP_DESCRIPTOR_H +#include "../ExprConstShared.h" #include "DeclOrExpr.h" #include "InitMap.h" #include "PrimType.h" @@ -269,6 +270,18 @@ struct Descriptor final { /// Whether variables of this descriptor need their destructor called or not. bool hasTrivialDtor() const; + /// Returns the kind of dynamic allocation source of this block. + static DynAllocKind getDynAllocKindForExpr(const Expr *E); + /// Returns the kind of dynamic allocation source of this block. + DynAllocKind getDynAllocKind() const { + return asExpr() ? getDynAllocKindForExpr(asExpr()) : DynAllocKind::None; + } + /// Checks if the descriptor is of a dynamic allocation. + bool isDynAlloc() const { return getDynAllocKind() != DynAllocKind::None; } + + /// Compute the alignment for a dynamic allocation. + CharUnits computeAlignForDynamicAlloc(const ASTContext &Ctx) const; + void dump() const; void dump(llvm::raw_ostream &OS) const; void dumpFull(unsigned Offset = 0, unsigned Indent = 0) const; diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index eae2698e29db8..4bcabd73a4e26 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1384,20 +1384,8 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, return false; } -bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source, - const Pointer &Ptr) { - if (!Ptr.isBlockPointer() && !Ptr.isOpaquePointer()) - return false; - // Regular new type(...) call. - if (isa_and_nonnull<CXXNewExpr>(Source)) - return true; - // operator new. - if (const auto *CE = dyn_cast_if_present<CallExpr>(Source); - CE && CE->getBuiltinCallee() == Builtin::BI__builtin_operator_new) - return true; - // std::allocator.allocate() call - if (const auto *MCE = dyn_cast_if_present<CXXMemberCallExpr>(Source); - MCE && MCE->getMethodDecl()->getIdentifier()->isStr("allocate")) +bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { + if (Ptr.isBlockPointer() && Ptr.block()->isDynamic()) return true; // Whatever this is, we didn't heap allocate it. @@ -1563,7 +1551,7 @@ bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm, return true; if (!Ptr.isBlockPointer()) - return CheckDeleteSource(S, OpPC, nullptr, Ptr); + return CheckDeleteSource(S, OpPC, Ptr); // Remove base casts. QualType InitialType = Ptr.getType(); @@ -1602,7 +1590,7 @@ bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm, return false; } - if (!CheckDeleteSource(S, OpPC, Source, Ptr)) + if (!CheckDeleteSource(S, OpPC, Ptr)) return false; // For a class type with a virtual destructor, the selected operator delete diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 62f179ebaf7b8..b82600ca5db86 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -105,8 +105,7 @@ bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC); /// Check the source of the pointer passed to delete/delete[] has actually /// been heap allocated by us. -bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source, - const Pointer &Ptr); +bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Pointer &Ptr); /// Sets the given integral value to the pointer, which is of /// a std::{weak,partial,strong}_ordering type. diff --git a/clang/lib/AST/ByteCode/InterpBlock.h b/clang/lib/AST/ByteCode/InterpBlock.h index 4d6a2ecf81321..8aaff6eb9464e 100644 --- a/clang/lib/AST/ByteCode/InterpBlock.h +++ b/clang/lib/AST/ByteCode/InterpBlock.h @@ -80,7 +80,12 @@ class Block final { /// Checks if the block is temporary. bool isTemporary() const { return Desc->IsTemporary; } bool isWeak() const { return AccessFlags & WeakFlag; } - bool isDynamic() const { return (DynAllocId != std::nullopt); } + bool isDynamic() const { + bool Result = (DynAllocId != std::nullopt); + assert((Result == Desc->isDynAlloc()) && + "Inconsistent block/descriptor dynamic alloc state"); + return Result; + } bool isDead() const { return AccessFlags & DeadFlag; } /// Returns the size of the block, including metadata. unsigned getSize() const { return Desc->getAllocSize() + MDSize; } diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 5ea3cc6bd2920..5f3f59ae24245 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -14,6 +14,8 @@ #include "InterpHelpers.h" #include "PrimType.h" #include "Program.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/InferAlloc.h" #include "clang/AST/OSLog.h" #include "clang/AST/RecordLayout.h" @@ -1454,7 +1456,9 @@ static bool interp__builtin_assume_aligned(InterpState &S, CodePtr OpPC, // If there is a base object, then it must have the correct alignment. if (Ptr.isBlockPointer() || Ptr.isOpaquePointer()) { CharUnits BaseAlignment; - if (const auto *VD = Ptr.getRootVarDecl()) + if (Ptr.isBlockPointer() && Ptr.block()->isDynamic()) + BaseAlignment = Ptr.getDeclDesc()->computeAlignForDynamicAlloc(ASTCtx); + else if (const auto *VD = Ptr.getRootVarDecl()) BaseAlignment = ASTCtx.getDeclAlign(VD); else if (const auto *E = Ptr.getRootExpr()) BaseAlignment = GetAlignOfExpr(ASTCtx, E, UETT_AlignOf); diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp index c7e00cc964f82..6ea2cae2a02ca 100644 --- a/clang/lib/AST/ByteCode/Pointer.cpp +++ b/clang/lib/AST/ByteCode/Pointer.cpp @@ -346,9 +346,9 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const { Base = VD; else if (const auto *E = Desc->asExpr()) { if (block()->isDynamic()) { - QualType AllocatedType = getDeclPtr().getFieldDesc()->getDataType(ASTCtx); - DynamicAllocLValue DA(*block()->DynAllocId); - Base = APValue::LValueBase::getDynamicAlloc(DA, AllocatedType); + DynamicAllocLValue DA(*block()->DynAllocId, Desc->getDynAllocKind()); + Base = + APValue::LValueBase::getDynamicAlloc(DA, Desc->getDataType(ASTCtx)); } else { Base = E; } diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h index 7693ea25592f1..b8682bcd3e44d 100644 --- a/clang/lib/AST/ExprConstShared.h +++ b/clang/lib/AST/ExprConstShared.h @@ -32,6 +32,9 @@ class CharUnits; class Expr; class CallExpr; class CXXRecordDecl; + +CharUnits GetAlignOfDynamicAlloc(const ASTContext &Ctx, QualType AllocType, + DynAllocKind AllocKind); } // namespace clang using namespace clang; /// Values returned by __builtin_classify_type, chosen to match the values diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 34c3e39807c7f..d5b8d0ed889d1 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -62,6 +62,7 @@ #include "llvm/ADT/Sequence.h" #include "llvm/ADT/SmallBitVector.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/bit.h" #include "llvm/Support/CRC.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Debug.h" @@ -731,20 +732,16 @@ namespace { /// std::allocator<T>::allocate). const Expr *AllocExpr = nullptr; - enum Kind { - New, - ArrayNew, - StdAllocator - }; - /// Get the kind of the allocation. This must match between allocation /// and deallocation. - Kind getKind() const { + static DynAllocKind kindOfExpr(const Expr *AllocExpr) { if (auto *NE = dyn_cast<CXXNewExpr>(AllocExpr)) - return NE->isArray() ? ArrayNew : New; + return NE->isArray() ? DynAllocKind::ArrayNew : DynAllocKind::New; assert(isa<CallExpr>(AllocExpr)); - return StdAllocator; + return DynAllocKind::StdAllocator; } + + DynAllocKind getKind() const { return kindOfExpr(AllocExpr); } }; struct DynAllocOrder { @@ -823,7 +820,7 @@ namespace { std::map<DynamicAllocLValue, DynAlloc, DynAllocOrder> HeapAllocs; /// The number of heap allocations performed so far in this evaluation. - unsigned NumHeapAllocs = 0; + uintptr_t NumHeapAllocs = 0; struct EvaluatingConstructorRAII { EvalInfo &EI; @@ -1823,13 +1820,109 @@ APValue &CallStackFrame::createLocal(APValue::LValueBase Base, const void *Key, return Result; } +CharUnits clang::GetAlignOfDynamicAlloc(const ASTContext &Ctx, + QualType AllocType, + DynAllocKind AllocKind) { + assert((AllocKind != DynAllocKind::None) && + "should only be called on dynamically allocated blocks"); + assert((AllocKind != DynAllocKind::BuiltinOperatorNew) && + "__builtin_operator_new should have been allowed only from " + "std::allocator::allocate"); + + const TargetInfo &TI = Ctx.getTargetInfo(); + uint64_t DefaultNewAlign = TI.getNewAlign(); + + uint64_t TypeAlignment = Ctx.getTypeAlign(AllocType); + assert(TypeAlignment > 0 && "Unknown alignment for allocated type!"); + + uint64_t AllocSize = Ctx.getTypeSize(AllocType); + + if (AllocSize == 0) { + switch (AllocKind) { + // Allocating a zero-sized array is allowed, however it doesn't have + // any alignment guarantees. + case DynAllocKind::ArrayNew: + case DynAllocKind::StdAllocator: + return CharUnits::One(); + + // Flexible array members are allowed as only member as an extension. + // In this case the size of the type will be zero, but the allocation + // should still be suitable for the array element type. + case DynAllocKind::New: + return Ctx.toCharUnitsFromBits(TypeAlignment); + + default: + llvm_unreachable("Unhandled DynAllocKind"); + } + } + + assert(TypeAlignment <= AllocSize && "Invalid alignment/size for type!"); + assert(AllocSize % TypeAlignment == 0 && "Invalid alignment/size for type!"); + + // For new-extended alignment the ::operator new overload with + // std::align_val_t parameter is used. According to C++ + // [basic.stc.dynamic.allocation]p3.1 this overload returns memory + // according to the requested alignment. No stricter guarantees are + // made. + if (TypeAlignment > DefaultNewAlign) + return Ctx.toCharUnitsFromBits(TypeAlignment); + + switch (AllocKind) { + // According to C++ [allocator.members]p5 it is unspecified how the + // memory obtained from ::operator new is used by + // std::allocator::allocate, therefore be conservative here. + case DynAllocKind::StdAllocator: + return Ctx.toCharUnitsFromBits(TypeAlignment); + + // The non-array form of new does not permit allocation overhead and + // therefore provides alignment as guaranteed by ::operator new. + // According to C++ [basic.stc.dynamic.allocation]p3.3 the allocation + // is suitably aligned for all objects without new-extended alignment + // with the exact size of the allocation. An object of the exact size + // AllocSize can have alignment of at most the lowest bit set in + // AllocSize. + case DynAllocKind::New: + return Ctx.toCharUnitsFromBits( + std::min(DefaultNewAlign, uint64_t(1) << llvm::countr_zero(AllocSize))); + + case DynAllocKind::ArrayNew: { + const Type *ET = AllocType.getTypePtr() + ->getArrayElementTypeNoTypeQual() + ->getCanonicalTypeUnqualified() + .getTypePtr(); + // According to C++ [expr.new]p17, unless the element type of an + // array new expression is char, unsigned char or std::byte, the + // allocation may be offset into the allocation returned by + // ::operator new[]. Therefore no stricter alignment than the type's + // alignment is guaranteed. For char, unsigned char and std::byte + if (!ET->isSpecificBuiltinType(BuiltinType::UChar) && + !ET->isSpecificBuiltinType(BuiltinType::Char_U) && + !ET->isSpecificBuiltinType(BuiltinType::Char_S) && !ET->isStdByteType()) + return Ctx.toCharUnitsFromBits(TypeAlignment); + + // Otherwise, the allocation is offset from the result of ::operator + // new[] by a multiple of the strictest fundamental alignment. + // According C++ [basic.stc.dynamic.allocation]p3.2 the allocation + // returned by ::operator new[] is suitably aligned for all objects + // without new-extended alignment and size up to the allocated size. + uint64_t MaxFundamentalAlign = + std::max(TI.getLongLongAlign(), TI.getLongDoubleAlign()); + return Ctx.toCharUnitsFromBits(std::min( + {DefaultNewAlign, MaxFundamentalAlign, llvm::bit_floor(AllocSize)})); + } + + default: + llvm_unreachable("Unhandled DynAllocKind"); + } +} + APValue *EvalInfo::createHeapAlloc(const Expr *E, QualType T, LValue &LV) { if (NumHeapAllocs > DynamicAllocLValue::getMaxIndex()) { FFDiag(E, diag::note_constexpr_heap_alloc_limit_exceeded); return nullptr; } - DynamicAllocLValue DA(NumHeapAllocs++); + DynamicAllocLValue DA(NumHeapAllocs++, DynAlloc::kindOfExpr(E)); LV.set(APValue::LValueBase::getDynamicAlloc(DA, T)); auto Result = HeapAllocs.emplace(std::piecewise_construct, std::forward_as_tuple(DA), std::tuple<>()); @@ -7798,7 +7891,7 @@ static const FunctionDecl *getVirtualOperatorDelete(QualType T) { /// a diagnostic and returns std::nullopt. static std::optional<DynAlloc *> CheckDeleteKind(EvalInfo &Info, const Expr *E, const LValue &Pointer, - DynAlloc::Kind DeallocKind) { + DynAllocKind DeallocKind) { auto PointerAsString = [&] { return Pointer.toString(Info.Ctx, Info.Ctx.VoidPtrTy); }; @@ -7827,7 +7920,7 @@ static std::optional<DynAlloc *> CheckDeleteKind(EvalInfo &Info, const Expr *E, } bool Subobject = false; - if (DeallocKind == DynAlloc::New) { + if (DeallocKind == DynAllocKind::New) { Subobject = Pointer.Designator.MostDerivedPathLength != 0 || Pointer.Designator.isOnePastTheEnd(); } else { @@ -7871,7 +7964,7 @@ static bool HandleOperatorDeleteCall(EvalInfo &Info, const CallExpr *E) { return true; } - if (!CheckDeleteKind(Info, E, Pointer, DynAlloc::StdAllocator)) + if (!CheckDeleteKind(Info, E, Pointer, DynAllocKind::StdAllocator)) return false; Info.HeapAllocs.erase(Pointer.Base.get<DynamicAllocLValue>()); @@ -10546,6 +10639,9 @@ static CharUnits getBaseAlignment(EvalInfo &Info, const LValue &Value) { return Info.Ctx.getDeclAlign(VD); if (const auto *E = Value.Base.dyn_cast<const Expr *>()) return GetAlignOfExpr(Info.Ctx, E, UETT_AlignOf); + if (const auto &DA = Value.Base.dyn_cast<DynamicAllocLValue>()) + return GetAlignOfDynamicAlloc(Info.getASTContext(), Value.Base.getType(), + DA.getAllocKind()); return GetAlignOfType(Info.Ctx, Value.Base.getTypeInfoType(), UETT_AlignOf); } @@ -21771,7 +21867,8 @@ bool VoidExprEvaluator::VisitCXXDeleteExpr(const CXXDeleteExpr *E) { } std::optional<DynAlloc *> Alloc = CheckDeleteKind( - Info, E, Pointer, E->isArrayForm() ? DynAlloc::ArrayNew : DynAlloc::New); + Info, E, Pointer, + E->isArrayForm() ? DynAllocKind::ArrayNew : DynAllocKind::New); if (!Alloc) return false; QualType AllocType = Pointer.Base.getDynamicAllocType(); diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index b89c97f2b8900..0d65db6f4523c 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -2633,6 +2633,7 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, } else { Diag(TypeRange.getEnd(), diag::err_new_array_size_unknown_from_init) << Initializer->getSourceRange(); + return ExprError(); } } } diff --git a/clang/test/AST/ByteCode/new-delete.cpp b/clang/test/AST/ByteCode/new-delete.cpp index e5ddd8eb2a037..a9a297a10e76c 100644 --- a/clang/test/AST/ByteCode/new-delete.cpp +++ b/clang/test/AST/ByteCode/new-delete.cpp @@ -1154,7 +1154,7 @@ namespace NewNegSizeNothrow { constexpr bool test_nothrow_neg_size() { int x = get_neg_size(); - int* p = new (std::nothrow) int[x]; + int* p = new (std::nothrow) int[x]; return p == nullptr; } @@ -1291,13 +1291,13 @@ namespace FreeNonBlockPointer { extern int f(); #define fold(x) (__builtin_constant_p(x) ? (x) : (x)) - constexpr int foo() { + constexpr int foo() { // expected-error {{constexpr function never produces a constant expression}} int *p; p = fold((int*)(void*)f); - delete p; + delete p; // expected-note 2 {{delete of pointer '&f' that does not point to a heap-allocated object}} return 10; } - static_assert(foo() == 10); // both-error {{not an integral constant expression}} + static_assert(foo() == 10); // both-error {{not an integral constant expression}} expected-note {{in call to 'foo()'}} } namespace NonPrimitiveImplicitValueInitExpr { diff --git a/clang/test/SemaCXX/builtin-assume-aligned.cpp b/clang/test/SemaCXX/builtin-assume-aligned.cpp index 30296c72c6be8..1131cbbb32966 100644 --- a/clang/test/SemaCXX/builtin-assume-aligned.cpp +++ b/clang/test/SemaCXX/builtin-assume-aligned.cpp @@ -1,14 +1,18 @@ -// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -triple x86_64-linux-gnu %s +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11 -std=c++11 -triple x86_64-linux-gnu %s +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11 -std=c++11 -triple x86_64-linux-gnu -fexperimental-new-constant-interpreter %s +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx26 -std=c++26 -triple x86_64-linux-gnu %s +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx26 -std=c++26 -triple x86_64-linux-gnu -fexperimental-new-constant-interpreter %s int n; constexpr int *p = 0; -// expected-error@+1 {{must be initialized by a constant expression}} +// expected-error@+2 {{must be initialized by a constant expression}} +// cxx26-note@+1 {{a constant expression cannot modify an object that is visible outside that expression}} constexpr int *k = (int *) __builtin_assume_aligned(p, 16, n = 5); constexpr void *l = __builtin_assume_aligned(p, 16); -// expected-error@+2 {{must be initialized by a constant expression}} -// expected-note@+1 {{cast from 'void *' is not allowed in a constant expression}} +// cxx11-error@+2 {{must be initialized by a constant expression}} +// cxx11-note@+1 {{cast from 'void *' is not allowed in a constant expression}} constexpr int *c = (int *) __builtin_assume_aligned(p, 16); // expected-error@+2 {{must be initialized by a constant expression}} @@ -60,3 +64,85 @@ void AllocateAlignedBytes() { void *m = __builtin_assume_aligned( reinterpret_cast<void *>(AllocateAlignedBytes_payload), kAlignment); } + +namespace std { + enum class byte : unsigned char {}; +} // namespace std + +namespace GH173767 { +#if __cplusplus > 202302L + constexpr int a1a = (delete[] (unsigned char*)__builtin_assume_aligned(new unsigned char[65], __STDCPP_DEFAULT_NEW_ALIGNMENT__), 0); + constexpr int a1b = (delete[] (char*)__builtin_assume_aligned(new char[65], __STDCPP_DEFAULT_NEW_ALIGNMENT__), 0); + constexpr int a1c = (delete[] (std::byte*)__builtin_assume_aligned(new std::byte[65], __STDCPP_DEFAULT_NEW_ALIGNMENT__), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (16 bytes) is less than the asserted 32 bytes}} + constexpr int a2a = (delete[] (unsigned char*)__builtin_assume_aligned(new unsigned char[65], 2*__STDCPP_DEFAULT_NEW_ALIGNMENT__), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (16 bytes) is less than the asserted 32 bytes}} + constexpr int a2b = (delete[] (char*)__builtin_assume_aligned(new char[65], 2*__STDCPP_DEFAULT_NEW_ALIGNMENT__), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (16 bytes) is less than the asserted 32 bytes}} + constexpr int a2c = (delete[] (std::byte*)__builtin_assume_aligned(new std::byte[65], 2*__STDCPP_DEFAULT_NEW_ALIGNMENT__), 0); + + constexpr int b1 = (delete (int*)__builtin_assume_aligned(new int, alignof(int)), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (4 bytes) is less than the asserted 8 bytes}} + constexpr int b2 = (delete (int*)__builtin_assume_aligned(new int, 2*alignof(int)), 0); + + constexpr int c1 = (delete[] (int*)__builtin_assume_aligned(new int[4], alignof(int)), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (4 bytes) is less than the asserted 8 bytes}} + constexpr int c2 = (delete[] (int*)__builtin_assume_aligned(new int[4], 2*alignof(int)), 0); + + struct D { + alignas(2*__STDCPP_DEFAULT_NEW_ALIGNMENT__) int x[2]; + }; + + constexpr int d1 = (delete (D*)__builtin_assume_aligned(new D, alignof(D)), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (32 bytes) is less than the asserted 64 bytes}} + constexpr int d2 = (delete (D*)__builtin_assume_aligned(new D, 2*alignof(D)), 0); + + constexpr int d3 = []{ + auto p = new D; + (void)__builtin_assume_aligned(p->x + 1, alignof(int)); + delete p; + return 0; + }(); + + // expected-error@+3 {{must be initialized by a constant expression}} + // expected-note@+2 {{in call to}} + // expected-note@+3 {{offset of the aligned pointer from the base pointee object (4 bytes) is not a multiple of the asserted 8 bytes}} + constexpr int d4 = []{ + auto p = new D; + (void)__builtin_assume_aligned(p->x + 1, 2*alignof(int)); + delete p; + return 0; + }(); + + struct E { + unsigned char x[65]; + }; + + constexpr int e1 = (delete (E*)__builtin_assume_aligned(new E, 1), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (1 byte) is less than the asserted 2 bytes}} + constexpr int e2 = (delete (E*)__builtin_assume_aligned(new E, 2), 0); + + constexpr int f = []{ + auto p = new int; + delete p; + (void)__builtin_assume_aligned(p, alignof(int)); + return 0; + }(); + + // flexible array members as only member are allowed as an extension + // and cause the type to have size zero, but should still be aligned + // properly for the element type + struct G { + int x[]; + }; + + constexpr int g1 = (delete (G*)__builtin_assume_aligned(new G, alignof(int)), 0); +#endif +} // namespace GH173767 diff --git a/clang/test/SemaCXX/constant-expression-cxx2a.cpp b/clang/test/SemaCXX/constant-expression-cxx2a.cpp index 870865646a61e..fcc982182c06e 100644 --- a/clang/test/SemaCXX/constant-expression-cxx2a.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx2a.cpp @@ -901,8 +901,7 @@ namespace dynamic_alloc { // An array new-expression whose bound is neither given nor deducible from the // initializer is ill-formed; the constant evaluator must reject it gracefully // rather than crash. See GH200139. - static_assert((new int[]())[0] == 0); // expected-error {{cannot determine allocated array size from initializer}} \ - // expected-error {{static assertion expression is not an integral constant expression}} + static_assert((new int[]())[0] == 0); // expected-error {{cannot determine allocated array size from initializer}} constexpr bool erroneous_array_bound_nothrow(long long n) { int *p = new (std::nothrow) int[n]; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
