Author: Timm Bäder Date: 2024-07-14T21:17:47+02:00 New Revision: 48d703e7f56282ce5d690e45a129a4a7fd040ee6
URL: https://github.com/llvm/llvm-project/commit/48d703e7f56282ce5d690e45a129a4a7fd040ee6 DIFF: https://github.com/llvm/llvm-project/commit/48d703e7f56282ce5d690e45a129a4a7fd040ee6.diff LOG: Revert "[clang][Interp] Implement dynamic memory allocation handling (#70306)" This reverts commit fa133d3151b5e428b1c5819d29b0ad28a90882a2. It looks like this has some more serious problems: https://lab.llvm.org/buildbot/#/builders/39/builds/528 As well as build failures on MacOS. Added: Modified: clang/lib/AST/CMakeLists.txt clang/lib/AST/Interp/Compiler.cpp clang/lib/AST/Interp/Compiler.h clang/lib/AST/Interp/EvalEmitter.cpp clang/lib/AST/Interp/EvaluationResult.cpp clang/lib/AST/Interp/EvaluationResult.h clang/lib/AST/Interp/Interp.cpp clang/lib/AST/Interp/Interp.h clang/lib/AST/Interp/InterpBlock.h clang/lib/AST/Interp/InterpState.cpp clang/lib/AST/Interp/InterpState.h clang/lib/AST/Interp/Opcodes.td clang/lib/AST/Interp/Pointer.h clang/test/Rewriter/rewrite-modern-catch.m clang/test/SemaCXX/delete.cpp clang/test/SemaCXX/new-delete.cpp Removed: clang/lib/AST/Interp/DynamicAllocator.cpp clang/lib/AST/Interp/DynamicAllocator.h clang/test/AST/Interp/new-delete.cpp ################################################################################ diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index 70aecb781c2ff..ceaad8d3c5a86 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -75,7 +75,6 @@ add_clang_library(clangAST Interp/InterpBuiltin.cpp Interp/Floating.cpp Interp/EvaluationResult.cpp - Interp/DynamicAllocator.cpp Interp/Interp.cpp Interp/InterpBlock.cpp Interp/InterpFrame.cpp diff --git a/clang/lib/AST/Interp/Compiler.cpp b/clang/lib/AST/Interp/Compiler.cpp index 1f43f46c399f1..30dc7f5e4840b 100644 --- a/clang/lib/AST/Interp/Compiler.cpp +++ b/clang/lib/AST/Interp/Compiler.cpp @@ -2771,109 +2771,6 @@ bool Compiler<Emitter>::VisitCXXInheritedCtorInitExpr( return this->emitCall(F, 0, E); } -template <class Emitter> -bool Compiler<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) { - assert(classifyPrim(E->getType()) == PT_Ptr); - const Expr *Init = E->getInitializer(); - QualType ElementType = E->getAllocatedType(); - std::optional<PrimType> ElemT = classify(ElementType); - unsigned PlacementArgs = E->getNumPlacementArgs(); - bool IsNoThrow = false; - - // FIXME: Better diagnostic. diag::note_constexpr_new_placement - if (PlacementArgs != 0) { - // The only new-placement list we support is of the form (std::nothrow). - // - // FIXME: There is no restriction on this, but it's not clear that any - // other form makes any sense. We get here for cases such as: - // - // new (std::align_val_t{N}) X(int) - // - // (which should presumably be valid only if N is a multiple of - // alignof(int), and in any case can't be deallocated unless N is - // alignof(X) and X has new-extended alignment). - if (PlacementArgs != 1 || !E->getPlacementArg(0)->getType()->isNothrowT()) - return this->emitInvalid(E); - - if (!this->discard(E->getPlacementArg(0))) - return false; - IsNoThrow = true; - } - - const Descriptor *Desc; - if (ElemT) { - if (E->isArray()) - Desc = nullptr; // We're not going to use it in this case. - else - Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD, - /*IsConst=*/false, /*IsTemporary=*/false, - /*IsMutable=*/false); - } else { - Desc = P.createDescriptor( - E, ElementType.getTypePtr(), - E->isArray() ? std::nullopt : Descriptor::InlineDescMD, - /*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init); - } - - if (E->isArray()) { - std::optional<const Expr *> ArraySizeExpr = E->getArraySize(); - if (!ArraySizeExpr) - return false; - PrimType SizeT = classifyPrim((*ArraySizeExpr)->getType()); - - if (!this->visit(*ArraySizeExpr)) - return false; - - if (ElemT) { - // N primitive elements. - if (!this->emitAllocN(SizeT, *ElemT, E, IsNoThrow, E)) - return false; - } else { - // N Composite elements. - if (!this->emitAllocCN(SizeT, Desc, IsNoThrow, E)) - return false; - } - - if (Init && !this->visitInitializer(Init)) - return false; - - } else { - // Allocate just one element. - if (!this->emitAlloc(Desc, E)) - return false; - - if (Init) { - if (ElemT) { - if (!this->visit(Init)) - return false; - - if (!this->emitInit(*ElemT, E)) - return false; - } else { - // Composite. - if (!this->visitInitializer(Init)) - return false; - } - } - } - - if (DiscardResult) - return this->emitPopPtr(E); - - return true; -} - -template <class Emitter> -bool Compiler<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) { - const Expr *Arg = E->getArgument(); - - // Arg must be an lvalue. - if (!this->visit(Arg)) - return false; - - return this->emitFree(E->isArrayForm(), E); -} - template <class Emitter> bool Compiler<Emitter>::VisitExpressionTraitExpr(const ExpressionTraitExpr *E) { assert(Ctx.getLangOpts().CPlusPlus); diff --git a/clang/lib/AST/Interp/Compiler.h b/clang/lib/AST/Interp/Compiler.h index 6df723df2b444..23e7afd767e88 100644 --- a/clang/lib/AST/Interp/Compiler.h +++ b/clang/lib/AST/Interp/Compiler.h @@ -190,8 +190,6 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, bool VisitObjCBoxedExpr(const ObjCBoxedExpr *E); bool VisitCXXStdInitializerListExpr(const CXXStdInitializerListExpr *E); bool VisitStmtExpr(const StmtExpr *E); - bool VisitCXXNewExpr(const CXXNewExpr *E); - bool VisitCXXDeleteExpr(const CXXDeleteExpr *E); // Statements. bool visitCompoundStmt(const CompoundStmt *S); diff --git a/clang/lib/AST/Interp/DynamicAllocator.cpp b/clang/lib/AST/Interp/DynamicAllocator.cpp deleted file mode 100644 index f6ea264766b13..0000000000000 --- a/clang/lib/AST/Interp/DynamicAllocator.cpp +++ /dev/null @@ -1,118 +0,0 @@ -//==-------- DynamicAllocator.cpp - Dynamic allocations ----------*- 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 -// -//===----------------------------------------------------------------------===// - -#include "DynamicAllocator.h" -#include "InterpBlock.h" -#include "InterpState.h" - -using namespace clang; -using namespace clang::interp; - -DynamicAllocator::~DynamicAllocator() { cleanup(); } - -void DynamicAllocator::cleanup() { - // Invoke destructors of all the blocks and as a last restort, - // reset all the pointers pointing to them to null pointees. - // This should never show up in diagnostics, but it's necessary - // for us to not cause use-after-free problems. - for (auto &Iter : AllocationSites) { - auto &AllocSite = Iter.second; - for (auto &Alloc : AllocSite.Allocations) { - Block *B = reinterpret_cast<Block *>(Alloc.Memory.get()); - B->invokeDtor(); - if (B->hasPointers()) { - while (B->Pointers) { - Pointer *Next = B->Pointers->Next; - B->Pointers->PointeeStorage.BS.Pointee = nullptr; - B->Pointers = Next; - } - B->Pointers = nullptr; - } - } - } - - AllocationSites.clear(); -} - -Block *DynamicAllocator::allocate(const Expr *Source, PrimType T, - size_t NumElements, unsigned EvalID) { - // Create a new descriptor for an array of the specified size and - // element type. - const Descriptor *D = allocateDescriptor( - Source, T, Descriptor::InlineDescMD, NumElements, /*IsConst=*/false, - /*IsTemporary=*/false, /*IsMutable=*/false); - - return allocate(D, EvalID); -} - -Block *DynamicAllocator::allocate(const Descriptor *ElementDesc, - size_t NumElements, unsigned EvalID) { - // Create a new descriptor for an array of the specified size and - // element type. - const Descriptor *D = allocateDescriptor( - ElementDesc->asExpr(), ElementDesc, Descriptor::InlineDescMD, NumElements, - /*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false); - return allocate(D, EvalID); -} - -Block *DynamicAllocator::allocate(const Descriptor *D, unsigned EvalID) { - assert(D->asExpr()); - - auto Memory = - std::make_unique<std::byte[]>(sizeof(Block) + D->getAllocSize()); - auto *B = new (Memory.get()) Block(EvalID, D, /*isStatic=*/false); - B->invokeCtor(); - - InlineDescriptor *ID = reinterpret_cast<InlineDescriptor *>(B->rawData()); - ID->Desc = D; - ID->IsActive = true; - ID->Offset = sizeof(InlineDescriptor); - ID->IsBase = false; - ID->IsFieldMutable = false; - ID->IsConst = false; - ID->IsInitialized = false; - assert(ID->Desc); - - B->IsDynamic = true; - - if (auto It = AllocationSites.find(D->asExpr()); It != AllocationSites.end()) - It->second.Allocations.emplace_back(std::move(Memory)); - else - AllocationSites.insert( - {D->asExpr(), AllocationSite(std::move(Memory), D->isArray())}); - return B; -} - -bool DynamicAllocator::deallocate(const Expr *Source, - const Block *BlockToDelete, InterpState &S) { - auto It = AllocationSites.find(Source); - if (It == AllocationSites.end()) - return false; - - auto &Site = It->second; - assert(Site.size() > 0); - - // Find the Block to delete. - auto AllocIt = llvm::find_if(Site.Allocations, [&](const Allocation &A) { - const Block *B = reinterpret_cast<const Block *>(A.Memory.get()); - return BlockToDelete == B; - }); - - assert(AllocIt != Site.Allocations.end()); - - Block *B = reinterpret_cast<Block *>(AllocIt->Memory.get()); - B->invokeDtor(); - - S.deallocate(B); - Site.Allocations.erase(AllocIt); - - if (Site.size() == 0) - AllocationSites.erase(It); - - return true; -} diff --git a/clang/lib/AST/Interp/DynamicAllocator.h b/clang/lib/AST/Interp/DynamicAllocator.h deleted file mode 100644 index 3a6be28866223..0000000000000 --- a/clang/lib/AST/Interp/DynamicAllocator.h +++ /dev/null @@ -1,96 +0,0 @@ -//==--------- DynamicAllocator.h - Dynamic allocations ------------*- 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H -#define LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H - -#include "Descriptor.h" -#include "InterpBlock.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/iterator_range.h" -#include "llvm/Support/Allocator.h" - -namespace clang { -class Expr; -namespace interp { -class Block; -class InterpState; - -/// Manages dynamic memory allocations done during bytecode interpretation. -/// -/// We manage allocations as a map from their new-expression to a list -/// of allocations. This is called an AllocationSite. For each site, we -/// record whether it was allocated using new or new[], the -/// IsArrayAllocation flag. -/// -/// For all array allocations, we need to allocat new Descriptor instances, -/// so the DynamicAllocator has a llvm::BumpPtrAllocator similar to Program. -class DynamicAllocator final { - struct Allocation { - std::unique_ptr<std::byte[]> Memory; - Allocation(std::unique_ptr<std::byte[]> Memory) - : Memory(std::move(Memory)) {} - }; - - struct AllocationSite { - llvm::SmallVector<Allocation> Allocations; - bool IsArrayAllocation = false; - - AllocationSite(std::unique_ptr<std::byte[]> Memory, bool Array) - : IsArrayAllocation(Array) { - Allocations.push_back({std::move(Memory)}); - } - - size_t size() const { return Allocations.size(); } - }; - -public: - DynamicAllocator() = default; - ~DynamicAllocator(); - - void cleanup(); - - unsigned getNumAllocations() const { return AllocationSites.size(); } - - /// Allocate ONE element of the given descriptor. - Block *allocate(const Descriptor *D, unsigned EvalID); - /// Allocate \p NumElements primitive elements of the given type. - Block *allocate(const Expr *Source, PrimType T, size_t NumElements, - unsigned EvalID); - /// Allocate \p NumElements elements of the given descriptor. - Block *allocate(const Descriptor *D, size_t NumElements, unsigned EvalID); - - /// Deallocate the given source+block combination. - /// Returns \c true if anything has been deallocatd, \c false otherwise. - bool deallocate(const Expr *Source, const Block *BlockToDelete, - InterpState &S); - - /// Checks whether the allocation done at the given source is an array - /// allocation. - bool isArrayAllocation(const Expr *Source) const { - if (auto It = AllocationSites.find(Source); It != AllocationSites.end()) - return It->second.IsArrayAllocation; - return false; - } - - // FIXME: Public because I'm not sure how to expose an iterator to it. - llvm::DenseMap<const Expr *, AllocationSite> AllocationSites; - -private: - using PoolAllocTy = llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator>; - PoolAllocTy DescAllocator; - - /// Allocates a new descriptor. - template <typename... Ts> Descriptor *allocateDescriptor(Ts &&...Args) { - return new (DescAllocator) Descriptor(std::forward<Ts>(Args)...); - } -}; - -} // namespace interp -} // namespace clang -#endif diff --git a/clang/lib/AST/Interp/EvalEmitter.cpp b/clang/lib/AST/Interp/EvalEmitter.cpp index 968f21ea974c2..9701796fb9303 100644 --- a/clang/lib/AST/Interp/EvalEmitter.cpp +++ b/clang/lib/AST/Interp/EvalEmitter.cpp @@ -133,17 +133,9 @@ bool EvalEmitter::fallthrough(const LabelTy &Label) { return true; } -static bool checkReturnState(InterpState &S) { - return S.maybeDiagnoseDanglingAllocations(); -} - template <PrimType OpType> bool EvalEmitter::emitRet(const SourceInfo &Info) { if (!isActive()) return true; - - if (!checkReturnState(S)) - return false; - using T = typename PrimConv<OpType>::T; EvalResult.setValue(S.Stk.pop<T>().toAPValue()); return true; @@ -155,14 +147,9 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(const SourceInfo &Info) { const Pointer &Ptr = S.Stk.pop<Pointer>(); - if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info)) - return false; if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr)) return false; - if (!checkReturnState(S)) - return false; - // Implicitly convert lvalue to rvalue, if requested. if (ConvertResultToRValue) { // Never allow reading from a non-const pointer, unless the memory @@ -185,17 +172,12 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(const SourceInfo &Info) { template <> bool EvalEmitter::emitRet<PT_FnPtr>(const SourceInfo &Info) { if (!isActive()) return true; - - if (!checkReturnState(S)) - return false; // Function pointers cannot be converted to rvalues. EvalResult.setFunctionPointer(S.Stk.pop<FunctionPointer>()); return true; } bool EvalEmitter::emitRetVoid(const SourceInfo &Info) { - if (!checkReturnState(S)) - return false; EvalResult.setValid(); return true; } @@ -203,14 +185,9 @@ bool EvalEmitter::emitRetVoid(const SourceInfo &Info) { bool EvalEmitter::emitRetValue(const SourceInfo &Info) { const auto &Ptr = S.Stk.pop<Pointer>(); - if (!EvalResult.checkReturnValue(S, Ctx, Ptr, Info)) - return false; if (CheckFullyInitialized && !EvalResult.checkFullyInitialized(S, Ptr)) return false; - if (!checkReturnState(S)) - return false; - if (std::optional<APValue> APV = Ptr.toRValue(S.getCtx(), EvalResult.getSourceType())) { EvalResult.setValue(*APV); diff --git a/clang/lib/AST/Interp/EvaluationResult.cpp b/clang/lib/AST/Interp/EvaluationResult.cpp index 0bebfd4ad984e..d0d68f75dd803 100644 --- a/clang/lib/AST/Interp/EvaluationResult.cpp +++ b/clang/lib/AST/Interp/EvaluationResult.cpp @@ -10,7 +10,6 @@ #include "InterpState.h" #include "Record.h" #include "clang/AST/ExprCXX.h" -#include "llvm/ADT/SetVector.h" namespace clang { namespace interp { @@ -153,11 +152,6 @@ bool EvaluationResult::checkFullyInitialized(InterpState &S, if (Ptr.isZero()) return true; - // We can't inspect dead pointers at all. Return true here so we can - // diagnose them later. - if (!Ptr.isLive()) - return true; - SourceLocation InitLoc; if (const auto *D = Source.dyn_cast<const Decl *>()) InitLoc = cast<VarDecl>(D)->getAnyInitializer()->getExprLoc(); @@ -174,71 +168,5 @@ bool EvaluationResult::checkFullyInitialized(InterpState &S, return true; } -static void collectBlocks(const Pointer &Ptr, - llvm::SetVector<const Block *> &Blocks) { - auto isUsefulPtr = [](const Pointer &P) -> bool { - return P.isLive() && !P.isZero() && !P.isDummy() && - !P.isUnknownSizeArray() && !P.isOnePastEnd() && P.isBlockPointer(); - }; - - if (!isUsefulPtr(Ptr)) - return; - - Blocks.insert(Ptr.block()); - - const Descriptor *Desc = Ptr.getFieldDesc(); - if (!Desc) - return; - - if (const Record *R = Desc->ElemRecord) { - for (const Record::Field &F : R->fields()) { - const Pointer &FieldPtr = Ptr.atField(F.Offset); - assert(FieldPtr.block() == Ptr.block()); - collectBlocks(FieldPtr, Blocks); - } - } else if (Desc->isPrimitive() && Desc->getPrimType() == PT_Ptr) { - const Pointer &Pointee = Ptr.deref<Pointer>(); - if (isUsefulPtr(Pointee) && !Blocks.contains(Pointee.block())) - collectBlocks(Pointee, Blocks); - - } else if (Desc->isPrimitiveArray() && Desc->getPrimType() == PT_Ptr) { - for (unsigned I = 0; I != Desc->getNumElems(); ++I) { - const Pointer &ElemPointee = Ptr.atIndex(I).deref<Pointer>(); - if (isUsefulPtr(ElemPointee) && !Blocks.contains(ElemPointee.block())) - collectBlocks(ElemPointee, Blocks); - } - } else if (Desc->isCompositeArray()) { - for (unsigned I = 0; I != Desc->getNumElems(); ++I) { - const Pointer &ElemPtr = Ptr.atIndex(I).narrow(); - collectBlocks(ElemPtr, Blocks); - } - } -} - -bool EvaluationResult::checkReturnValue(InterpState &S, const Context &Ctx, - const Pointer &Ptr, - const SourceInfo &Info) { - // Collect all blocks that this pointer (transitively) points to and - // return false if any of them is a dynamic block. - llvm::SetVector<const Block *> Blocks; - - collectBlocks(Ptr, Blocks); - - for (const Block *B : Blocks) { - if (B->isDynamic()) { - assert(B->getDescriptor()); - assert(B->getDescriptor()->asExpr()); - - S.FFDiag(Info, diag::note_constexpr_dynamic_alloc) - << Ptr.getType()->isReferenceType() << !Ptr.isRoot(); - S.Note(B->getDescriptor()->asExpr()->getExprLoc(), - diag::note_constexpr_dynamic_alloc_here); - return false; - } - } - - return true; -} - } // namespace interp } // namespace clang diff --git a/clang/lib/AST/Interp/EvaluationResult.h b/clang/lib/AST/Interp/EvaluationResult.h index ef662e3779bc3..378f1ccdb0af4 100644 --- a/clang/lib/AST/Interp/EvaluationResult.h +++ b/clang/lib/AST/Interp/EvaluationResult.h @@ -98,12 +98,7 @@ class EvaluationResult final { /// LValue and we can't read from it. std::optional<APValue> toRValue() const; - /// Check that all subobjects of the given pointer have been initialized. bool checkFullyInitialized(InterpState &S, const Pointer &Ptr) const; - /// Check that none of the blocks the given pointer (transitively) points - /// to are dynamically allocated. - bool checkReturnValue(InterpState &S, const Context &Ctx, const Pointer &Ptr, - const SourceInfo &Info); QualType getSourceType() const { if (const auto *D = @@ -118,7 +113,6 @@ class EvaluationResult final { void dump() const; friend class EvalEmitter; - friend class InterpState; }; } // namespace interp diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp index cafe2175f5cc4..70a470021e7f2 100644 --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -693,58 +693,6 @@ bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result, return true; } -bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC) { - if (S.getLangOpts().CPlusPlus20) - return true; - - const SourceInfo &E = S.Current->getSource(OpPC); - S.FFDiag(E, diag::note_constexpr_new); - return false; -} - -bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray, - bool DeleteIsArray, const Descriptor *D, - const Expr *NewExpr) { - if (NewWasArray == DeleteIsArray) - return true; - - QualType TypeToDiagnose; - // We need to shuffle things around a bit here to get a better diagnostic, - // because the expression we allocated the block for was of type int*, - // but we want to get the array size right. - if (D->isArray()) { - QualType ElemQT = D->getType()->getPointeeType(); - TypeToDiagnose = S.getCtx().getConstantArrayType( - ElemQT, APInt(64, D->getNumElems(), false), nullptr, - ArraySizeModifier::Normal, 0); - } else - TypeToDiagnose = D->getType()->getPointeeType(); - - const SourceInfo &E = S.Current->getSource(OpPC); - S.FFDiag(E, diag::note_constexpr_new_delete_mismatch) - << DeleteIsArray << 0 << TypeToDiagnose; - S.Note(NewExpr->getExprLoc(), diag::note_constexpr_dynamic_alloc_here) - << NewExpr->getSourceRange(); - return false; -} - -bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source, - const Pointer &Ptr) { - if (Source && isa<CXXNewExpr>(Source)) - return true; - - // Whatever this is, we didn't heap allocate it. - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_constexpr_delete_not_heap_alloc) - << Ptr.toDiagnosticString(S.getCtx()); - - if (Ptr.isTemporary()) - S.Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here); - else - S.Note(Ptr.getDeclLoc(), diag::note_declared_at); - return false; -} - /// We aleady know the given DeclRefExpr is invalid for some reason, /// now figure out why and print appropriate diagnostics. bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR) { diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h index 955b002c3b3f4..c7d8604c7dc2a 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -15,7 +15,6 @@ #include "../ExprConstShared.h" #include "Boolean.h" -#include "DynamicAllocator.h" #include "Floating.h" #include "Function.h" #include "FunctionPointer.h" @@ -123,20 +122,6 @@ bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD); bool CheckNonNullArgs(InterpState &S, CodePtr OpPC, const Function *F, const CallExpr *CE, unsigned ArgSize); -/// Checks if dynamic memory allocation is available in the current -/// language mode. -bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC); - -/// Diagnose mismatched new[]/delete or new/delete[] pairs. -bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray, - bool DeleteIsArray, const Descriptor *D, - const Expr *NewExpr); - -/// 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); - /// Sets the given integral value to the pointer, which is of /// a std::{weak,partial,strong}_ordering type. bool SetThreeWayComparisonField(InterpState &S, CodePtr OpPC, @@ -204,30 +189,6 @@ bool CheckDivRem(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS) { return true; } -template <typename SizeT> -bool CheckArraySize(InterpState &S, CodePtr OpPC, SizeT *NumElements, - bool IsNoThrow) { - // FIXME: Both the SizeT::from() as well as the - // NumElements.toAPSInt() in this function are rather expensive. - - // FIXME: GH63562 - // APValue stores array extents as unsigned, - // so anything that is greater that unsigned would overflow when - // constructing the array, we catch this here. - SizeT MaxElements = SizeT::from(std::numeric_limits<unsigned>::max()); - if (NumElements->toAPSInt().getActiveBits() > - ConstantArrayType::getMaxSizeBits(S.getCtx()) || - *NumElements > MaxElements) { - if (!IsNoThrow) { - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_constexpr_new_too_large) - << NumElements->toDiagnosticString(S.getCtx()); - } - return false; - } - return true; -} - /// Checks if the result of a floating-point operation is valid /// in the current context. bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result, @@ -2806,118 +2767,6 @@ inline bool CheckDecl(InterpState &S, CodePtr OpPC, const VarDecl *VD) { return true; } -inline bool Alloc(InterpState &S, CodePtr OpPC, const Descriptor *Desc) { - assert(Desc); - - if (!CheckDynamicMemoryAllocation(S, OpPC)) - return false; - - DynamicAllocator &Allocator = S.getAllocator(); - Block *B = Allocator.allocate(Desc, S.Ctx.getEvalID()); - assert(B); - - S.Stk.push<Pointer>(B, sizeof(InlineDescriptor)); - - return true; -} - -template <PrimType Name, class SizeT = typename PrimConv<Name>::T> -inline bool AllocN(InterpState &S, CodePtr OpPC, PrimType T, const Expr *Source, - bool IsNoThrow) { - if (!CheckDynamicMemoryAllocation(S, OpPC)) - return false; - - SizeT NumElements = S.Stk.pop<SizeT>(); - if (!CheckArraySize(S, OpPC, &NumElements, IsNoThrow)) { - if (!IsNoThrow) - return false; - - // If this failed and is nothrow, just return a null ptr. - S.Stk.push<Pointer>(0, nullptr); - return true; - } - - DynamicAllocator &Allocator = S.getAllocator(); - Block *B = Allocator.allocate(Source, T, static_cast<size_t>(NumElements), - S.Ctx.getEvalID()); - assert(B); - S.Stk.push<Pointer>(B, sizeof(InlineDescriptor)); - - return true; -} - -template <PrimType Name, class SizeT = typename PrimConv<Name>::T> -inline bool AllocCN(InterpState &S, CodePtr OpPC, const Descriptor *ElementDesc, - bool IsNoThrow) { - if (!CheckDynamicMemoryAllocation(S, OpPC)) - return false; - - SizeT NumElements = S.Stk.pop<SizeT>(); - if (!CheckArraySize(S, OpPC, &NumElements, IsNoThrow)) { - if (!IsNoThrow) - return false; - - // If this failed and is nothrow, just return a null ptr. - S.Stk.push<Pointer>(0, ElementDesc); - return true; - } - - DynamicAllocator &Allocator = S.getAllocator(); - Block *B = Allocator.allocate(ElementDesc, static_cast<size_t>(NumElements), - S.Ctx.getEvalID()); - assert(B); - - S.Stk.push<Pointer>(B, sizeof(InlineDescriptor)); - - return true; -} - -static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm) { - - if (!CheckDynamicMemoryAllocation(S, OpPC)) - return false; - - const Expr *Source = nullptr; - const Block *BlockToDelete = nullptr; - { - // Extra scope for this so the block doesn't have this pointer - // pointing to it when we destroy it. - const Pointer &Ptr = S.Stk.pop<Pointer>(); - - // Deleteing nullptr is always fine. - if (Ptr.isZero()) - return true; - - if (!Ptr.isRoot() || Ptr.isOnePastEnd() || Ptr.isArrayElement()) { - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_constexpr_delete_subobject) - << Ptr.toDiagnosticString(S.getCtx()) << Ptr.isOnePastEnd(); - return false; - } - - Source = Ptr.getDeclDesc()->asExpr(); - BlockToDelete = Ptr.block(); - - if (!CheckDeleteSource(S, OpPC, Source, Ptr)) - return false; - } - assert(Source); - assert(BlockToDelete); - - DynamicAllocator &Allocator = S.getAllocator(); - bool WasArrayAlloc = Allocator.isArrayAllocation(Source); - const Descriptor *BlockDesc = BlockToDelete->getDescriptor(); - - if (!Allocator.deallocate(Source, BlockToDelete, S)) { - // Nothing has been deallocated, this must be a double-delete. - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_constexpr_double_delete); - return false; - } - return CheckNewDeleteForms(S, OpPC, WasArrayAlloc, DeleteIsArrayForm, - BlockDesc, Source); -} - //===----------------------------------------------------------------------===// // Read opcode arguments //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/Interp/InterpBlock.h b/clang/lib/AST/Interp/InterpBlock.h index e86f411704479..1f25de3589630 100644 --- a/clang/lib/AST/Interp/InterpBlock.h +++ b/clang/lib/AST/Interp/InterpBlock.h @@ -52,14 +52,14 @@ class Block final { Block(unsigned EvalID, const std::optional<unsigned> &DeclID, const Descriptor *Desc, bool IsStatic = false, bool IsExtern = false) : EvalID(EvalID), DeclID(DeclID), IsStatic(IsStatic), IsExtern(IsExtern), - IsDynamic(false), Desc(Desc) { + Desc(Desc) { assert(Desc); } Block(unsigned EvalID, const Descriptor *Desc, bool IsStatic = false, bool IsExtern = false) : EvalID(EvalID), DeclID((unsigned)-1), IsStatic(IsStatic), - IsExtern(IsExtern), IsDynamic(false), Desc(Desc) { + IsExtern(IsExtern), Desc(Desc) { assert(Desc); } @@ -73,7 +73,6 @@ class Block final { bool isStatic() const { return IsStatic; } /// Checks if the block is temporary. bool isTemporary() const { return Desc->IsTemporary; } - bool isDynamic() const { return IsDynamic; } /// Returns the size of the block. unsigned getSize() const { return Desc->getAllocSize(); } /// Returns the declaration ID. @@ -136,12 +135,11 @@ class Block final { friend class Pointer; friend class DeadBlock; friend class InterpState; - friend class DynamicAllocator; Block(unsigned EvalID, const Descriptor *Desc, bool IsExtern, bool IsStatic, bool IsDead) : EvalID(EvalID), IsStatic(IsStatic), IsExtern(IsExtern), IsDead(true), - IsDynamic(false), Desc(Desc) { + Desc(Desc) { assert(Desc); } @@ -171,9 +169,6 @@ class Block final { /// Flag indicating if the block contents have been initialized /// via invokeCtor. bool IsInitialized = false; - /// Flag indicating if this block has been allocated via dynamic - /// memory allocation (e.g. malloc). - bool IsDynamic = false; /// Pointer to the stack slot descriptor. const Descriptor *Desc; }; diff --git a/clang/lib/AST/Interp/InterpState.cpp b/clang/lib/AST/Interp/InterpState.cpp index fa4d220299e46..a8538541f4915 100644 --- a/clang/lib/AST/Interp/InterpState.cpp +++ b/clang/lib/AST/Interp/InterpState.cpp @@ -41,8 +41,6 @@ void InterpState::cleanup() { P->PointeeStorage.BS.Pointee = nullptr; } } - - Alloc.cleanup(); } Frame *InterpState::getCurrentFrame() { @@ -83,18 +81,3 @@ void InterpState::deallocate(Block *B) { B->invokeDtor(); } } - -bool InterpState::maybeDiagnoseDanglingAllocations() { - bool NoAllocationsLeft = (Alloc.getNumAllocations() == 0); - - if (!checkingPotentialConstantExpression()) { - for (const auto &It : Alloc.AllocationSites) { - assert(It.second.size() > 0); - - const Expr *Source = It.first; - CCEDiag(Source->getExprLoc(), diag::note_constexpr_memory_leak) - << (It.second.size() - 1) << Source->getSourceRange(); - } - } - return NoAllocationsLeft; -} diff --git a/clang/lib/AST/Interp/InterpState.h b/clang/lib/AST/Interp/InterpState.h index 61ee54331c65d..138e1d7ac95d5 100644 --- a/clang/lib/AST/Interp/InterpState.h +++ b/clang/lib/AST/Interp/InterpState.h @@ -14,7 +14,6 @@ #define LLVM_CLANG_AST_INTERP_INTERPSTATE_H #include "Context.h" -#include "DynamicAllocator.h" #include "Function.h" #include "InterpFrame.h" #include "InterpStack.h" @@ -103,23 +102,13 @@ class InterpState final : public State, public SourceMapper { void setEvalLocation(SourceLocation SL) { this->EvalLocation = SL; } - DynamicAllocator &getAllocator() { return Alloc; } - - /// Diagnose any dynamic allocations that haven't been freed yet. - /// Will return \c false if there were any allocations to diagnose, - /// \c true otherwise. - bool maybeDiagnoseDanglingAllocations(); - private: - friend class EvaluationResult; /// AST Walker state. State &Parent; /// Dead block chain. DeadBlock *DeadBlocks = nullptr; /// Reference to the offset-source mapping. SourceMapper *M; - /// Allocator used for dynamic allocations performed via the program. - DynamicAllocator Alloc; public: /// Reference to the module containing all bytecode. diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td index 3e69098570bd7..8d01fe1ac2bd1 100644 --- a/clang/lib/AST/Interp/Opcodes.td +++ b/clang/lib/AST/Interp/Opcodes.td @@ -58,14 +58,12 @@ def ArgRoundingMode : ArgType { let Name = "llvm::RoundingMode"; } def ArgLETD: ArgType { let Name = "const LifetimeExtendedTemporaryDecl *"; } def ArgCastKind : ArgType { let Name = "CastKind"; } def ArgCallExpr : ArgType { let Name = "const CallExpr *"; } -def ArgExpr : ArgType { let Name = "const Expr *"; } def ArgOffsetOfExpr : ArgType { let Name = "const OffsetOfExpr *"; } def ArgDeclRef : ArgType { let Name = "const DeclRefExpr *"; } +def ArgDesc : ArgType { let Name = "const Descriptor *"; } def ArgCCI : ArgType { let Name = "const ComparisonCategoryInfo *"; } def ArgDecl : ArgType { let Name = "const Decl*"; } def ArgVarDecl : ArgType { let Name = "const VarDecl*"; } -def ArgDesc : ArgType { let Name = "const Descriptor *"; } -def ArgPrimType : ArgType { let Name = "PrimType"; } //===----------------------------------------------------------------------===// // Classes of types instructions operate on. @@ -749,23 +747,3 @@ def GetMemberPtrDecl : Opcode; // Debugging. //===----------------------------------------------------------------------===// def Dump : Opcode; - -def Alloc : Opcode { - let Args = [ArgDesc]; -} - -def AllocN : Opcode { - let Types = [IntegerTypeClass]; - let Args = [ArgPrimType, ArgExpr, ArgBool]; - let HasGroup = 1; -} - -def AllocCN : Opcode { - let Types = [IntegerTypeClass]; - let Args = [ArgDesc, ArgBool]; - let HasGroup = 1; -} - -def Free : Opcode { - let Args = [ArgBool]; -} diff --git a/clang/lib/AST/Interp/Pointer.h b/clang/lib/AST/Interp/Pointer.h index 2beb83590dde5..6e9e8675306ef 100644 --- a/clang/lib/AST/Interp/Pointer.h +++ b/clang/lib/AST/Interp/Pointer.h @@ -637,7 +637,6 @@ class Pointer { friend class MemberPointer; friend class InterpState; friend struct InitMap; - friend class DynamicAllocator; Pointer(Block *Pointee, unsigned Base, uint64_t Offset); diff --git a/clang/test/AST/Interp/new-delete.cpp b/clang/test/AST/Interp/new-delete.cpp deleted file mode 100644 index 744b142492924..0000000000000 --- a/clang/test/AST/Interp/new-delete.cpp +++ /dev/null @@ -1,488 +0,0 @@ -// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify=expected,both %s -// RUN: %clang_cc1 -std=c++20 -fexperimental-new-constant-interpreter -verify=expected,both %s -// RUN: %clang_cc1 -verify=ref,both %s -// RUN: %clang_cc1 -std=c++20 -verify=ref,both %s - -#if __cplusplus >= 202002L - -constexpr int *Global = new int(12); // both-error {{must be initialized by a constant expression}} \ - // both-note {{pointer to heap-allocated object}} \ - // both-note {{heap allocation performed here}} - -static_assert(*(new int(12)) == 12); // both-error {{not an integral constant expression}} \ - // both-note {{allocation performed here was not deallocated}} - - -constexpr int a() { - new int(12); // both-note {{allocation performed here was not deallocated}} - return 1; -} -static_assert(a() == 1, ""); // both-error {{not an integral constant expression}} - -constexpr int b() { - int *i = new int(12); - int m = *i; - delete(i); - return m; -} -static_assert(b() == 12, ""); - - -struct S { - int a; - int b; - - static constexpr S *create(int a, int b) { - return new S(a, b); - } -}; - -constexpr int c() { - S *s = new S(12, 13); - - int i = s->a; - delete s; - - return i; -} -static_assert(c() == 12, ""); - -/// Dynamic allocation in function ::create(), freed in function d(). -constexpr int d() { - S* s = S::create(12, 14); - - int sum = s->a + s->b; - delete s; - return sum; -} -static_assert(d() == 26); - - -/// Test we emit the right diagnostic for several allocations done on -/// the same site. -constexpr int loop() { - for (int i = 0; i < 10; ++i) { - int *a = new int[10]; // both-note {{not deallocated (along with 9 other memory leaks)}} - } - - return 1; -} -static_assert(loop() == 1, ""); // both-error {{not an integral constant expression}} - -/// No initializer. -constexpr int noInit() { - int *i = new int; - delete i; - return 0; -} -static_assert(noInit() == 0, ""); - -/// Try to delete a pointer that hasn't been heap allocated. -constexpr int notHeapAllocated() { // both-error {{never produces a constant expression}} - int A = 0; // both-note 2{{declared here}} - delete &A; // ref-note 2{{delete of pointer '&A' that does not point to a heap-allocated object}} \ - // expected-note 2{{delete of pointer '&A' that does not point to a heap-allocated object}} - - return 1; -} -static_assert(notHeapAllocated() == 1, ""); // both-error {{not an integral constant expression}} \ - // both-note {{in call to 'notHeapAllocated()'}} - -consteval int deleteNull() { - int *A = nullptr; - delete A; - return 1; -} -static_assert(deleteNull() == 1, ""); - -consteval int doubleDelete() { // both-error {{never produces a constant expression}} - int *A = new int; - delete A; - delete A; // both-note 2{{delete of pointer that has already been deleted}} - return 1; -} -static_assert(doubleDelete() == 1); // both-error {{not an integral constant expression}} \ - // both-note {{in call to 'doubleDelete()'}} - -constexpr int AutoArray() { - auto array = new int[]{0, 1, 2, 3}; - int ret = array[3]; - delete [] array; - return ret; -} - -static_assert(AutoArray() == 3); - -#if 0 -consteval int largeArray1(bool b) { - if (b) { - int *a = new int[1ull<<32]; // both-note {{cannot allocate array; evaluated array bound 4294967296 is too large}} - delete[] a; - } - return 1; -} -static_assert(largeArray1(false) == 1, ""); -static_assert(largeArray1(true) == 1, ""); // both-error {{not an integral constant expression}} \ - // both-note {{in call to 'largeArray1(true)'}} - -consteval int largeArray2(bool b) { - if (b) { - S *a = new S[1ull<<32]; // both-note {{cannot allocate array; evaluated array bound 4294967296 is too large}} - delete[] a; - } - return 1; -} -static_assert(largeArray2(false) == 1, ""); -static_assert(largeArray2(true) == 1, ""); // both-error {{not an integral constant expression}} \ - // both-note {{in call to 'largeArray2(true)'}} -#endif -namespace Arrays { - constexpr int d() { - int *Arr = new int[12]; - - Arr[0] = 1; - Arr[1] = 5; - - int sum = Arr[0] + Arr[1]; - delete[] Arr; - return sum; - } - static_assert(d() == 6); - - - constexpr int mismatch1() { // both-error {{never produces a constant expression}} - int *i = new int(12); // both-note {{allocated with 'new' here}} \ - // both-note 2{{heap allocation performed here}} - delete[] i; // both-warning {{'delete[]' applied to a pointer that was allocated with 'new'}} \ - // both-note 2{{array delete used to delete pointer to non-array object of type 'int'}} - return 6; - } - static_assert(mismatch1() == 6); // both-error {{not an integral constant expression}} \ - // both-note {{in call to 'mismatch1()'}} - - constexpr int mismatch2() { // both-error {{never produces a constant expression}} - int *i = new int[12]; // both-note {{allocated with 'new[]' here}} \ - // both-note 2{{heap allocation performed here}} - delete i; // both-warning {{'delete' applied to a pointer that was allocated with 'new[]'}} \ - // both-note 2{{non-array delete used to delete pointer to array object of type 'int[12]'}} - return 6; - } - static_assert(mismatch2() == 6); // both-error {{not an integral constant expression}} \ - // both-note {{in call to 'mismatch2()'}} - /// Array of composite elements. - constexpr int foo() { - S *ss = new S[12]; - - ss[0].a = 12; - - int m = ss[0].a; - - delete[] ss; - return m; - } - static_assert(foo() == 12); - - - - constexpr int ArrayInit() { - auto array = new int[4]{0, 1, 2, 3}; - int ret = array[0]; - delete [] array; - return ret; - } - static_assert(ArrayInit() == 0, ""); - - struct S { - float F; - }; - constexpr float ArrayInit2() { - auto array = new S[4]{}; - float ret = array[0].F; - delete [] array; - return ret; - } - static_assert(ArrayInit2() == 0.0f, ""); -} - -namespace std { - struct type_info; - struct destroying_delete_t { - explicit destroying_delete_t() = default; - } inline constexpr destroying_delete{}; - struct nothrow_t { - explicit nothrow_t() = default; - } inline constexpr nothrow{}; - using size_t = decltype(sizeof(0)); - enum class align_val_t : size_t {}; -}; - -[[nodiscard]] void *operator new(std::size_t, const std::nothrow_t&) noexcept; -[[nodiscard]] void *operator new(std::size_t, std::align_val_t, const std::nothrow_t&) noexcept; -[[nodiscard]] void *operator new[](std::size_t, const std::nothrow_t&) noexcept; -[[nodiscard]] void *operator new[](std::size_t, std::align_val_t, const std::nothrow_t&) noexcept; -[[nodiscard]] void *operator new[](std::size_t, std::align_val_t); -void operator delete(void*, const std::nothrow_t&) noexcept; -void operator delete(void*, std::align_val_t, const std::nothrow_t&) noexcept; -void operator delete[](void*, const std::nothrow_t&) noexcept; -void operator delete[](void*, std::align_val_t, const std::nothrow_t&) noexcept; - -struct placement_new_arg {}; -void *operator new(std::size_t, placement_new_arg); -void operator delete(void*, placement_new_arg); - - -constexpr void *operator new(std::size_t, void *p) { return p; } -namespace std { - template<typename T> constexpr T *construct(T *p) { return new (p) T; } - template<typename T> constexpr void destroy(T *p) { p->~T(); } -} - - - -/// FIXME: The new interpreter produces the wrong diagnostic. -namespace PlacementNew { - constexpr int foo() { // both-error {{never produces a constant expression}} - char c[sizeof(int)]; - new (c) int{12}; // ref-note {{call to placement 'operator new'}} \ - // expected-note {{subexpression not valid in a constant expression}} - return 0; - } -} - -namespace NowThrowNew { - constexpr bool erroneous_array_bound_nothrow(long long n) { - int *p = new (std::nothrow) int[n]; - bool result = p != nullptr; - delete[] p; - return result; - } - static_assert(erroneous_array_bound_nothrow(3)); - static_assert(erroneous_array_bound_nothrow(0)); - static_assert(erroneous_array_bound_nothrow(-1) == 0); - static_assert(!erroneous_array_bound_nothrow(1LL << 62)); - - struct S { int a; }; - constexpr bool erroneous_array_bound_nothrow2(long long n) { - S *p = new (std::nothrow) S[n]; - bool result = p != nullptr; - delete[] p; - return result; - } - /// This needs support for CXXConstrucExprs with non-constant array sizes. - static_assert(erroneous_array_bound_nothrow2(3)); // expected-error {{not an integral constant expression}} - static_assert(erroneous_array_bound_nothrow2(0));// expected-error {{not an integral constant expression}} - static_assert(erroneous_array_bound_nothrow2(-1) == 0);// expected-error {{not an integral constant expression}} - static_assert(!erroneous_array_bound_nothrow2(1LL << 62));// expected-error {{not an integral constant expression}} - - constexpr bool evaluate_nothrow_arg() { - bool ok = false; - delete new ((ok = true, std::nothrow)) int; - return ok; - } - static_assert(evaluate_nothrow_arg()); -} - -namespace placement_new_delete { - struct ClassSpecificNew { - void *operator new(std::size_t); - }; - struct ClassSpecificDelete { - void operator delete(void*); - }; - struct DestroyingDelete { - void operator delete(DestroyingDelete*, std::destroying_delete_t); - }; - struct alignas(64) Overaligned {}; - - constexpr bool ok() { - delete new Overaligned; - delete ::new ClassSpecificNew; - ::delete new ClassSpecificDelete; - ::delete new DestroyingDelete; - return true; - } - static_assert(ok()); - - /// FIXME: Diagnosting placement new. - constexpr bool bad(int which) { - switch (which) { - case 0: - delete new (placement_new_arg{}) int; // ref-note {{call to placement 'operator new'}} \ - // expected-note {{subexpression not valid in a constant expression}} - break; - - case 1: - delete new ClassSpecificNew; // ref-note {{call to class-specific 'operator new'}} - break; - - case 2: - delete new ClassSpecificDelete; // ref-note {{call to class-specific 'operator delete'}} - break; - - case 3: - delete new DestroyingDelete; // ref-note {{call to class-specific 'operator delete'}} - break; - - case 4: - // FIXME: This technically follows the standard's rules, but it seems - // unreasonable to expect implementations to support this. - delete new (std::align_val_t{64}) Overaligned; // ref-note {{placement new expression is not yet supported}} \ - // expected-note {{subexpression not valid in a constant expression}} - break; - } - - return true; - } - static_assert(bad(0)); // both-error {{constant expression}} \ - // both-note {{in call}} - static_assert(bad(1)); // ref-error {{constant expression}} ref-note {{in call}} - static_assert(bad(2)); // ref-error {{constant expression}} ref-note {{in call}} - static_assert(bad(3)); // ref-error {{constant expression}} ref-note {{in call}} - static_assert(bad(4)); // both-error {{constant expression}} \ - // both-note {{in call}} -} - - - - -namespace delete_random_things { - static_assert((delete new int, true)); - static_assert((delete (int*)0, true)); - int n; // both-note {{declared here}} - static_assert((delete &n, true)); // both-error {{}} \ - // both-note {{delete of pointer '&n' that does not point to a heap-allocated object}} - struct A { int n; }; - static_assert((delete &(new A)->n, true)); // both-error {{}} \ - // both-note {{delete of pointer to subobject }} - static_assert((delete (new int + 1), true)); // both-error {{}} \ - // ref-note {{delete of pointer '&{*new int#0} + 1' that does not point to complete object}} \ - // expected-note {{delete of pointer '&new int + 1' that does not point to complete object}} - static_assert((delete[] (new int[3] + 1), true)); // both-error {{}} \ - // both-note {{delete of pointer to subobject}} - static_assert((delete &(int&)(int&&)0, true)); // both-error {{}} \ - // both-note {{delete of pointer '&0' that does not point to a heap-allocated object}} \ - // both-note {{temporary created here}} -} - -namespace value_dependent_delete { - template<typename T> void f(T *p) { - int arr[(delete p, 0)]; - } -} - -namespace memory_leaks { - static_assert(*new bool(true)); // both-error {{}} both-note {{allocation performed here was not deallocated}} - - constexpr bool *f() { return new bool(true); } // both-note {{allocation performed here was not deallocated}} - static_assert(*f()); // both-error {{}} - - struct UP { - bool *p; - constexpr ~UP() { delete p; } - constexpr bool &operator*() { return *p; } - }; - constexpr UP g() { return {new bool(true)}; } - static_assert(*g()); // ok - - constexpr bool h(UP p) { return *p; } - static_assert(h({new bool(true)})); // ok -} - -/// From test/SemaCXX/cxx2a-consteval.cpp - -namespace std { -template <typename T> struct remove_reference { using type = T; }; -template <typename T> struct remove_reference<T &> { using type = T; }; -template <typename T> struct remove_reference<T &&> { using type = T; }; -template <typename T> -constexpr typename std::remove_reference<T>::type&& move(T &&t) noexcept { - return static_cast<typename std::remove_reference<T>::type &&>(t); -} -} - -namespace cxx2a { -struct A { - int* p = new int(42); // both-note 7{{heap allocation performed here}} - consteval int ret_i() const { return p ? *p : 0; } - consteval A ret_a() const { return A{}; } - constexpr ~A() { delete p; } -}; - -consteval int by_value_a(A a) { return a.ret_i(); } - -consteval int const_a_ref(const A &a) { - return a.ret_i(); -} - -consteval int rvalue_ref(const A &&a) { - return a.ret_i(); -} - -consteval const A &to_lvalue_ref(const A &&a) { - return a; -} - -void test() { - constexpr A a{ nullptr }; - { int k = A().ret_i(); } - - { A k = A().ret_a(); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \ - // both-note {{heap-allocated object is not a constant expression}} - { A k = to_lvalue_ref(A()); } // both-error {{'cxx2a::to_lvalue_ref' is not a constant expression}} \ - // both-note {{reference to temporary is not a constant expression}} \ - // both-note {{temporary created here}} - { A k = to_lvalue_ref(A().ret_a()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \ - // both-note {{heap-allocated object is not a constant expression}} \ - // both-error {{'cxx2a::to_lvalue_ref' is not a constant expression}} \ - // both-note {{reference to temporary is not a constant expression}} \ - // both-note {{temporary created here}} - { int k = A().ret_a().ret_i(); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \ - // both-note {{heap-allocated object is not a constant expression}} - { int k = by_value_a(A()); } - { int k = const_a_ref(A()); } - { int k = const_a_ref(a); } - { int k = rvalue_ref(A()); } - { int k = rvalue_ref(std::move(a)); } - { int k = const_a_ref(A().ret_a()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \ - // both-note {{is not a constant expression}} - { int k = const_a_ref(to_lvalue_ref(A().ret_a())); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \ - // both-note {{is not a constant expression}} - { int k = const_a_ref(to_lvalue_ref(std::move(a))); } - { int k = by_value_a(A().ret_a()); } - { int k = by_value_a(to_lvalue_ref(static_cast<const A&&>(a))); } - { int k = (A().ret_a(), A().ret_i()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \ - // both-note {{is not a constant expression}} \ - // both-warning {{left operand of comma operator has no effect}} - { int k = (const_a_ref(A().ret_a()), A().ret_i()); } // both-error {{'cxx2a::A::ret_a' is not a constant expression}} \ - // both-note {{is not a constant expression}} \ - // both-warning {{left operand of comma operator has no effect}} -} -} - -constexpr int *const &p = new int; // both-error {{must be initialized by a constant expression}} \ - // both-note {{pointer to heap-allocated object}} \ - // both-note {{allocation performed here}} - -constexpr const int *A[] = {nullptr, nullptr, new int{12}}; // both-error {{must be initialized by a constant expression}} \ - // both-note {{pointer to heap-allocated object}} \ - // both-note {{allocation performed here}} - -struct Sp { - const int *p; -}; -constexpr Sp ss[] = {Sp{new int{154}}}; // both-error {{must be initialized by a constant expression}} \ - // both-note {{pointer to heap-allocated object}} \ - // both-note {{allocation performed here}} - - - - -#else -/// Make sure we reject this prior to C++20 -constexpr int a() { // both-error {{never produces a constant expression}} - delete new int(12); // both-note 2{{dynamic memory allocation is not permitted in constant expressions until C++20}} - return 1; -} -static_assert(a() == 1, ""); // both-error {{not an integral constant expression}} \ - // both-note {{in call to 'a()'}} -#endif diff --git a/clang/test/Rewriter/rewrite-modern-catch.m b/clang/test/Rewriter/rewrite-modern-catch.m index 621c7ec45bae8..1900301e91129 100644 --- a/clang/test/Rewriter/rewrite-modern-catch.m +++ b/clang/test/Rewriter/rewrite-modern-catch.m @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -x objective-c -Wno-return-type -fblocks -fms-extensions -rewrite-objc %s -o %t-rw.cpp -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -x objective-c -Wno-return-type -fblocks -fms-extensions -rewrite-objc %s -o %t-rw.cpp // RUN: %clang_cc1 -fsyntax-only -fcxx-exceptions -fexceptions -Wno-address-of-temporary -D"id=void*" -D"SEL=void*" -D"__declspec(X)=" %t-rw.cpp void foo(id arg); diff --git a/clang/test/SemaCXX/delete.cpp b/clang/test/SemaCXX/delete.cpp index 7d1f51cb218ce..08cc1766e9f7e 100644 --- a/clang/test/SemaCXX/delete.cpp +++ b/clang/test/SemaCXX/delete.cpp @@ -1,5 +1,5 @@ // Test without PCH -// RUN: %clang_cc1 -fsyntax-only -include %S/delete-mismatch.h -fdiagnostics-parseable-fixits -std=c++11 %s 2>&1 -fexperimental-new-constant-interpreter | FileCheck %s +// RUN: %clang_cc1 -fsyntax-only -include %S/delete-mismatch.h -fdiagnostics-parseable-fixits -std=c++11 %s 2>&1 | FileCheck %s // Test with PCH // RUN: %clang_cc1 -x c++-header -std=c++11 -emit-pch -o %t %S/delete-mismatch.h diff --git a/clang/test/SemaCXX/new-delete.cpp b/clang/test/SemaCXX/new-delete.cpp index 595bdc689d694..ec6ad43476f94 100644 --- a/clang/test/SemaCXX/new-delete.cpp +++ b/clang/test/SemaCXX/new-delete.cpp @@ -6,14 +6,6 @@ // RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++23 // RUN: %clang_cc1 -fsyntax-only -verify=expected,since-cxx26,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++2c -// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++98 -fexperimental-new-constant-interpreter -DNEW_INTERP -// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++11 -fexperimental-new-constant-interpreter -DNEW_INTERP -// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++14 -fexperimental-new-constant-interpreter -DNEW_INTERP -// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++17 -fexperimental-new-constant-interpreter -DNEW_INTERP -// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++20 -fexperimental-new-constant-interpreter -DNEW_INTERP -// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++23 -fexperimental-new-constant-interpreter -DNEW_INTERP -// RUN: %clang_cc1 -fsyntax-only -verify=expected,since-cxx26,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++2c -fexperimental-new-constant-interpreter -DNEW_INTERP - // FIXME Location is (frontend) // cxx17-note@*:* {{candidate function not viable: requires 2 arguments, but 3 were provided}} @@ -661,22 +653,10 @@ int *fail = dependent_array_size("hello"); // expected-note {{instantiation of}} // FIXME: Our behavior here is incredibly inconsistent. GCC allows // constant-folding in array bounds in new-expressions. int (*const_fold)[12] = new int[3][&const_fold + 12 - &const_fold]; -#if __cplusplus >= 201402L && !defined(NEW_INTERP) +#if __cplusplus >= 201402L // expected-error@-2 {{array size is not a constant expression}} // expected-note@-3 {{cannot refer to element 12 of non-array}} -#elif __cplusplus < 201103L && !defined(NEW_INTERP) +#elif __cplusplus < 201103L // expected-error@-5 {{cannot allocate object of variably modified type}} // expected-warning@-6 {{variable length arrays in C++ are a Clang extension}} #endif -#ifdef NEW_INTERP -#if __cplusplus >= 201402L -// expected-error@-10 {{array size is not a constant expression}} -// expected-note@-11 {{cannot refer to element 12 of non-array}} -#elif __cplusplus >= 201103L -// expected-error@-13 {{only the first dimension of an allocated array may have dynamic size}} -// expected-note@-14 {{cannot refer to element 12 of non-array}} -#else -// expected-error@-16 {{only the first dimension of an allocated array may have dynamic size}} -// expected-note@-17 {{cannot refer to element 12 of non-array}} -#endif -#endif _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits