https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/70306
>From 8825030bcb7f2249fbc37a9911b8b5a7afc56d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbae...@redhat.com> Date: Wed, 25 Oct 2023 08:33:30 +0200 Subject: [PATCH] [clang][Interp] Implement dynamic memory allocation handling --- clang/lib/AST/CMakeLists.txt | 1 + clang/lib/AST/Interp/Compiler.cpp | 103 +++++ clang/lib/AST/Interp/Compiler.h | 2 + clang/lib/AST/Interp/DynamicAllocator.cpp | 118 +++++ clang/lib/AST/Interp/DynamicAllocator.h | 96 ++++ clang/lib/AST/Interp/EvalEmitter.cpp | 23 + clang/lib/AST/Interp/EvaluationResult.cpp | 72 +++ clang/lib/AST/Interp/EvaluationResult.h | 6 + clang/lib/AST/Interp/Interp.cpp | 52 +++ clang/lib/AST/Interp/Interp.h | 151 +++++++ clang/lib/AST/Interp/InterpBlock.h | 11 +- clang/lib/AST/Interp/InterpState.cpp | 17 + clang/lib/AST/Interp/InterpState.h | 11 + clang/lib/AST/Interp/Opcodes.td | 24 +- clang/lib/AST/Interp/Pointer.h | 1 + clang/test/AST/Interp/new-delete.cpp | 488 +++++++++++++++++++++ clang/test/Rewriter/rewrite-modern-catch.m | 2 +- clang/test/SemaCXX/delete.cpp | 2 +- clang/test/SemaCXX/new-delete.cpp | 24 +- 19 files changed, 1196 insertions(+), 8 deletions(-) create mode 100644 clang/lib/AST/Interp/DynamicAllocator.cpp create mode 100644 clang/lib/AST/Interp/DynamicAllocator.h create mode 100644 clang/test/AST/Interp/new-delete.cpp diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index ceaad8d3c5a86..70aecb781c2ff 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -75,6 +75,7 @@ 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 30dc7f5e4840b..1f43f46c399f1 100644 --- a/clang/lib/AST/Interp/Compiler.cpp +++ b/clang/lib/AST/Interp/Compiler.cpp @@ -2771,6 +2771,109 @@ 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 23e7afd767e88..6df723df2b444 100644 --- a/clang/lib/AST/Interp/Compiler.h +++ b/clang/lib/AST/Interp/Compiler.h @@ -190,6 +190,8 @@ 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 new file mode 100644 index 0000000000000..f6ea264766b13 --- /dev/null +++ b/clang/lib/AST/Interp/DynamicAllocator.cpp @@ -0,0 +1,118 @@ +//==-------- 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 new file mode 100644 index 0000000000000..3a6be28866223 --- /dev/null +++ b/clang/lib/AST/Interp/DynamicAllocator.h @@ -0,0 +1,96 @@ +//==--------- 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 9701796fb9303..968f21ea974c2 100644 --- a/clang/lib/AST/Interp/EvalEmitter.cpp +++ b/clang/lib/AST/Interp/EvalEmitter.cpp @@ -133,9 +133,17 @@ 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; @@ -147,9 +155,14 @@ 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 @@ -172,12 +185,17 @@ 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; } @@ -185,9 +203,14 @@ 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 d0d68f75dd803..0bebfd4ad984e 100644 --- a/clang/lib/AST/Interp/EvaluationResult.cpp +++ b/clang/lib/AST/Interp/EvaluationResult.cpp @@ -10,6 +10,7 @@ #include "InterpState.h" #include "Record.h" #include "clang/AST/ExprCXX.h" +#include "llvm/ADT/SetVector.h" namespace clang { namespace interp { @@ -152,6 +153,11 @@ 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(); @@ -168,5 +174,71 @@ 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 378f1ccdb0af4..ef662e3779bc3 100644 --- a/clang/lib/AST/Interp/EvaluationResult.h +++ b/clang/lib/AST/Interp/EvaluationResult.h @@ -98,7 +98,12 @@ 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 = @@ -113,6 +118,7 @@ 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 70a470021e7f2..cafe2175f5cc4 100644 --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -693,6 +693,58 @@ 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 c7d8604c7dc2a..955b002c3b3f4 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -15,6 +15,7 @@ #include "../ExprConstShared.h" #include "Boolean.h" +#include "DynamicAllocator.h" #include "Floating.h" #include "Function.h" #include "FunctionPointer.h" @@ -122,6 +123,20 @@ 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, @@ -189,6 +204,30 @@ 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, @@ -2767,6 +2806,118 @@ 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 1f25de3589630..e86f411704479 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), - Desc(Desc) { + IsDynamic(false), 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), Desc(Desc) { + IsExtern(IsExtern), IsDynamic(false), Desc(Desc) { assert(Desc); } @@ -73,6 +73,7 @@ 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. @@ -135,11 +136,12 @@ 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), - Desc(Desc) { + IsDynamic(false), Desc(Desc) { assert(Desc); } @@ -169,6 +171,9 @@ 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 a8538541f4915..fa4d220299e46 100644 --- a/clang/lib/AST/Interp/InterpState.cpp +++ b/clang/lib/AST/Interp/InterpState.cpp @@ -41,6 +41,8 @@ void InterpState::cleanup() { P->PointeeStorage.BS.Pointee = nullptr; } } + + Alloc.cleanup(); } Frame *InterpState::getCurrentFrame() { @@ -81,3 +83,18 @@ 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 138e1d7ac95d5..61ee54331c65d 100644 --- a/clang/lib/AST/Interp/InterpState.h +++ b/clang/lib/AST/Interp/InterpState.h @@ -14,6 +14,7 @@ #define LLVM_CLANG_AST_INTERP_INTERPSTATE_H #include "Context.h" +#include "DynamicAllocator.h" #include "Function.h" #include "InterpFrame.h" #include "InterpStack.h" @@ -102,13 +103,23 @@ 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 8d01fe1ac2bd1..3e69098570bd7 100644 --- a/clang/lib/AST/Interp/Opcodes.td +++ b/clang/lib/AST/Interp/Opcodes.td @@ -58,12 +58,14 @@ 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. @@ -747,3 +749,23 @@ 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 6e9e8675306ef..2beb83590dde5 100644 --- a/clang/lib/AST/Interp/Pointer.h +++ b/clang/lib/AST/Interp/Pointer.h @@ -637,6 +637,7 @@ 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 new file mode 100644 index 0000000000000..744b142492924 --- /dev/null +++ b/clang/test/AST/Interp/new-delete.cpp @@ -0,0 +1,488 @@ +// 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 1900301e91129..621c7ec45bae8 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 +// 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 -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 08cc1766e9f7e..7d1f51cb218ce 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 | FileCheck %s +// 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 // 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 ec6ad43476f94..595bdc689d694 100644 --- a/clang/test/SemaCXX/new-delete.cpp +++ b/clang/test/SemaCXX/new-delete.cpp @@ -6,6 +6,14 @@ // 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}} @@ -653,10 +661,22 @@ 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 +#if __cplusplus >= 201402L && !defined(NEW_INTERP) // expected-error@-2 {{array size is not a constant expression}} // expected-note@-3 {{cannot refer to element 12 of non-array}} -#elif __cplusplus < 201103L +#elif __cplusplus < 201103L && !defined(NEW_INTERP) // 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