llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Timm Baeder (tbaederr) <details> <summary>Changes</summary> --- Patch is 21.56 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/107672.diff 6 Files Affected: - (modified) clang/lib/AST/ByteCode/DynamicAllocator.cpp (+9-6) - (modified) clang/lib/AST/ByteCode/DynamicAllocator.h (+18-9) - (modified) clang/lib/AST/ByteCode/Interp.cpp (+14-5) - (modified) clang/lib/AST/ByteCode/Interp.h (+21-11) - (modified) clang/lib/AST/ByteCode/InterpBuiltin.cpp (+160) - (modified) clang/test/AST/ByteCode/new-delete.cpp (+112) ``````````diff diff --git a/clang/lib/AST/ByteCode/DynamicAllocator.cpp b/clang/lib/AST/ByteCode/DynamicAllocator.cpp index a5159977407805..819fbdb8b070bf 100644 --- a/clang/lib/AST/ByteCode/DynamicAllocator.cpp +++ b/clang/lib/AST/ByteCode/DynamicAllocator.cpp @@ -40,27 +40,30 @@ void DynamicAllocator::cleanup() { } Block *DynamicAllocator::allocate(const Expr *Source, PrimType T, - size_t NumElements, unsigned EvalID) { + size_t NumElements, unsigned EvalID, + Form AllocForm) { // 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); + return allocate(D, EvalID, AllocForm); } Block *DynamicAllocator::allocate(const Descriptor *ElementDesc, - size_t NumElements, unsigned EvalID) { + size_t NumElements, unsigned EvalID, + Form AllocForm) { // 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); + return allocate(D, EvalID, AllocForm); } -Block *DynamicAllocator::allocate(const Descriptor *D, unsigned EvalID) { +Block *DynamicAllocator::allocate(const Descriptor *D, unsigned EvalID, + Form AllocForm) { assert(D); assert(D->asExpr()); @@ -84,7 +87,7 @@ Block *DynamicAllocator::allocate(const Descriptor *D, unsigned EvalID) { It->second.Allocations.emplace_back(std::move(Memory)); else AllocationSites.insert( - {D->asExpr(), AllocationSite(std::move(Memory), D->isArray())}); + {D->asExpr(), AllocationSite(std::move(Memory), AllocForm)}); return B; } diff --git a/clang/lib/AST/ByteCode/DynamicAllocator.h b/clang/lib/AST/ByteCode/DynamicAllocator.h index a84600aa54cc56..1ed5dc843e4c8c 100644 --- a/clang/lib/AST/ByteCode/DynamicAllocator.h +++ b/clang/lib/AST/ByteCode/DynamicAllocator.h @@ -31,6 +31,14 @@ class InterpState; /// For all array allocations, we need to allocate new Descriptor instances, /// so the DynamicAllocator has a llvm::BumpPtrAllocator similar to Program. class DynamicAllocator final { +public: + enum class Form : uint8_t { + NonArray, + Array, + Operator, + }; + +private: struct Allocation { std::unique_ptr<std::byte[]> Memory; Allocation(std::unique_ptr<std::byte[]> Memory) @@ -39,10 +47,10 @@ class DynamicAllocator final { struct AllocationSite { llvm::SmallVector<Allocation> Allocations; - bool IsArrayAllocation = false; + Form AllocForm; - AllocationSite(std::unique_ptr<std::byte[]> Memory, bool Array) - : IsArrayAllocation(Array) { + AllocationSite(std::unique_ptr<std::byte[]> Memory, Form AllocForm) + : AllocForm(AllocForm) { Allocations.push_back({std::move(Memory)}); } @@ -58,12 +66,13 @@ class DynamicAllocator final { unsigned getNumAllocations() const { return AllocationSites.size(); } /// Allocate ONE element of the given descriptor. - Block *allocate(const Descriptor *D, unsigned EvalID); + Block *allocate(const Descriptor *D, unsigned EvalID, Form AllocForm); /// Allocate \p NumElements primitive elements of the given type. Block *allocate(const Expr *Source, PrimType T, size_t NumElements, - unsigned EvalID); + unsigned EvalID, Form AllocForm); /// Allocate \p NumElements elements of the given descriptor. - Block *allocate(const Descriptor *D, size_t NumElements, unsigned EvalID); + Block *allocate(const Descriptor *D, size_t NumElements, unsigned EvalID, + Form AllocForm); /// Deallocate the given source+block combination. /// Returns \c true if anything has been deallocatd, \c false otherwise. @@ -72,10 +81,10 @@ class DynamicAllocator final { /// Checks whether the allocation done at the given source is an array /// allocation. - bool isArrayAllocation(const Expr *Source) const { + std::optional<Form> getAllocationForm(const Expr *Source) const { if (auto It = AllocationSites.find(Source); It != AllocationSites.end()) - return It->second.IsArrayAllocation; - return false; + return It->second.AllocForm; + return std::nullopt; } /// Allocation site iterator. diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 6777ac150abf4f..108cb2b12ac375 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -811,10 +811,11 @@ bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC) { return true; } -bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray, - bool DeleteIsArray, const Descriptor *D, +bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, + DynamicAllocator::Form AllocForm, + DynamicAllocator::Form DeleteForm, const Descriptor *D, const Expr *NewExpr) { - if (NewWasArray == DeleteIsArray) + if (AllocForm == DeleteForm) return true; QualType TypeToDiagnose; @@ -831,7 +832,8 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray, const SourceInfo &E = S.Current->getSource(OpPC); S.FFDiag(E, diag::note_constexpr_new_delete_mismatch) - << DeleteIsArray << 0 << TypeToDiagnose; + << static_cast<int>(DeleteForm) << static_cast<int>(AllocForm) + << TypeToDiagnose; S.Note(NewExpr->getExprLoc(), diag::note_constexpr_dynamic_alloc_here) << NewExpr->getSourceRange(); return false; @@ -839,7 +841,12 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray, bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source, const Pointer &Ptr) { - if (Source && isa<CXXNewExpr>(Source)) + // The two sources we currently allow are new expressions and + // __builtin_operator_new calls. + if (isa_and_nonnull<CXXNewExpr>(Source)) + return true; + if (const CallExpr *CE = dyn_cast_if_present<CallExpr>(Source); + CE && CE->getBuiltinCallee() == Builtin::BI__builtin_operator_new) return true; // Whatever this is, we didn't heap allocate it. @@ -1165,6 +1172,8 @@ bool CallVirt(InterpState &S, CodePtr OpPC, const Function *Func, bool CallBI(InterpState &S, CodePtr &PC, const Function *Func, const CallExpr *CE) { + if (S.checkingPotentialConstantExpression()) + return false; auto NewFrame = std::make_unique<InterpFrame>(S, Func, PC); InterpFrame *FrameBefore = S.Current; diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index be900769f25845..f83ac93b2b07d4 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -130,8 +130,9 @@ bool CheckNonNullArgs(InterpState &S, CodePtr OpPC, const Function *F, 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, +bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, + DynamicAllocator::Form AllocForm, + DynamicAllocator::Form DeleteForm, const Descriptor *D, const Expr *NewExpr); /// Check the source of the pointer passed to delete/delete[] has actually @@ -2793,10 +2794,11 @@ inline bool Alloc(InterpState &S, CodePtr OpPC, const Descriptor *Desc) { return false; DynamicAllocator &Allocator = S.getAllocator(); - Block *B = Allocator.allocate(Desc, S.Ctx.getEvalID()); + Block *B = Allocator.allocate(Desc, S.Ctx.getEvalID(), + DynamicAllocator::Form::NonArray); assert(B); - S.Stk.push<Pointer>(B, sizeof(InlineDescriptor)); + S.Stk.push<Pointer>(B); return true; } @@ -2818,8 +2820,9 @@ inline bool AllocN(InterpState &S, CodePtr OpPC, PrimType T, const Expr *Source, } DynamicAllocator &Allocator = S.getAllocator(); - Block *B = Allocator.allocate(Source, T, static_cast<size_t>(NumElements), - S.Ctx.getEvalID()); + Block *B = + Allocator.allocate(Source, T, static_cast<size_t>(NumElements), + S.Ctx.getEvalID(), DynamicAllocator::Form::Array); assert(B); S.Stk.push<Pointer>(B, sizeof(InlineDescriptor)); @@ -2844,8 +2847,9 @@ inline bool AllocCN(InterpState &S, CodePtr OpPC, const Descriptor *ElementDesc, } DynamicAllocator &Allocator = S.getAllocator(); - Block *B = Allocator.allocate(ElementDesc, static_cast<size_t>(NumElements), - S.Ctx.getEvalID()); + Block *B = + Allocator.allocate(ElementDesc, static_cast<size_t>(NumElements), + S.Ctx.getEvalID(), DynamicAllocator::Form::Array); assert(B); S.Stk.push<Pointer>(B, sizeof(InlineDescriptor)); @@ -2890,8 +2894,9 @@ static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm) { return false; DynamicAllocator &Allocator = S.getAllocator(); - bool WasArrayAlloc = Allocator.isArrayAllocation(Source); const Descriptor *BlockDesc = BlockToDelete->getDescriptor(); + std::optional<DynamicAllocator::Form> AllocForm = + Allocator.getAllocationForm(Source); if (!Allocator.deallocate(Source, BlockToDelete, S)) { // Nothing has been deallocated, this must be a double-delete. @@ -2899,8 +2904,13 @@ static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm) { S.FFDiag(Loc, diag::note_constexpr_double_delete); return false; } - return CheckNewDeleteForms(S, OpPC, WasArrayAlloc, DeleteIsArrayForm, - BlockDesc, Source); + + assert(AllocForm); + DynamicAllocator::Form DeleteForm = DeleteIsArrayForm + ? DynamicAllocator::Form::Array + : DynamicAllocator::Form::NonArray; + return CheckNewDeleteForms(S, OpPC, *AllocForm, DeleteForm, BlockDesc, + Source); } static inline bool IsConstantContext(InterpState &S, CodePtr OpPC) { diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 81e49f203524b7..49fbaa3cbcb316 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -1244,6 +1244,156 @@ static bool interp__builtin_constant_p(InterpState &S, CodePtr OpPC, return returnInt(false); } +static bool interp__builtin_operator_new(InterpState &S, CodePtr OpPC, + const InterpFrame *Frame, + const Function *Func, + const CallExpr *Call) { + // A call to __operator_new is only valid within std::allocate<>::allocate. + // Walk up the call stack to find the appropriate caller and get the + // element type from it. + QualType ElemType; + + for (const InterpFrame *F = Frame; F; F = F->Caller) { + const Function *Func = F->getFunction(); + if (!Func) + continue; + const auto *MD = dyn_cast_if_present<CXXMethodDecl>(Func->getDecl()); + if (!MD) + continue; + const IdentifierInfo *FnII = MD->getIdentifier(); + if (!FnII || !FnII->isStr("allocate")) + continue; + + const auto *CTSD = + dyn_cast<ClassTemplateSpecializationDecl>(MD->getParent()); + if (!CTSD) + continue; + + const IdentifierInfo *ClassII = CTSD->getIdentifier(); + const TemplateArgumentList &TAL = CTSD->getTemplateArgs(); + if (CTSD->isInStdNamespace() && ClassII && ClassII->isStr("allocator") && + TAL.size() >= 1 && TAL[0].getKind() == TemplateArgument::Type) { + ElemType = TAL[0].getAsType(); + break; + } + } + + if (ElemType.isNull()) { + S.FFDiag(Call, S.getLangOpts().CPlusPlus20 + ? diag::note_constexpr_new_untyped + : diag::note_constexpr_new); + return false; + } + + if (ElemType->isIncompleteType() || ElemType->isFunctionType()) { + S.FFDiag(Call, diag::note_constexpr_new_not_complete_object_type) + << (ElemType->isIncompleteType() ? 0 : 1) << ElemType; + return false; + } + + APSInt Bytes = peekToAPSInt(S.Stk, *S.getContext().classify(Call->getArg(0))); + CharUnits ElemSize = S.getASTContext().getTypeSizeInChars(ElemType); + assert(!ElemSize.isZero()); + // Divide the number of bytes by sizeof(ElemType), so we get the number of + // elements we should allocate. + APInt NumElems, Remainder; + APInt ElemSizeAP(Bytes.getBitWidth(), ElemSize.getQuantity()); + APInt::udivrem(Bytes, ElemSizeAP, NumElems, Remainder); + if (Remainder != 0) { + // This likely indicates a bug in the implementation of 'std::allocator'. + S.FFDiag(Call, diag::note_constexpr_operator_new_bad_size) + << Bytes << APSInt(ElemSizeAP, true) << ElemType; + return false; + } + + // FIXME: CheckArraySize for NumElems? + + std::optional<PrimType> ElemT = S.getContext().classify(ElemType); + DynamicAllocator &Allocator = S.getAllocator(); + if (ElemT) { + if (NumElems.ule(1)) { + const Descriptor *Desc = + S.P.createDescriptor(Call, *ElemT, Descriptor::InlineDescMD, + /*IsConst=*/false, /*IsTemporary=*/false, + /*IsMutable=*/false); + Block *B = Allocator.allocate(Desc, S.getContext().getEvalID(), + DynamicAllocator::Form::Operator); + assert(B); + + S.Stk.push<Pointer>(B); + return true; + } + assert(NumElems.ugt(1)); + + Block *B = + Allocator.allocate(Call, *ElemT, NumElems.getZExtValue(), + S.Ctx.getEvalID(), DynamicAllocator::Form::Operator); + assert(B); + S.Stk.push<Pointer>(B); + return true; + } + + assert(!ElemT); + // Structs etc. + const Descriptor *Desc = S.P.createDescriptor( + Call, ElemType.getTypePtr(), + NumElems.ule(1) ? std::nullopt : Descriptor::InlineDescMD, + /*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, + /*Init=*/nullptr); + + if (NumElems.ule(1)) { + Block *B = Allocator.allocate(Desc, S.getContext().getEvalID(), + DynamicAllocator::Form::Operator); + assert(B); + S.Stk.push<Pointer>(B); + return true; + } + + Block *B = + Allocator.allocate(Desc, NumElems.getZExtValue(), S.Ctx.getEvalID(), + DynamicAllocator::Form::Operator); + assert(B); + S.Stk.push<Pointer>(B); + return true; +} + +static bool interp__builtin_operator_delete(InterpState &S, CodePtr OpPC, + const InterpFrame *Frame, + const Function *Func, + const CallExpr *Call) { + const Expr *Source = nullptr; + const Block *BlockToDelete = nullptr; + + { + const Pointer &Ptr = S.Stk.peek<Pointer>(); + + if (Ptr.isZero()) { + S.CCEDiag(Call, diag::note_constexpr_deallocate_null); + return true; + } + + Source = Ptr.getDeclDesc()->asExpr(); + BlockToDelete = Ptr.block(); + } + assert(BlockToDelete); + + DynamicAllocator &Allocator = S.getAllocator(); + const Descriptor *BlockDesc = BlockToDelete->getDescriptor(); + std::optional<DynamicAllocator::Form> AllocForm = + Allocator.getAllocationForm(Source); + + 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; + } + assert(AllocForm); + + return CheckNewDeleteForms( + S, OpPC, *AllocForm, DynamicAllocator::Form::Operator, BlockDesc, Source); +} + bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F, const CallExpr *Call) { const InterpFrame *Frame = S.Current; @@ -1597,6 +1747,16 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F, pushInteger(S, 0, Call->getType()); break; + case Builtin::BI__builtin_operator_new: + if (!interp__builtin_operator_new(S, OpPC, Frame, F, Call)) + return false; + break; + + case Builtin::BI__builtin_operator_delete: + if (!interp__builtin_operator_delete(S, OpPC, Frame, F, Call)) + return false; + break; + default: S.FFDiag(S.Current->getLocation(OpPC), diag::note_invalid_subexpr_in_const_expr) diff --git a/clang/test/AST/ByteCode/new-delete.cpp b/clang/test/AST/ByteCode/new-delete.cpp index 145bb366710f9b..556efa65ae1181 100644 --- a/clang/test/AST/ByteCode/new-delete.cpp +++ b/clang/test/AST/ByteCode/new-delete.cpp @@ -586,6 +586,118 @@ constexpr void use_after_free_2() { // both-error {{never produces a constant ex p->f(); // both-note {{member call on heap allocated object that has been deleted}} } + +/// std::allocator definition +namespace std { + using size_t = decltype(sizeof(0)); + template<typename T> struct allocator { + constexpr T *allocate(size_t N) { + return (T*)__builtin_operator_new(sizeof(T) * N); // both-note 2{{allocation performed here}} + } + constexpr void deallocate(void *p) { + __builtin_operator_delete(p); // both-note 2{{std::allocator<...>::deallocate' used to delete pointer to object allocated with 'new'}} \ + // both-note {{used to delete a null pointer}} + } + }; +} + +namespace OperatorNewDelete { + + constexpr bool mismatched(int alloc_kind, int dealloc_kind) { + int *p; + + if (alloc_kind == 0) + p = new int; // both-note {{allocation performed here}} + else if (alloc_kind == 1) + p = new int[1]; // both-note {{allocation performed here}} + else if (alloc_kind == 2) + p = std::allocator<int>().allocate(1); + + + if (dealloc_kind == 0) + delete p; // both-note {{'delete' used to delete pointer to object allocated with 'std::allocator<...>::allocate'}} + else if (dealloc_kind == 1) + delete[] p; // both-note {{'delete' used to delete pointer to object allocated with 'std::allocator<...>::allocate'}} + else if (dealloc_kind == 2) + std::allocator<int>().deallocate(p); // both-note 2{{in call to}} + + return true; + } + static_assert(mismatched(0, 2)); // both-error {{constant expression}} \ + // both-note {{in call to}} + static_assert(mismatched(1, 2)); // both-error {{constant expression}} \ + // both-note {{in call to}} + static_assert(mismatched(2, 0)); // both-error {{constant expression}} \ + // both-note {{in call}} + static_assert(mismatched(2, 1)); // both-error {{constant expression}} \ + // both-note {{in call}} + static_assert(mismatched(2, 2)); + + constexpr bool zeroAlloc() { + int *F = std::allocator<int>().allocate(0); + std::allocator<int>().deallocate(F); + return true; + } + static_assert(zeroAlloc()); + + /// FIXME: This is broken in the current interpreter. + constexpr int arrayAlloc() { + int *F = std::allocator<int>().allocate(2); + F[0] = 10; // ref-note {{assignment to object outside its lifetime is not allowed in a constant expression}} + F[1] = 13; + int Res = F[1] + F[0]; + std::allocator<int>().deallocate(F); + return Res; + } + static_assert(arrayAlloc() == 23); // ref-error {{not an integral constant expression}} \ + // ref-note {{in call to}} + + struct S { + int i; + constexpr S(int i) : i(i) {} + constexpr ~S() { } + }; + + /// FIXME: This is broken in the current interpreter. + constexpr bool structAlloc() { + S *s = std::allocator<S>().allocate(1); + + s->i = 12; /... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/107672 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits