Author: rsmith Date: Wed Oct 2 17:39:33 2019 New Revision: 373546 URL: http://llvm.org/viewvc/llvm-project?rev=373546&view=rev Log: For P0784R7: allow direct calls to operator new / operator delete from std::allocator::{allocate,deallocate} in constant evaluation.
Added: cfe/trunk/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp Modified: cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td cfe/trunk/lib/AST/ExprConstant.cpp Modified: cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td?rev=373546&r1=373545&r2=373546&view=diff ============================================================================== --- cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td (original) +++ cfe/trunk/include/clang/Basic/DiagnosticASTKinds.td Wed Oct 2 17:39:33 2019 @@ -272,6 +272,13 @@ def note_constexpr_new_too_large : Note< def note_constexpr_new_too_small : Note< "cannot allocate array; evaluated array bound %0 is too small to hold " "%1 explicitly initialized elements">; +def note_constexpr_new_untyped : Note< + "cannot allocate untyped memory in a constant expression; " + "use 'std::allocator<T>::allocate' to allocate memory of type 'T'">; +def note_constexpr_new_not_complete_object_type : Note< + "cannot allocate memory of %select{incomplete|function}0 type %1">; +def note_constexpr_operator_new_bad_size : Note< + "allocated size %0 is not a multiple of size %1 of element type %2">; def note_constexpr_delete_not_heap_alloc : Note< "delete of pointer '%0' that does not point to a heap-allocated object">; def note_constexpr_double_delete : Note< @@ -279,8 +286,12 @@ def note_constexpr_double_delete : Note< def note_constexpr_double_destroy : Note< "destruction of object that is already being destroyed">; def note_constexpr_new_delete_mismatch : Note< - "%select{non-|}0array delete used to delete pointer to " - "%select{|non-}0array object of type %1">; + "%plural{2:'delete' used to delete pointer to object " + "allocated with 'std::allocator<...>::allocate'|" + ":%select{non-array delete|array delete|'std::allocator<...>::deallocate'}0 " + "used to delete pointer to " + "%select{array object of type %2|non-array object of type %2|" + "object allocated with 'new'}0}1">; def note_constexpr_delete_subobject : Note< "delete of pointer%select{ to subobject|}1 '%0' " "%select{|that does not point to complete object}1">; Modified: cfe/trunk/lib/AST/ExprConstant.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/ExprConstant.cpp?rev=373546&r1=373545&r2=373546&view=diff ============================================================================== --- cfe/trunk/lib/AST/ExprConstant.cpp (original) +++ cfe/trunk/lib/AST/ExprConstant.cpp Wed Oct 2 17:39:33 2019 @@ -690,6 +690,37 @@ template<> struct DenseMapInfo<ObjectUnd } namespace { + /// A dynamically-allocated heap object. + struct DynAlloc { + /// The value of this heap-allocated object. + APValue Value; + /// The allocating expression; used for diagnostics. Either a CXXNewExpr + /// or a CallExpr (the latter is for direct calls to operator new inside + /// std::allocator<T>::allocate). + const Expr *AllocExpr = nullptr; + + enum Kind { + New, + ArrayNew, + StdAllocator + }; + + /// Get the kind of the allocation. This must match between allocation + /// and deallocation. + Kind getKind() const { + if (auto *NE = dyn_cast<CXXNewExpr>(AllocExpr)) + return NE->isArray() ? ArrayNew : New; + assert(isa<CallExpr>(AllocExpr)); + return StdAllocator; + } + }; + + struct DynAllocOrder { + bool operator()(DynamicAllocLValue L, DynamicAllocLValue R) const { + return L.getIndex() < R.getIndex(); + } + }; + /// EvalInfo - This is a private struct used by the evaluator to capture /// information about a subexpression as it is folded. It retains information /// about the AST context, but also maintains information about the folded @@ -761,20 +792,6 @@ namespace { llvm::DenseMap<ObjectUnderConstruction, ConstructionPhase> ObjectsUnderConstruction; - /// A dynamically-allocated heap object. - struct DynAlloc { - /// The value of this heap-allocated object. - APValue Value; - /// The allocating expression; used for diagnostics. - const Expr *AllocExpr = nullptr; - }; - - struct DynAllocOrder { - bool operator()(DynamicAllocLValue L, DynamicAllocLValue R) const { - return L.getIndex() < R.getIndex(); - } - }; - /// Current heap allocations, along with the location where each was /// allocated. We use std::map here because we need stable addresses /// for the stored APValues. @@ -970,6 +987,39 @@ namespace { return Result; } + /// Information about a stack frame for std::allocator<T>::[de]allocate. + struct StdAllocatorCaller { + unsigned FrameIndex; + QualType ElemType; + explicit operator bool() const { return FrameIndex != 0; }; + }; + + StdAllocatorCaller getStdAllocatorCaller(StringRef FnName) const { + for (const CallStackFrame *Call = CurrentCall; Call != &BottomFrame; + Call = Call->Caller) { + const auto *MD = dyn_cast_or_null<CXXMethodDecl>(Call->Callee); + if (!MD) + continue; + const IdentifierInfo *FnII = MD->getIdentifier(); + if (!FnII || !FnII->isStr(FnName)) + 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) + return {Call->Index, TAL[0].getAsType()}; + } + + return {}; + } + void performLifetimeExtension() { // Disable the cleanups for lifetime-extended temporaries. CleanupStack.erase( @@ -1453,9 +1503,10 @@ namespace { IsNullPtr = false; } - void setNull(QualType PointerTy, uint64_t TargetVal) { + void setNull(ASTContext &Ctx, QualType PointerTy) { Base = (Expr *)nullptr; - Offset = CharUnits::fromQuantity(TargetVal); + Offset = + CharUnits::fromQuantity(Ctx.getTargetNullPointerValue(PointerTy)); InvalidBase = false; Designator = SubobjectDesignator(PointerTy->getPointeeType()); IsNullPtr = true; @@ -1465,6 +1516,12 @@ namespace { set(B, true); } + std::string toString(ASTContext &Ctx, QualType T) const { + APValue Printable; + moveInto(Printable); + return Printable.getAsString(Ctx, T); + } + private: // Check that this LValue is not based on a null pointer. If it is, produce // a diagnostic and mark the designator as invalid. @@ -1905,7 +1962,7 @@ static void NoteLValueLocation(EvalInfo Info.Note(E->getExprLoc(), diag::note_constexpr_temporary_here); else if (DynamicAllocLValue DA = Base.dyn_cast<DynamicAllocLValue>()) { // FIXME: Produce a note for dangling pointers too. - if (Optional<EvalInfo::DynAlloc*> Alloc = Info.lookupDynamicAlloc(DA)) + if (Optional<DynAlloc*> Alloc = Info.lookupDynamicAlloc(DA)) Info.Note((*Alloc)->AllocExpr->getExprLoc(), diag::note_constexpr_dynamic_alloc_here); } @@ -3560,7 +3617,7 @@ static CompleteObject findCompleteObject if (!evaluateVarDeclInit(Info, E, VD, Frame, BaseVal, &LVal)) return CompleteObject(); } else if (DynamicAllocLValue DA = LVal.Base.dyn_cast<DynamicAllocLValue>()) { - Optional<EvalInfo::DynAlloc*> Alloc = Info.lookupDynamicAlloc(DA); + Optional<DynAlloc*> Alloc = Info.lookupDynamicAlloc(DA); if (!Alloc) { Info.FFDiag(E, diag::note_constexpr_access_deleted_object) << AK; return CompleteObject(); @@ -5147,8 +5204,7 @@ static bool HandleDynamicCast(EvalInfo & if (!E->isGLValue()) { // The value of a failed cast to pointer type is the null pointer value // of the required result type. - auto TargetVal = Info.Ctx.getTargetNullPointerValue(E->getType()); - Ptr.setNull(E->getType(), TargetVal); + Ptr.setNull(Info.Ctx, E->getType()); return true; } @@ -5878,6 +5934,161 @@ static bool HandleDestruction(EvalInfo & return HandleDestructionImpl(Info, Loc, LV, Value, T); } +/// Perform a call to 'perator new' or to `__builtin_operator_new'. +static bool HandleOperatorNewCall(EvalInfo &Info, const CallExpr *E, + LValue &Result) { + if (Info.checkingPotentialConstantExpression() || + Info.SpeculativeEvaluationDepth) + return false; + + // This is permitted only within a call to std::allocator<T>::allocate. + auto Caller = Info.getStdAllocatorCaller("allocate"); + if (!Caller) { + Info.FFDiag(E->getExprLoc(), Info.getLangOpts().CPlusPlus2a + ? diag::note_constexpr_new_untyped + : diag::note_constexpr_new); + return false; + } + + QualType ElemType = Caller.ElemType; + if (ElemType->isIncompleteType() || ElemType->isFunctionType()) { + Info.FFDiag(E->getExprLoc(), + diag::note_constexpr_new_not_complete_object_type) + << (ElemType->isIncompleteType() ? 0 : 1) << ElemType; + return false; + } + + APSInt ByteSize; + if (!EvaluateInteger(E->getArg(0), ByteSize, Info)) + return false; + bool IsNothrow = false; + for (unsigned I = 1, N = E->getNumArgs(); I != N; ++I) { + EvaluateIgnoredValue(Info, E->getArg(I)); + IsNothrow |= E->getType()->isNothrowT(); + } + + CharUnits ElemSize; + if (!HandleSizeof(Info, E->getExprLoc(), ElemType, ElemSize)) + return false; + APInt Size, Remainder; + APInt ElemSizeAP(ByteSize.getBitWidth(), ElemSize.getQuantity()); + APInt::udivrem(ByteSize, ElemSizeAP, Size, Remainder); + if (Remainder != 0) { + // This likely indicates a bug in the implementation of 'std::allocator'. + Info.FFDiag(E->getExprLoc(), diag::note_constexpr_operator_new_bad_size) + << ByteSize << APSInt(ElemSizeAP, true) << ElemType; + return false; + } + + if (ByteSize.getActiveBits() > ConstantArrayType::getMaxSizeBits(Info.Ctx)) { + if (IsNothrow) { + Result.setNull(Info.Ctx, E->getType()); + return true; + } + + Info.FFDiag(E, diag::note_constexpr_new_too_large) << APSInt(Size, true); + return false; + } + + QualType AllocType = + Info.Ctx.getConstantArrayType(ElemType, Size, ArrayType::Normal, 0); + APValue *Val = Info.createHeapAlloc(E, AllocType, Result); + *Val = APValue(APValue::UninitArray(), 0, Size.getZExtValue()); + Result.addArray(Info, E, cast<ConstantArrayType>(AllocType)); + return true; +} + +static bool hasVirtualDestructor(QualType T) { + if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) + if (CXXDestructorDecl *DD = RD->getDestructor()) + return DD->isVirtual(); + return false; +} + +/// Check that the given object is a suitable pointer to a heap allocation that +/// still exists and is of the right kind for the purpose of a deletion. +/// +/// On success, returns the heap allocation to deallocate. On failure, produces +/// a diagnostic and returns None. +static Optional<DynAlloc *> CheckDeleteKind(EvalInfo &Info, const Expr *E, + const LValue &Pointer, + DynAlloc::Kind DeallocKind) { + auto PointerAsString = [&] { + return Pointer.toString(Info.Ctx, Info.Ctx.VoidPtrTy); + }; + + DynamicAllocLValue DA = Pointer.Base.dyn_cast<DynamicAllocLValue>(); + if (!DA) { + Info.FFDiag(E, diag::note_constexpr_delete_not_heap_alloc) + << PointerAsString(); + if (Pointer.Base) + NoteLValueLocation(Info, Pointer.Base); + return None; + } + + Optional<DynAlloc *> Alloc = Info.lookupDynamicAlloc(DA); + if (!Alloc) { + Info.FFDiag(E, diag::note_constexpr_double_delete); + return None; + } + + QualType AllocType = Pointer.Base.getDynamicAllocType(); + if (DeallocKind != (*Alloc)->getKind()) { + Info.FFDiag(E, diag::note_constexpr_new_delete_mismatch) + << DeallocKind << (*Alloc)->getKind() << AllocType; + NoteLValueLocation(Info, Pointer.Base); + return None; + } + + bool Subobject = false; + if (DeallocKind == DynAlloc::New) { + Subobject = Pointer.Designator.MostDerivedPathLength != 0 || + Pointer.Designator.isOnePastTheEnd(); + } else { + Subobject = Pointer.Designator.Entries.size() != 1 || + Pointer.Designator.Entries[0].getAsArrayIndex() != 0; + } + if (Subobject) { + Info.FFDiag(E, diag::note_constexpr_delete_subobject) + << PointerAsString() << Pointer.Designator.isOnePastTheEnd(); + return None; + } + + return Alloc; +} + +// Perform a call to 'operator delete' or '__builtin_operator_delete'. +bool HandleOperatorDeleteCall(EvalInfo &Info, const CallExpr *E) { + if (Info.checkingPotentialConstantExpression() || + Info.SpeculativeEvaluationDepth) + return false; + + // This is permitted only within a call to std::allocator<T>::deallocate. + if (!Info.getStdAllocatorCaller("deallocate")) { + Info.FFDiag(E->getExprLoc()); + return true; + } + + LValue Pointer; + if (!EvaluatePointer(E->getArg(0), Pointer, Info)) + return false; + for (unsigned I = 1, N = E->getNumArgs(); I != N; ++I) + EvaluateIgnoredValue(Info, E->getArg(I)); + + if (Pointer.Designator.Invalid) + return false; + + // Deleting a null pointer has no effect. + if (Pointer.isNullPointer()) + return true; + + if (!CheckDeleteKind(Info, E, Pointer, DynAlloc::StdAllocator)) + return false; + + Info.HeapAllocs.erase(Pointer.Base.get<DynamicAllocLValue>()); + return true; +} + //===----------------------------------------------------------------------===// // Generic Evaluation //===----------------------------------------------------------------------===// @@ -6700,6 +6911,17 @@ public: FD = cast<CXXMethodDecl>(CorrespondingCallOpSpecialization); } else FD = LambdaCallOp; + } else if (FD->isReplaceableGlobalAllocationFunction()) { + if (FD->getDeclName().getCXXOverloadedOperator() == OO_New || + FD->getDeclName().getCXXOverloadedOperator() == OO_Array_New) { + LValue Ptr; + if (!HandleOperatorNewCall(Info, E, Ptr)) + return false; + Ptr.moveInto(Result); + return true; + } else { + return HandleOperatorDeleteCall(Info, E); + } } } else return Error(E); @@ -7565,8 +7787,7 @@ public: return true; } bool ZeroInitialization(const Expr *E) { - auto TargetVal = Info.Ctx.getTargetNullPointerValue(E->getType()); - Result.setNull(E->getType(), TargetVal); + Result.setNull(Info.Ctx, E->getType()); return true; } @@ -7693,12 +7914,22 @@ bool PointerExprEvaluator::VisitCastExpr // permitted in constant expressions in C++11. Bitcasts from cv void* are // also static_casts, but we disallow them as a resolution to DR1312. if (!E->getType()->isVoidPointerType()) { - Result.Designator.setInvalid(); - if (SubExpr->getType()->isVoidPointerType()) - CCEDiag(E, diag::note_constexpr_invalid_cast) - << 3 << SubExpr->getType(); - else - CCEDiag(E, diag::note_constexpr_invalid_cast) << 2; + if (!Result.InvalidBase && !Result.Designator.Invalid && + !Result.IsNullPtr && + Info.Ctx.hasSameUnqualifiedType(Result.Designator.getType(Info.Ctx), + E->getType()->getPointeeType()) && + Info.getStdAllocatorCaller("allocate")) { + // Inside a call to std::allocator::allocate and friends, we permit + // casting from void* back to cv1 T* for a pointer that points to a + // cv2 T. + } else { + Result.Designator.setInvalid(); + if (SubExpr->getType()->isVoidPointerType()) + CCEDiag(E, diag::note_constexpr_invalid_cast) + << 3 << SubExpr->getType(); + else + CCEDiag(E, diag::note_constexpr_invalid_cast) << 2; + } } if (E->getCastKind() == CK_AddressSpaceConversion && Result.IsNullPtr) ZeroInitialization(E); @@ -7935,6 +8166,8 @@ bool PointerExprEvaluator::VisitBuiltinC return true; } + case Builtin::BI__builtin_operator_new: + return HandleOperatorNewCall(Info, E, Result); case Builtin::BI__builtin_launder: return evaluatePointer(E->getArg(0), Result); case Builtin::BIstrchr: @@ -8186,8 +8419,10 @@ bool PointerExprEvaluator::VisitBuiltinC } default: - return visitNonBuiltinCallExpr(E); + break; } + + return visitNonBuiltinCallExpr(E); } static bool EvaluateArrayNewInitList(EvalInfo &Info, LValue &This, @@ -12838,26 +13073,25 @@ public: bool VisitCallExpr(const CallExpr *E) { switch (E->getBuiltinCallee()) { - default: - return ExprEvaluatorBaseTy::VisitCallExpr(E); case Builtin::BI__assume: case Builtin::BI__builtin_assume: // The argument is not evaluated! return true; + + case Builtin::BI__builtin_operator_delete: + return HandleOperatorDeleteCall(Info, E); + + default: + break; } + + return ExprEvaluatorBaseTy::VisitCallExpr(E); } bool VisitCXXDeleteExpr(const CXXDeleteExpr *E); }; } // end anonymous namespace -static bool hasVirtualDestructor(QualType T) { - if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) - if (CXXDestructorDecl *DD = RD->getDestructor()) - return DD->isVirtual(); - return false; -} - bool VoidExprEvaluator::VisitCXXDeleteExpr(const CXXDeleteExpr *E) { // We cannot speculatively evaluate a delete expression. if (Info.SpeculativeEvaluationDepth) @@ -12888,49 +13122,12 @@ bool VoidExprEvaluator::VisitCXXDeleteEx return true; } - auto PointerAsString = [&] { - APValue Printable; - Pointer.moveInto(Printable); - return Printable.getAsString(Info.Ctx, Arg->getType()); - }; - - DynamicAllocLValue DA = Pointer.Base.dyn_cast<DynamicAllocLValue>(); - if (!DA) { - Info.FFDiag(E, diag::note_constexpr_delete_not_heap_alloc) - << PointerAsString(); - if (Pointer.Base) - NoteLValueLocation(Info, Pointer.Base); + Optional<DynAlloc *> Alloc = CheckDeleteKind( + Info, E, Pointer, E->isArrayForm() ? DynAlloc::ArrayNew : DynAlloc::New); + if (!Alloc) return false; - } QualType AllocType = Pointer.Base.getDynamicAllocType(); - Optional<EvalInfo::DynAlloc*> Alloc = Info.lookupDynamicAlloc(DA); - if (!Alloc) { - Info.FFDiag(E, diag::note_constexpr_double_delete); - return false; - } - - if (E->isArrayForm() != AllocType->isConstantArrayType()) { - Info.FFDiag(E, diag::note_constexpr_new_delete_mismatch) - << E->isArrayForm() << AllocType; - NoteLValueLocation(Info, Pointer.Base); - return false; - } - - bool Subobject = false; - if (E->isArrayForm()) { - Subobject = Pointer.Designator.Entries.size() != 1 || - Pointer.Designator.Entries[0].getAsArrayIndex() != 0; - } else { - Subobject = Pointer.Designator.MostDerivedPathLength != 0 || - Pointer.Designator.isOnePastTheEnd(); - } - if (Subobject) { - Info.FFDiag(E, diag::note_constexpr_delete_subobject) - << PointerAsString() << Pointer.Designator.isOnePastTheEnd(); - return false; - } - // For the non-array case, the designator must be empty if the static type // does not have a virtual destructor. if (!E->isArrayForm() && Pointer.Designator.Entries.size() != 0 && @@ -12944,7 +13141,7 @@ bool VoidExprEvaluator::VisitCXXDeleteEx (*Alloc)->Value, AllocType)) return false; - if (!Info.HeapAllocs.erase(DA)) { + if (!Info.HeapAllocs.erase(Pointer.Base.dyn_cast<DynamicAllocLValue>())) { // The element was already erased. This means the destructor call also // deleted the object. // FIXME: This probably results in undefined behavior before we get this Added: cfe/trunk/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp?rev=373546&view=auto ============================================================================== --- cfe/trunk/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp (added) +++ cfe/trunk/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp Wed Oct 2 17:39:33 2019 @@ -0,0 +1,85 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s -DNEW=__builtin_operator_new -DDELETE=__builtin_operator_delete +// RUN: %clang_cc1 -std=c++2a -verify %s "-DNEW=operator new" "-DDELETE=operator delete" +// RUN: %clang_cc1 -std=c++2a -verify %s "-DNEW=::operator new" "-DDELETE=::operator delete" + +constexpr bool alloc_from_user_code() { + void *p = NEW(sizeof(int)); // expected-note {{cannot allocate untyped memory in a constant expression; use 'std::allocator<T>::allocate'}} + DELETE(p); + return true; +} +static_assert(alloc_from_user_code()); // expected-error {{constant expression}} expected-note {{in call}} + +namespace std { + using size_t = decltype(sizeof(0)); + // FIXME: It would be preferable to point these notes at the location of the call to allocator<...>::[de]allocate instead + template<typename T> struct allocator { + constexpr T *allocate(size_t N) { + return (T*)NEW(sizeof(T) * N); // expected-note 3{{heap allocation}} expected-note {{not deallocated}} + } + constexpr void deallocate(void *p) { + DELETE(p); // expected-note 2{{'std::allocator<...>::deallocate' used to delete pointer to object allocated with 'new'}} + } + }; +} + +constexpr bool alloc_via_std_allocator() { + std::allocator<int> alloc; + int *p = alloc.allocate(1); + alloc.deallocate(p); + return true; +} +static_assert(alloc_via_std_allocator()); + +template<> struct std::allocator<void()> { + constexpr void *allocate() { return NEW(8); } // expected-note {{cannot allocate memory of function type 'void ()'}} +}; +constexpr void *fn = std::allocator<void()>().allocate(); // expected-error {{constant expression}} expected-note {{in call}} + +struct Incomplete; +template<> struct std::allocator<Incomplete> { + constexpr void *allocate() { return NEW(8); } // expected-note {{cannot allocate memory of incomplete type 'Incomplete'}} +}; +constexpr void *incomplete = std::allocator<Incomplete>().allocate(); // expected-error {{constant expression}} expected-note {{in call}} + +struct WrongSize { char x[5]; }; +static_assert(sizeof(WrongSize) == 5); +template<> struct std::allocator<WrongSize> { + constexpr void *allocate() { return NEW(7); } // expected-note {{allocated size 7 is not a multiple of size 5 of element type 'WrongSize'}} +}; +constexpr void *wrong_size = std::allocator<WrongSize>().allocate(); // expected-error {{constant expression}} expected-note {{in call}} + +constexpr bool mismatched(int alloc_kind, int dealloc_kind) { + int *p; + switch (alloc_kind) { + case 0: + p = new int; // expected-note {{heap allocation}} + break; + case 1: + p = new int[1]; // expected-note {{heap allocation}} + break; + case 2: + p = std::allocator<int>().allocate(1); + break; + } + switch (dealloc_kind) { + case 0: + delete p; // expected-note {{'delete' used to delete pointer to object allocated with 'std::allocator<...>::allocate'}} + break; + case 1: + delete[] p; // expected-note {{'delete' used to delete pointer to object allocated with 'std::allocator<...>::allocate'}} + break; + case 2: + std::allocator<int>().deallocate(p); // expected-note 2{{in call}} + break; + } + return true; +} +static_assert(mismatched(0, 2)); // expected-error {{constant expression}} expected-note {{in call}} +static_assert(mismatched(1, 2)); // expected-error {{constant expression}} expected-note {{in call}} +static_assert(mismatched(2, 0)); // expected-error {{constant expression}} expected-note {{in call}} +static_assert(mismatched(2, 1)); // expected-error {{constant expression}} expected-note {{in call}} +static_assert(mismatched(2, 2)); + +constexpr int *escape = std::allocator<int>().allocate(3); // expected-error {{constant expression}} expected-note {{pointer to subobject of heap-allocated}} +constexpr int leak = (std::allocator<int>().allocate(3), 0); // expected-error {{constant expression}} +constexpr int no_lifetime_start = (*std::allocator<int>().allocate(1) = 1); // expected-error {{constant expression}} expected-note {{assignment to object outside its lifetime}} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits