From e9401b10f5a8f2d6e40aba10304a66daafa2e610 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 27 Sep 2019 01:26:47 +0000 Subject: [PATCH] For P0784R7: Add support for dynamic allocation with new / delete during constant evaluation. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@373036 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/AST/APValue.h | 46 +- include/clang/Basic/DiagnosticASTKinds.td | 44 ++ lib/AST/APValue.cpp | 34 +- lib/AST/ExprCXX.cpp | 6 + lib/AST/ExprConstant.cpp | 489 ++++++++++++++++++--- lib/Sema/SemaDeclCXX.cpp | 6 + test/CXX/expr/expr.const/p2-0x.cpp | 33 +- test/CodeGenCXX/const-init-cxx2a.cpp | 5 + test/SemaCXX/builtin-object-size-cxx14.cpp | 7 + test/SemaCXX/constant-expression-cxx2a.cpp | 304 ++++++++++++- 10 files changed, 901 insertions(+), 73 deletions(-) create mode 100644 test/CodeGenCXX/const-init-cxx2a.cpp diff --git a/include/clang/AST/APValue.h b/include/clang/AST/APValue.h index 6943479831..63359294ef 100644 --- a/include/clang/AST/APValue.h +++ b/include/clang/AST/APValue.h @@ -53,6 +53,34 @@ public: void print(llvm::raw_ostream &Out, const PrintingPolicy &Policy) const; }; + +/// Symbolic representation of a dynamic allocation. +class DynamicAllocLValue { + unsigned Index; + +public: + DynamicAllocLValue() : Index(0) {} + explicit DynamicAllocLValue(unsigned Index) : Index(Index + 1) {} + unsigned getIndex() { return Index - 1; } + + explicit operator bool() const { return Index != 0; } + + void *getOpaqueValue() { + return reinterpret_cast(static_cast(Index) + << NumLowBitsAvailable); + } + static DynamicAllocLValue getFromOpaqueValue(void *Value) { + DynamicAllocLValue V; + V.Index = reinterpret_cast(Value) >> NumLowBitsAvailable; + return V; + } + + static unsigned getMaxIndex() { + return (std::numeric_limits::max() >> NumLowBitsAvailable) - 1; + } + + static constexpr int NumLowBitsAvailable = 3; +}; } namespace llvm { @@ -67,6 +95,17 @@ template<> struct PointerLikeTypeTraits { // to include Type.h. static constexpr int NumLowBitsAvailable = 3; }; + +template<> struct PointerLikeTypeTraits { + static void *getAsVoidPointer(clang::DynamicAllocLValue V) { + return V.getOpaqueValue(); + } + static clang::DynamicAllocLValue getFromVoidPointer(void *P) { + return clang::DynamicAllocLValue::getFromOpaqueValue(P); + } + static constexpr int NumLowBitsAvailable = + clang::DynamicAllocLValue::NumLowBitsAvailable; +}; } namespace clang { @@ -97,13 +136,15 @@ public: }; class LValueBase { - typedef llvm::PointerUnion + typedef llvm::PointerUnion PtrTy; public: LValueBase() : Local{} {} LValueBase(const ValueDecl *P, unsigned I = 0, unsigned V = 0); LValueBase(const Expr *P, unsigned I = 0, unsigned V = 0); + static LValueBase getDynamicAlloc(DynamicAllocLValue LV, QualType Type); static LValueBase getTypeInfo(TypeInfoLValue LV, QualType TypeInfo); template @@ -124,6 +165,7 @@ public: unsigned getCallIndex() const; unsigned getVersion() const; QualType getTypeInfoType() const; + QualType getDynamicAllocType() const; friend bool operator==(const LValueBase &LHS, const LValueBase &RHS); friend bool operator!=(const LValueBase &LHS, const LValueBase &RHS) { @@ -140,6 +182,8 @@ public: LocalState Local; /// The type std::type_info, if this is a TypeInfoLValue. void *TypeInfoType; + /// The QualType, if this is a DynamicAllocLValue. + void *DynamicAllocType; }; }; diff --git a/include/clang/Basic/DiagnosticASTKinds.td b/include/clang/Basic/DiagnosticASTKinds.td index f11cd75afa..5e38b3a190 100644 --- a/include/clang/Basic/DiagnosticASTKinds.td +++ b/include/clang/Basic/DiagnosticASTKinds.td @@ -53,6 +53,9 @@ def note_constexpr_nonliteral : Note< def note_constexpr_non_global : Note< "%select{pointer|reference}0 to %select{|subobject of }1" "%select{temporary|%3}2 is not a constant expression">; +def note_constexpr_dynamic_alloc : Note< + "%select{pointer|reference}0 to %select{|subobject of }1" + "heap-allocated object is not a constant expression">; def note_constexpr_uninitialized : Note< "%select{|sub}0object of type %1 is not initialized">; def note_constexpr_subobject_declared_here : Note< @@ -100,6 +103,7 @@ def note_constexpr_typeid_polymorphic : Note< def note_constexpr_void_comparison : Note< "comparison between unequal pointers to void has unspecified result">; def note_constexpr_temporary_here : Note<"temporary created here">; +def note_constexpr_dynamic_alloc_here : Note<"heap allocation performed here">; def note_constexpr_conditional_never_const : Note< "both arms of conditional operator are unable to produce a " "constant expression">; @@ -109,6 +113,8 @@ def note_constexpr_call_limit_exceeded : Note< "constexpr evaluation hit maximum call limit">; def note_constexpr_step_limit_exceeded : Note< "constexpr evaluation hit maximum step limit; possible infinite loop?">; +def note_constexpr_heap_alloc_limit_exceeded : Note< + "constexpr evaluation hit maximum heap allocation limit">; def note_constexpr_this : Note< "%select{|implicit }0use of 'this' pointer is only allowed within the " "evaluation of a call to a 'constexpr' member function">; @@ -174,6 +180,10 @@ def note_constexpr_access_unreadable_object : Note< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to}0 object '%1' " "whose value is not known">; +def note_constexpr_access_deleted_object : Note< + "%select{read of|read of|assignment to|increment of|decrement of|" + "member call on|dynamic_cast of|typeid applied to}0 heap allocated " + "object that has been deleted">; def note_constexpr_modify_global : Note< "a constant expression cannot modify an object that is visible outside " "that expression">; @@ -193,6 +203,8 @@ def note_constexpr_baa_insufficient_alignment : Note< def note_constexpr_baa_value_insufficient_alignment : Note< "value of the aligned pointer (%0) is not a multiple of the asserted %1 " "%plural{1:byte|:bytes}1">; +def note_constexpr_destroy_out_of_lifetime : Note< + "destroying object '%0' whose lifetime has already ended">; def note_constexpr_unsupported_destruction : Note< "non-trivial destruction of type %0 in a constant expression is not supported">; def note_constexpr_unsupported_unsized_array : Note< @@ -234,6 +246,38 @@ def note_constexpr_bit_cast_invalid_subtype : Note< def note_constexpr_bit_cast_indet_dest : Note< "indeterminate value can only initialize an object of type 'unsigned char'" "%select{, 'char',|}1 or 'std::byte'; %0 is invalid">; +def note_constexpr_new : Note< + "dynamic memory allocation is not permitted in constant expressions " + "until C++20">; +def note_constexpr_new_non_replaceable : Note< + "call to %select{placement|class-specific}0 %1">; +def note_constexpr_new_placement : Note< + "this placement new expression is not yet supported in constant expressions">; +def note_constexpr_new_negative : Note< + "cannot allocate array; evaluated array bound %0 is negative">; +def note_constexpr_new_too_large : Note< + "cannot allocate array; evaluated array bound %0 is too large">; +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_delete_not_heap_alloc : Note< + "delete of pointer '%0' that does not point to a heap-allocated object">; +def note_constexpr_double_delete : Note< + "delete of pointer that has already been deleted">; +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">; +def note_constexpr_delete_subobject : Note< + "delete of pointer%select{ to subobject|}1 '%0' " + "%select{|that does not point to complete object}1">; +def note_constexpr_delete_base_nonvirt_dtor : Note< + "delete of object with dynamic type %1 through pointer to " + "base class type %0 with non-virtual destructor">; +def note_constexpr_memory_leak : Note< + "allocation performed here was not deallocated" + "%plural{0:|: (along with %0 other memory leak%s0)}0">; def err_experimental_clang_interp_failed : Error< "the experimental clang interpreter failed to evaluate an expression">; diff --git a/lib/AST/APValue.cpp b/lib/AST/APValue.cpp index 1993bba9bd..b44ab9837b 100644 --- a/lib/AST/APValue.cpp +++ b/lib/AST/APValue.cpp @@ -42,6 +42,14 @@ APValue::LValueBase::LValueBase(const ValueDecl *P, unsigned I, unsigned V) APValue::LValueBase::LValueBase(const Expr *P, unsigned I, unsigned V) : Ptr(P), Local{I, V} {} +APValue::LValueBase APValue::LValueBase::getDynamicAlloc(DynamicAllocLValue LV, + QualType Type) { + LValueBase Base; + Base.Ptr = LV; + Base.DynamicAllocType = Type.getAsOpaquePtr(); + return Base; +} + APValue::LValueBase APValue::LValueBase::getTypeInfo(TypeInfoLValue LV, QualType TypeInfo) { LValueBase Base; @@ -51,11 +59,12 @@ APValue::LValueBase APValue::LValueBase::getTypeInfo(TypeInfoLValue LV, } unsigned APValue::LValueBase::getCallIndex() const { - return is() ? 0 : Local.CallIndex; + return (is() || is()) ? 0 + : Local.CallIndex; } unsigned APValue::LValueBase::getVersion() const { - return is() ? 0 : Local.Version; + return (is() || is()) ? 0 : Local.Version; } QualType APValue::LValueBase::getTypeInfoType() const { @@ -63,6 +72,11 @@ QualType APValue::LValueBase::getTypeInfoType() const { return QualType::getFromOpaquePtr(TypeInfoType); } +QualType APValue::LValueBase::getDynamicAllocType() const { + assert(is() && "not a dynamic allocation lvalue"); + return QualType::getFromOpaquePtr(DynamicAllocType); +} + namespace clang { bool operator==(const APValue::LValueBase &LHS, const APValue::LValueBase &RHS) { @@ -111,7 +125,7 @@ llvm::DenseMapInfo::getTombstoneKey() { namespace clang { llvm::hash_code hash_value(const APValue::LValueBase &Base) { - if (Base.is()) + if (Base.is() || Base.is()) return llvm::hash_value(Base.getOpaqueValue()); return llvm::hash_combine(Base.getOpaqueValue(), Base.getCallIndex(), Base.getVersion()); @@ -528,13 +542,18 @@ void APValue::printPretty(raw_ostream &Out, const ASTContext &Ctx, S = CharUnits::One(); } Out << '&'; - } else if (!IsReference) + } else if (!IsReference) { Out << '&'; + } if (const ValueDecl *VD = Base.dyn_cast()) Out << *VD; else if (TypeInfoLValue TI = Base.dyn_cast()) { TI.print(Out, Ctx.getPrintingPolicy()); + } else if (DynamicAllocLValue DA = Base.dyn_cast()) { + Out << "{*new " + << Base.getDynamicAllocType().stream(Ctx.getPrintingPolicy()) << "#" + << DA.getIndex() << "}"; } else { assert(Base.get() != nullptr && "Expecting non-null Expr"); @@ -563,10 +582,17 @@ void APValue::printPretty(raw_ostream &Out, const ASTContext &Ctx, } else if (TypeInfoLValue TI = Base.dyn_cast()) { TI.print(Out, Ctx.getPrintingPolicy()); ElemTy = Base.getTypeInfoType(); + } else if (DynamicAllocLValue DA = Base.dyn_cast()) { + Out << "{*new " + << Base.getDynamicAllocType().stream(Ctx.getPrintingPolicy()) << "#" + << DA.getIndex() << "}"; + ElemTy = Base.getDynamicAllocType(); } else { const Expr *E = Base.get(); assert(E != nullptr && "Expecting non-null Expr"); E->printPretty(Out, nullptr, Ctx.getPrintingPolicy()); + // FIXME: This is wrong if E is a MaterializeTemporaryExpr with an lvalue + // adjustment. ElemTy = E->getType(); } diff --git a/lib/AST/ExprCXX.cpp b/lib/AST/ExprCXX.cpp index c5f86a4cc1..30c28314d5 100644 --- a/lib/AST/ExprCXX.cpp +++ b/lib/AST/ExprCXX.cpp @@ -124,6 +124,8 @@ CXXNewExpr::CXXNewExpr(bool IsGlobalNew, FunctionDecl *OperatorNew, if (ArraySize) { if (Expr *SizeExpr = *ArraySize) { + if (SizeExpr->isValueDependent()) + ExprBits.ValueDependent = true; if (SizeExpr->isInstantiationDependent()) ExprBits.InstantiationDependent = true; if (SizeExpr->containsUnexpandedParameterPack()) @@ -134,6 +136,8 @@ CXXNewExpr::CXXNewExpr(bool IsGlobalNew, FunctionDecl *OperatorNew, } if (Initializer) { + if (Initializer->isValueDependent()) + ExprBits.ValueDependent = true; if (Initializer->isInstantiationDependent()) ExprBits.InstantiationDependent = true; if (Initializer->containsUnexpandedParameterPack()) @@ -143,6 +147,8 @@ CXXNewExpr::CXXNewExpr(bool IsGlobalNew, FunctionDecl *OperatorNew, } for (unsigned I = 0; I != PlacementArgs.size(); ++I) { + if (PlacementArgs[I]->isValueDependent()) + ExprBits.ValueDependent = true; if (PlacementArgs[I]->isInstantiationDependent()) ExprBits.InstantiationDependent = true; if (PlacementArgs[I]->containsUnexpandedParameterPack()) diff --git a/lib/AST/ExprConstant.cpp b/lib/AST/ExprConstant.cpp index fbec3ae582..ed3d1fa905 100644 --- a/lib/AST/ExprConstant.cpp +++ b/lib/AST/ExprConstant.cpp @@ -66,8 +66,6 @@ using llvm::APSInt; using llvm::APFloat; using llvm::Optional; -static bool IsGlobalLValue(APValue::LValueBase B); - namespace { struct LValue; class CallStackFrame; @@ -98,6 +96,9 @@ namespace { if (B.is()) return B.getTypeInfoType(); + if (B.is()) + return B.getDynamicAllocType(); + const Expr *Base = B.get(); // For a materialized temporary, the type of the temporary we materialized @@ -612,8 +613,9 @@ namespace { }; } -static bool HandleDestructorCall(EvalInfo &Info, APValue::LValueBase LVBase, - APValue &Value, QualType T); +static bool HandleDestructorCall(EvalInfo &Info, SourceLocation Loc, + APValue::LValueBase LVBase, APValue &Value, + QualType T); namespace { /// A cleanup, and a flag indicating whether it is lifetime-extended. @@ -629,8 +631,14 @@ namespace { bool isLifetimeExtended() const { return Value.getInt(); } bool endLifetime(EvalInfo &Info, bool RunDestructors) { - if (RunDestructors && T.isDestructedType()) - return HandleDestructorCall(Info, Base, *Value.getPointer(), T); + if (RunDestructors) { + SourceLocation Loc; + if (const ValueDecl *VD = Base.dyn_cast()) + Loc = VD->getLocation(); + else if (const Expr *E = Base.dyn_cast()) + Loc = E->getExprLoc(); + return HandleDestructorCall(Info, Loc, Base, *Value.getPointer(), T); + } *Value.getPointer() = APValue(); return true; } @@ -742,6 +750,28 @@ namespace { llvm::DenseMap 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. + std::map HeapAllocs; + + /// The number of heap allocations performed so far in this evaluation. + unsigned NumHeapAllocs = 0; + struct EvaluatingConstructorRAII { EvalInfo &EI; ObjectUnderConstruction Object; @@ -766,20 +796,20 @@ namespace { struct EvaluatingDestructorRAII { EvalInfo &EI; ObjectUnderConstruction Object; + bool DidInsert; EvaluatingDestructorRAII(EvalInfo &EI, ObjectUnderConstruction Object) : EI(EI), Object(Object) { - bool DidInsert = EI.ObjectsUnderConstruction - .insert({Object, ConstructionPhase::Destroying}) - .second; - (void)DidInsert; - assert(DidInsert && "destroyed object multiple times"); + DidInsert = EI.ObjectsUnderConstruction + .insert({Object, ConstructionPhase::Destroying}) + .second; } void startedDestroyingBases() { EI.ObjectsUnderConstruction[Object] = ConstructionPhase::DestroyingBases; } ~EvaluatingDestructorRAII() { - EI.ObjectsUnderConstruction.erase(Object); + if (DidInsert) + EI.ObjectsUnderConstruction.erase(Object); } }; @@ -917,6 +947,16 @@ namespace { return true; } + APValue *createHeapAlloc(const Expr *E, QualType T, LValue &LV); + + Optional lookupDynamicAlloc(DynamicAllocLValue DA) { + Optional Result; + auto It = HeapAllocs.find(DA); + if (It != HeapAllocs.end()) + Result = &It->second; + return Result; + } + void performLifetimeExtension() { // Disable the cleanups for lifetime-extended temporaries. CleanupStack.erase( @@ -1192,12 +1232,17 @@ namespace { Info.CurrentCall->popTempVersion(); } private: - static bool cleanup(EvalInfo &Info, bool RunDestructors, unsigned OldStackSize) { + static bool cleanup(EvalInfo &Info, bool RunDestructors, + unsigned OldStackSize) { + assert(OldStackSize <= Info.CleanupStack.size() && + "running cleanups out of order?"); + // Run all cleanups for a block scope, and non-lifetime-extended cleanups // for a full-expression scope. for (unsigned I = Info.CleanupStack.size(); I > OldStackSize; --I) { - if (!(IsFullExpression && Info.CleanupStack[I-1].isLifetimeExtended())) { - if (!Info.CleanupStack[I-1].endLifetime(Info, RunDestructors)) + if (!(IsFullExpression && + Info.CleanupStack[I - 1].isLifetimeExtended())) { + if (!Info.CleanupStack[I - 1].endLifetime(Info, RunDestructors)) return false; } } @@ -1634,15 +1679,40 @@ static void negateAsSigned(APSInt &Int) { template APValue &CallStackFrame::createTemporary(const KeyT *Key, QualType T, bool IsLifetimeExtended, LValue &LV) { - unsigned Version = Info.CurrentCall->getTempVersion(); + unsigned Version = getTempVersion(); APValue::LValueBase Base(Key, Index, Version); LV.set(Base); APValue &Result = Temporaries[MapKeyTy(Key, Version)]; assert(Result.isAbsent() && "temporary created multiple times"); - Info.CleanupStack.push_back(Cleanup(&Result, Base, T, IsLifetimeExtended)); + + // If we're creating a temporary immediately in the operand of a speculative + // evaluation, don't register a cleanup to be run outside the speculative + // evaluation context, since we won't actually be able to initialize this + // object. + if (Index <= Info.SpeculativeEvaluationDepth) { + if (T.isDestructedType()) + Info.noteSideEffect(); + } else { + Info.CleanupStack.push_back(Cleanup(&Result, Base, T, IsLifetimeExtended)); + } return Result; } +APValue *EvalInfo::createHeapAlloc(const Expr *E, QualType T, LValue &LV) { + if (NumHeapAllocs > DynamicAllocLValue::getMaxIndex()) { + FFDiag(E, diag::note_constexpr_heap_alloc_limit_exceeded); + return nullptr; + } + + DynamicAllocLValue DA(NumHeapAllocs++); + LV.set(APValue::LValueBase::getDynamicAlloc(DA, T)); + auto Result = HeapAllocs.emplace(std::piecewise_construct, + std::forward_as_tuple(DA), std::tuple<>()); + assert(Result.second && "reused a heap alloc index?"); + Result.first->second.AllocExpr = E; + return &Result.first->second.Value; +} + /// Produce a string describing the given constexpr call. void CallStackFrame::describe(raw_ostream &Out) { unsigned ArgIndex = 0; @@ -1713,7 +1783,7 @@ static bool IsGlobalLValue(APValue::LValueBase B) { return isa(D); } - if (B.is()) + if (B.is() || B.is()) return true; const Expr *E = B.get(); @@ -1812,6 +1882,12 @@ static void NoteLValueLocation(EvalInfo &Info, APValue::LValueBase Base) { Info.Note(VD->getLocation(), diag::note_declared_at); else if (const Expr *E = Base.dyn_cast()) Info.Note(E->getExprLoc(), diag::note_constexpr_temporary_here); + else if (DynamicAllocLValue DA = Base.dyn_cast()) { + // FIXME: Produce a note for dangling pointers too. + if (Optional Alloc = Info.lookupDynamicAlloc(DA)) + Info.Note((*Alloc)->AllocExpr->getExprLoc(), + diag::note_constexpr_dynamic_alloc_here); + } // We have no information to show for a typeid(T) object. } @@ -1846,14 +1922,23 @@ static bool CheckLValueConstantExpression(EvalInfo &Info, SourceLocation Loc, LVal.getLValueCallIndex() == 0) && "have call index for global lvalue"); + if (Base.is()) { + Info.FFDiag(Loc, diag::note_constexpr_dynamic_alloc) + << IsReferenceType << !Designator.Entries.empty(); + NoteLValueLocation(Info, Base); + return false; + } + if (const ValueDecl *VD = Base.dyn_cast()) { if (const VarDecl *Var = dyn_cast(VD)) { // Check if this is a thread-local variable. if (Var->getTLSKind()) + // FIXME: Diagnostic! return false; // A dllimport variable never acts like a constant. if (Usage == Expr::EvaluateForCodeGen && Var->hasAttr()) + // FIXME: Diagnostic! return false; } if (const auto *FD = dyn_cast(VD)) { @@ -1869,6 +1954,7 @@ static bool CheckLValueConstantExpression(EvalInfo &Info, SourceLocation Loc, // perform initialization with the address of the thunk. if (Info.getLangOpts().CPlusPlus && Usage == Expr::EvaluateForCodeGen && FD->hasAttr()) + // FIXME: Diagnostic! return false; } } @@ -2045,6 +2131,20 @@ static bool CheckFullyInitialized(EvalInfo &Info, SourceLocation DiagLoc, Expr::EvaluateForCodeGen); } +/// Enforce C++2a [expr.const]/4.17, which disallows new-expressions unless +/// "the allocated storage is deallocated within the evaluation". +static bool CheckMemoryLeaks(EvalInfo &Info) { + if (!Info.HeapAllocs.empty()) { + // We can still fold to a constant despite a compile-time memory leak, + // so long as the heap allocation isn't referenced in the result (we check + // that in CheckConstantExpression). + Info.CCEDiag(Info.HeapAllocs.begin()->second.AllocExpr, + diag::note_constexpr_memory_leak) + << unsigned(Info.HeapAllocs.size() - 1); + } + return true; +} + static bool EvalPointerValueAsBool(const APValue &Value, bool &Result) { // A null base expression indicates a null pointer. These are always // evaluatable, and they are false unless the offset is zero. @@ -2736,9 +2836,10 @@ static APSInt extractStringLiteralCharacter(EvalInfo &Info, const Expr *Lit, // FIXME: This is inefficient; we should probably introduce something similar // to the LLVM ConstantDataArray to make this cheaper. static void expandStringLiteral(EvalInfo &Info, const StringLiteral *S, - APValue &Result) { - const ConstantArrayType *CAT = - Info.Ctx.getAsConstantArrayType(S->getType()); + APValue &Result, + QualType AllocType = QualType()) { + const ConstantArrayType *CAT = Info.Ctx.getAsConstantArrayType( + AllocType.isNull() ? S->getType() : AllocType); assert(CAT && "string literal isn't an array"); QualType CharType = CAT->getElementType(); assert(CharType->isIntegerType() && "unexpected character type"); @@ -3372,6 +3473,14 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, if (!evaluateVarDeclInit(Info, E, VD, Frame, BaseVal, &LVal)) return CompleteObject(); + } else if (DynamicAllocLValue DA = LVal.Base.dyn_cast()) { + Optional Alloc = Info.lookupDynamicAlloc(DA); + if (!Alloc) { + Info.FFDiag(E, diag::note_constexpr_access_deleted_object) << AK; + return CompleteObject(); + } + return CompleteObject(LVal.Base, &(*Alloc)->Value, + LVal.Base.getDynamicAllocType()); } else { const Expr *Base = LVal.Base.dyn_cast(); @@ -5462,6 +5571,18 @@ static bool HandleConstructorCall(const Expr *E, const LValue &This, static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc, const LValue &This, APValue &Value, QualType T) { + // Objects can only be destroyed while they're within their lifetimes. + // FIXME: We have no representation for whether an object of type nullptr_t + // is in its lifetime; it usually doesn't matter. Perhaps we should model it + // as indeterminate instead? + if (Value.isAbsent() && !T->isNullPtrType()) { + APValue Printable; + This.moveInto(Printable); + Info.FFDiag(CallLoc, diag::note_constexpr_destroy_out_of_lifetime) + << Printable.getAsString(Info.Ctx, Info.Ctx.getLValueReferenceType(T)); + return false; + } + // Invent an expression for location purposes. // FIXME: We shouldn't need to do this. OpaqueValueExpr LocE(CallLoc, Info.Ctx.IntTy, VK_RValue); @@ -5476,10 +5597,16 @@ static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc, if (!HandleLValueArrayAdjustment(Info, &LocE, ElemLV, ElemT, Size)) return false; + // Ensure that we have actual array elements available to destroy; the + // destructors might mutate the value, so we can't run them on the array + // filler. + if (Size && Size > Value.getArrayInitializedElts()) + expandArray(Value, Value.getArraySize() - 1); + for (; Size != 0; --Size) { APValue &Elem = Value.getArrayInitializedElt(Size - 1); - if (!HandleDestructorCallImpl(Info, CallLoc, ElemLV, Elem, ElemT) || - !HandleLValueArrayAdjustment(Info, &LocE, ElemLV, ElemT, -1)) + if (!HandleLValueArrayAdjustment(Info, &LocE, ElemLV, ElemT, -1) || + !HandleDestructorCallImpl(Info, CallLoc, ElemLV, Elem, ElemT)) return false; } @@ -5505,16 +5632,12 @@ static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc, } const CXXDestructorDecl *DD = RD->getDestructor(); - if (!DD) { - // FIXME: Can we get here for a type with an irrelevant destructor? + if (!DD && !RD->hasTrivialDestructor()) { Info.FFDiag(CallLoc); return false; } - const FunctionDecl *Definition = nullptr; - const Stmt *Body = DD->getBody(Definition); - - if ((DD && DD->isTrivial()) || + if (!DD || DD->isTrivial() || (RD->isAnonymousStructOrUnion() && RD->isUnion())) { // A trivial destructor just ends the lifetime of the object. Check for // this case before checking for a body, because we might not bother @@ -5532,6 +5655,9 @@ static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc, if (!Info.CheckCallLimit(CallLoc)) return false; + const FunctionDecl *Definition = nullptr; + const Stmt *Body = DD->getBody(Definition); + if (!CheckConstexprFunction(Info, CallLoc, DD, Definition, Body)) return false; @@ -5542,6 +5668,16 @@ static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc, EvalInfo::EvaluatingDestructorRAII EvalObj( Info, ObjectUnderConstruction{This.getLValueBase(), This.Designator.Entries}); + if (!EvalObj.DidInsert) { + // C++2a [class.dtor]p19: + // the behavior is undefined if the destructor is invoked for an object + // whose lifetime has ended + // (Note that formally the lifetime ends when the period of destruction + // begins, even though certain uses of the object remain valid until the + // period of destruction ends.) + Info.FFDiag(CallLoc, diag::note_constexpr_double_destroy); + return false; + } // FIXME: Creating an APValue just to hold a nonexistent return value is // wasteful. @@ -5598,13 +5734,13 @@ static bool HandleDestructorCallImpl(EvalInfo &Info, SourceLocation CallLoc, return true; } -static bool HandleDestructorCall(EvalInfo &Info, APValue::LValueBase LVBase, - APValue &Value, QualType T) { - SourceLocation Loc; - if (const ValueDecl *VD = LVBase.dyn_cast()) - Loc = VD->getLocation(); - else if (const Expr *E = LVBase.dyn_cast()) - Loc = E->getExprLoc(); +static bool HandleDestructorCall(EvalInfo &Info, SourceLocation Loc, + APValue::LValueBase LVBase, APValue &Value, + QualType T) { + // If we've had an unmodeled side-effect, we can't rely on mutable state + // (such as the object we're about to destroy) being correct. + if (Info.EvalStatus.HasSideEffects) + return false; LValue LV; LV.set({LVBase}); @@ -7339,6 +7475,8 @@ public: return true; } + bool VisitCXXNewExpr(const CXXNewExpr *E); + bool VisitSourceLocExpr(const SourceLocExpr *E) { assert(E->isStringType() && "SourceLocExpr isn't a pointer type?"); APValue LValResult = E->EvaluateInContext( @@ -7900,6 +8038,125 @@ bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, } } +static bool EvaluateArrayNewInitList(EvalInfo &Info, LValue &This, + APValue &Result, const InitListExpr *ILE, + QualType AllocType); + +bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) { + // We cannot speculatively evaluate a delete expression. + if (Info.SpeculativeEvaluationDepth) + return false; + + FunctionDecl *OperatorNew = E->getOperatorNew(); + if (!OperatorNew->isReplaceableGlobalAllocationFunction()) { + Info.FFDiag(E, diag::note_constexpr_new_non_replaceable) + << isa(OperatorNew) << OperatorNew; + return false; + } + + // FIXME: There is no restriction on this, but it's not clear that it + // 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). + if (E->getNumPlacementArgs()) + return Error(E, diag::note_constexpr_new_placement); + if (!Info.getLangOpts().CPlusPlus2a) + Info.CCEDiag(E, diag::note_constexpr_new); + + const Expr *Init = E->getInitializer(); + const InitListExpr *ResizedArrayILE = nullptr; + + QualType AllocType = E->getAllocatedType(); + if (Optional ArraySize = E->getArraySize()) { + const Expr *Stripped = *ArraySize; + for (; auto *ICE = dyn_cast(Stripped); + Stripped = ICE->getSubExpr()) + if (ICE->getCastKind() != CK_NoOp && + ICE->getCastKind() != CK_IntegralCast) + break; + + llvm::APSInt ArrayBound; + if (!EvaluateInteger(Stripped, ArrayBound, Info)) + return false; + + // C++ [expr.new]p9: + // The expression is erroneous if: + // -- [...] its value before converting to size_t [or] applying the + // second standard conversion sequence is less than zero + if (ArrayBound.isSigned() && ArrayBound.isNegative()) { + Info.FFDiag(*ArraySize, diag::note_constexpr_new_negative) + << ArrayBound << (*ArraySize)->getSourceRange(); + return false; + } + + // -- its value is such that the size of the allocated object would + // exceed the implementation-defined limit + if (ConstantArrayType::getNumAddressingBits(Info.Ctx, AllocType, + ArrayBound) > + ConstantArrayType::getMaxSizeBits(Info.Ctx)) { + Info.FFDiag(*ArraySize, diag::note_constexpr_new_too_large) + << ArrayBound << (*ArraySize)->getSourceRange(); + return false; + } + + // -- the new-initializer is a braced-init-list and the number of + // array elements for which initializers are provided [...] + // exceeds the number of elements to initialize + if (Init) { + auto *CAT = Info.Ctx.getAsConstantArrayType(Init->getType()); + assert(CAT && "unexpected type for array initializer"); + + unsigned Bits = + std::max(CAT->getSize().getBitWidth(), ArrayBound.getBitWidth()); + llvm::APInt InitBound = CAT->getSize().zextOrSelf(Bits); + llvm::APInt AllocBound = ArrayBound.zextOrSelf(Bits); + if (InitBound.ugt(AllocBound)) { + Info.FFDiag(*ArraySize, diag::note_constexpr_new_too_small) + << AllocBound.toString(10, /*Signed=*/false) + << InitBound.toString(10, /*Signed=*/false) + << (*ArraySize)->getSourceRange(); + return false; + } + + // If the sizes differ, we must have an initializer list, and we need + // special handling for this case when we initialize. + if (InitBound != AllocBound) + ResizedArrayILE = cast(Init); + } + + AllocType = Info.Ctx.getConstantArrayType(AllocType, ArrayBound, + ArrayType::Normal, 0); + } else { + assert(!AllocType->isArrayType() && + "array allocation with non-array new"); + } + + // Perform the allocation and obtain a pointer to the resulting object. + APValue *Val = Info.createHeapAlloc(E, AllocType, Result); + if (!Val) + return false; + + if (ResizedArrayILE) { + if (!EvaluateArrayNewInitList(Info, Result, *Val, ResizedArrayILE, + AllocType)) + return false; + } else if (Init) { + if (!EvaluateInPlace(*Val, Info, Result, Init)) + return false; + } else { + *Val = getDefaultInitValue(AllocType); + } + + // Array new returns a pointer to the first element, not a pointer to the + // array. + if (auto *AT = AllocType->getAsArrayTypeUnsafe()) + Result.addArray(Info, E, cast(AT)); + + return true; +} //===----------------------------------------------------------------------===// // Member Pointer Evaluation //===----------------------------------------------------------------------===// @@ -8367,9 +8624,8 @@ bool RecordExprEvaluator::VisitCXXStdInitializerListExpr( bool RecordExprEvaluator::VisitLambdaExpr(const LambdaExpr *E) { const CXXRecordDecl *ClosureClass = E->getLambdaClass(); - if (ClosureClass->isInvalidDecl()) return false; - - if (Info.checkingPotentialConstantExpression()) return true; + if (ClosureClass->isInvalidDecl()) + return false; const size_t NumFields = std::distance(ClosureClass->field_begin(), ClosureClass->field_end()); @@ -8688,14 +8944,16 @@ namespace { bool VisitCallExpr(const CallExpr *E) { return handleCallExpr(E, Result, &This); } - bool VisitInitListExpr(const InitListExpr *E); + bool VisitInitListExpr(const InitListExpr *E, + QualType AllocType = QualType()); bool VisitArrayInitLoopExpr(const ArrayInitLoopExpr *E); bool VisitCXXConstructExpr(const CXXConstructExpr *E); bool VisitCXXConstructExpr(const CXXConstructExpr *E, const LValue &Subobject, APValue *Value, QualType Type); - bool VisitStringLiteral(const StringLiteral *E) { - expandStringLiteral(Info, E, Result); + bool VisitStringLiteral(const StringLiteral *E, + QualType AllocType = QualType()) { + expandStringLiteral(Info, E, Result, AllocType); return true; } }; @@ -8707,6 +8965,15 @@ static bool EvaluateArray(const Expr *E, const LValue &This, return ArrayExprEvaluator(Info, This, Result).Visit(E); } +static bool EvaluateArrayNewInitList(EvalInfo &Info, LValue &This, + APValue &Result, const InitListExpr *ILE, + QualType AllocType) { + assert(ILE->isRValue() && ILE->getType()->isArrayType() && + "not an array rvalue"); + return ArrayExprEvaluator(Info, This, Result) + .VisitInitListExpr(ILE, AllocType); +} + // Return true iff the given array filler may depend on the element index. static bool MaybeElementDependentArrayFiller(const Expr *FillerExpr) { // For now, just whitelist non-class value-initialization and initialization @@ -8723,15 +8990,23 @@ static bool MaybeElementDependentArrayFiller(const Expr *FillerExpr) { return true; } -bool ArrayExprEvaluator::VisitInitListExpr(const InitListExpr *E) { - const ConstantArrayType *CAT = Info.Ctx.getAsConstantArrayType(E->getType()); +bool ArrayExprEvaluator::VisitInitListExpr(const InitListExpr *E, + QualType AllocType) { + const ConstantArrayType *CAT = Info.Ctx.getAsConstantArrayType( + AllocType.isNull() ? E->getType() : AllocType); if (!CAT) return Error(E); // C++11 [dcl.init.string]p1: A char array [...] can be initialized by [...] // an appropriately-typed string literal enclosed in braces. - if (E->isStringLiteralInit()) - return Visit(E->getInit(0)); + if (E->isStringLiteralInit()) { + auto *SL = dyn_cast(E->getInit(0)->IgnoreParens()); + // FIXME: Support ObjCEncodeExpr here once we support it in + // ArrayExprEvaluator generally. + if (!SL) + return Error(E); + return VisitStringLiteral(SL, AllocType); + } bool Success = true; @@ -9415,6 +9690,8 @@ static QualType getObjectType(APValue::LValueBase B) { return E->getType(); } else if (B.is()) { return B.getTypeInfoType(); + } else if (B.is()) { + return B.getDynamicAllocType(); } return QualType(); @@ -12402,9 +12679,116 @@ public: return true; } } + + 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) + return false; + + FunctionDecl *OperatorDelete = E->getOperatorDelete(); + if (!OperatorDelete->isReplaceableGlobalAllocationFunction()) { + Info.FFDiag(E, diag::note_constexpr_new_non_replaceable) + << isa(OperatorDelete) << OperatorDelete; + return false; + } + + const Expr *Arg = E->getArgument(); + + LValue Pointer; + if (!EvaluatePointer(Arg, Pointer, Info)) + return false; + if (Pointer.Designator.Invalid) + return false; + + // Deleting a null pointer has no effect. + if (Pointer.isNullPointer()) { + // This is the only case where we need to produce an extension warning: + // the only other way we can succeed is if we find a dynamic allocation, + // and we will have warned when we allocated it in that case. + if (!Info.getLangOpts().CPlusPlus2a) + Info.CCEDiag(E, diag::note_constexpr_new); + return true; + } + + auto PointerAsString = [&] { + APValue Printable; + Pointer.moveInto(Printable); + return Printable.getAsString(Info.Ctx, Arg->getType()); + }; + + DynamicAllocLValue DA = Pointer.Base.dyn_cast(); + if (!DA) { + Info.FFDiag(E, diag::note_constexpr_delete_not_heap_alloc) + << PointerAsString(); + if (Pointer.Base) + NoteLValueLocation(Info, Pointer.Base); + return false; + } + QualType AllocType = Pointer.Base.getDynamicAllocType(); + + Optional 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 && + !hasVirtualDestructor(Arg->getType()->getPointeeType())) { + Info.FFDiag(E, diag::note_constexpr_delete_base_nonvirt_dtor) + << Arg->getType()->getPointeeType() << AllocType; + return false; + } + + if (!HandleDestructorCall(Info, E->getExprLoc(), Pointer.getLValueBase(), + (*Alloc)->Value, AllocType)) + return false; + + if (!Info.HeapAllocs.erase(DA)) { + // 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 + // far, and should be diagnosed elsewhere first. + Info.FFDiag(E, diag::note_constexpr_double_delete); + return false; + } + + return true; +} + static bool EvaluateVoid(const Expr *E, EvalInfo &Info) { assert(E->isRValue() && E->getType()->isVoidType()); return VoidExprEvaluator(Info).Visit(E); @@ -12554,7 +12938,8 @@ static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result) { } // Check this core constant expression is a constant expression. - return CheckConstantExpression(Info, E->getExprLoc(), E->getType(), Result); + return CheckConstantExpression(Info, E->getExprLoc(), E->getType(), Result) && + CheckMemoryLeaks(Info); } static bool FastEvaluateAsRValue(const Expr *Exp, Expr::EvalResult &Result, @@ -12723,14 +13108,15 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, ConstExprUsage Usage, EvalInfo Info(Ctx, Result, EM); Info.InConstantContext = true; - if (!::Evaluate(Result.Val, Info, this)) + if (!::Evaluate(Result.Val, Info, this) || Result.HasSideEffects) return false; if (!Info.discardCleanups()) llvm_unreachable("Unhandled cleanup; missing full expression marker?"); return CheckConstantExpression(Info, getExprLoc(), getType(), Result.Val, - Usage); + Usage) && + CheckMemoryLeaks(Info); } bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx, @@ -12799,7 +13185,8 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx, if (!Info.discardCleanups()) llvm_unreachable("Unhandled cleanup; missing full expression marker?"); - return CheckConstantExpression(Info, DeclLoc, DeclTy, Value); + return CheckConstantExpression(Info, DeclLoc, DeclTy, Value) && + CheckMemoryLeaks(Info); } /// isEvaluatable - Call EvaluateAsRValue to see if this expression can be @@ -13409,7 +13796,7 @@ bool Expr::isCXX11ConstantExpr(const ASTContext &Ctx, APValue *Result, ::EvaluateAsRValue(Info, this, Result ? *Result : Scratch) && // FIXME: We don't produce a diagnostic for this, but the callers that // call us on arbitrary full-expressions should generally not care. - Info.discardCleanups(); + Info.discardCleanups() && !Status.HasSideEffects; if (!Diags.empty()) { IsConstExpr = false; diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index bf957797e9..c03e4dc66f 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -6364,6 +6364,12 @@ void Sema::CheckCompletedCXXClass(CXXRecordDecl *Record) { DelayedDllExportMemberFunctions.push_back(M); } } + + // Define defaulted constexpr virtual functions that override a base class + // function right away. + // FIXME: We can defer doing this until the vtable is marked as used. + if (M->isDefaulted() && M->isConstexpr() && M->size_overridden_methods()) + DefineImplicitSpecialMember(*this, M, M->getLocation()); }; bool HasMethodWithOverrideControl = false, diff --git a/test/CXX/expr/expr.const/p2-0x.cpp b/test/CXX/expr/expr.const/p2-0x.cpp index 091ef09725..63e30175b9 100644 --- a/test/CXX/expr/expr.const/p2-0x.cpp +++ b/test/CXX/expr/expr.const/p2-0x.cpp @@ -1,4 +1,5 @@ -// RUN: %clang_cc1 -fsyntax-only -std=c++11 -pedantic -verify -fcxx-exceptions %s -fconstexpr-depth 128 -triple i686-pc-linux-gnu +// RUN: %clang_cc1 -fsyntax-only -std=c++11 -pedantic -verify=expected,cxx11 -fcxx-exceptions %s -fconstexpr-depth 128 -triple i686-pc-linux-gnu +// RUN: %clang_cc1 -fsyntax-only -std=c++2a -pedantic -verify=expected,cxx20 -fcxx-exceptions %s -fconstexpr-depth 128 -triple i686-pc-linux-gnu // A conditional-expression is a core constant expression unless it involves one // of the following as a potentially evaluated subexpression [...]: @@ -157,13 +158,13 @@ namespace UndefinedBehavior { constexpr int shl_unsigned_negative = unsigned(-3) << 1; // ok constexpr int shl_unsigned_into_sign = 1u << 31; // ok constexpr int shl_unsigned_overflow = 1024u << 31; // ok - constexpr int shl_signed_negative = (-3) << 1; // expected-error {{constant expression}} expected-note {{left shift of negative value -3}} + constexpr int shl_signed_negative = (-3) << 1; // cxx11-error {{constant expression}} cxx11-note {{left shift of negative value -3}} constexpr int shl_signed_ok = 1 << 30; // ok constexpr int shl_signed_into_sign = 1 << 31; // ok (DR1457) constexpr int shl_signed_into_sign_2 = 0x7fffffff << 1; // ok (DR1457) - constexpr int shl_signed_off_end = 2 << 31; // expected-error {{constant expression}} expected-note {{signed left shift discards bits}} expected-warning {{signed shift result (0x100000000) requires 34 bits to represent, but 'int' only has 32 bits}} - constexpr int shl_signed_off_end_2 = 0x7fffffff << 2; // expected-error {{constant expression}} expected-note {{signed left shift discards bits}} expected-warning {{signed shift result (0x1FFFFFFFC) requires 34 bits to represent, but 'int' only has 32 bits}} - constexpr int shl_signed_overflow = 1024 << 31; // expected-error {{constant expression}} expected-note {{signed left shift discards bits}} expected-warning {{requires 43 bits to represent}} + constexpr int shl_signed_off_end = 2 << 31; // cxx11-error {{constant expression}} cxx11-note {{signed left shift discards bits}} expected-warning {{signed shift result (0x100000000) requires 34 bits to represent, but 'int' only has 32 bits}} + constexpr int shl_signed_off_end_2 = 0x7fffffff << 2; // cxx11-error {{constant expression}} cxx11-note {{signed left shift discards bits}} expected-warning {{signed shift result (0x1FFFFFFFC) requires 34 bits to represent, but 'int' only has 32 bits}} + constexpr int shl_signed_overflow = 1024 << 31; // cxx11-error {{constant expression}} cxx11-note {{signed left shift discards bits}} expected-warning {{requires 43 bits to represent}} constexpr int shl_signed_ok2 = 1024 << 20; // ok constexpr int shr_m1 = 0 >> -1; // expected-error {{constant expression}} expected-note {{negative shift count -1}} @@ -291,7 +292,7 @@ namespace UndefinedBehavior { // - a lambda-expression (5.1.2); struct Lambda { - int n : []{ return 1; }(); // expected-error {{constant expression}} expected-error {{integral constant expression}} expected-note {{non-literal type}} + int n : []{ return 1; }(); // cxx11-error {{constant expression}} cxx11-error {{integral constant expression}} cxx11-note {{non-literal type}} }; // - an lvalue-to-rvalue conversion (4.1) unless it is applied to @@ -360,7 +361,7 @@ namespace LValueToRValueUnion { extern const U pu; constexpr const int *pua = &pu.a; constexpr const int *pub = &pu.b; - constexpr U pu = { .b = 1 }; // expected-warning {{C++20 extension}} + constexpr U pu = { .b = 1 }; // cxx11-warning {{C++20 extension}} constexpr const int a2 = *pua; // expected-error {{constant expression}} expected-note {{read of member 'a' of union with active member 'b'}} constexpr const int b2 = *pub; // ok } @@ -402,7 +403,7 @@ namespace DynamicCast { struct S { int n; }; constexpr S s { 16 }; struct T { - int n : dynamic_cast(&s)->n; // expected-warning {{constant expression}} expected-note {{dynamic_cast}} + int n : dynamic_cast(&s)->n; // cxx11-warning {{constant expression}} cxx11-note {{dynamic_cast}} }; } @@ -431,8 +432,8 @@ namespace PseudoDtor { namespace IncDec { int k = 2; struct T { - int n : ++k; // expected-error {{constant expression}} - int m : --k; // expected-error {{constant expression}} + int n : ++k; // expected-error {{constant expression}} cxx20-note {{visible outside}} + int m : --k; // expected-error {{constant expression}} cxx20-note {{visible outside}} }; } @@ -446,7 +447,7 @@ namespace std { namespace TypeId { struct S { virtual void f(); }; constexpr S *p = 0; - constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} expected-note {{typeid applied to expression of polymorphic type 'TypeId::S'}} + constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'TypeId::S'}} cxx20-note {{dereferenced null pointer}} struct T {} t; constexpr const std::type_info &ti2 = typeid(t); @@ -455,10 +456,10 @@ namespace TypeId { // - a new-expression (5.3.4); // - a delete-expression (5.3.5); namespace NewDelete { - int *p = 0; + constexpr int *p = 0; struct T { - int n : *new int(4); // expected-error {{constant expression}} - int m : (delete p, 2); // expected-error {{constant expression}} + int n : *new int(4); // expected-warning {{constant expression}} cxx11-note {{until C++20}} cxx20-note {{was not deallocated}} + int m : (delete p, 2); // cxx11-warning {{constant expression}} cxx11-note {{until C++20}} }; } @@ -550,8 +551,8 @@ namespace UnspecifiedRelations { namespace Assignment { int k; struct T { - int n : (k = 9); // expected-error {{constant expression}} - int m : (k *= 2); // expected-error {{constant expression}} + int n : (k = 9); // expected-error {{constant expression}} cxx20-note {{visible outside}} + int m : (k *= 2); // expected-error {{constant expression}} cxx20-note {{visible outside}} }; struct Literal { diff --git a/test/CodeGenCXX/const-init-cxx2a.cpp b/test/CodeGenCXX/const-init-cxx2a.cpp new file mode 100644 index 0000000000..499a16ea6d --- /dev/null +++ b/test/CodeGenCXX/const-init-cxx2a.cpp @@ -0,0 +1,5 @@ +// RUN: %clang_cc1 -verify -triple x86_64-apple-darwin -emit-llvm -o - %s -std=c++2a | FileCheck %s +// expected-no-diagnostics + +// CHECK: @a = global i32 123, +int a = (delete new int, 123); diff --git a/test/SemaCXX/builtin-object-size-cxx14.cpp b/test/SemaCXX/builtin-object-size-cxx14.cpp index e0b05883bb..a57bb6b537 100644 --- a/test/SemaCXX/builtin-object-size-cxx14.cpp +++ b/test/SemaCXX/builtin-object-size-cxx14.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only -verify -std=c++14 %s +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++2a %s typedef __SIZE_TYPE__ size_t; @@ -105,4 +106,10 @@ namespace InvalidBase { struct S { const char *name; }; S invalid_base(); constexpr size_t bos_name = __builtin_object_size(invalid_base().name, 1); + static_assert(bos_name == -1, ""); + + struct T { ~T(); }; + T invalid_base_2(); + constexpr size_t bos_dtor = __builtin_object_size(&(T&)(T&&)invalid_base_2(), 0); + static_assert(bos_dtor == -1, ""); } diff --git a/test/SemaCXX/constant-expression-cxx2a.cpp b/test/SemaCXX/constant-expression-cxx2a.cpp index ae627b07b8..73e4951536 100644 --- a/test/SemaCXX/constant-expression-cxx2a.cpp +++ b/test/SemaCXX/constant-expression-cxx2a.cpp @@ -1,11 +1,28 @@ -// RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions -triple=x86_64-linux-gnu +// RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions -triple=x86_64-linux-gnu -Wno-mismatched-new-delete #include "Inputs/std-compare.h" 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; +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; + // Helper to print out values for debugging. constexpr void not_defined(); template constexpr void print(T) { not_defined(); } @@ -752,4 +769,289 @@ namespace dtor { "ca"; } static_assert(check_abnormal_termination()); + + constexpr bool run_dtors_on_array_filler() { + struct S { + int times_destroyed = 0; + constexpr ~S() { if (++times_destroyed != 1) throw "oops"; } + }; + S s[3]; + return true; + } + static_assert(run_dtors_on_array_filler()); +} + +namespace dynamic_alloc { + constexpr int *p = // expected-error {{constant}} expected-note {{pointer to heap-allocated object is not a constant expression}} + new int; // expected-note {{heap allocation performed here}} + + constexpr int f(int n) { + int *p = new int[n]; + for (int i = 0; i != n; ++i) { + p[i] = i; + } + int k = 0; + for (int i = 0; i != n; ++i) { + k += p[i]; + } + delete[] p; + return k; + } + static_assert(f(123) == 123 * 122 / 2); + + constexpr bool nvdtor() { // expected-error {{never produces a constant expression}} + struct S { + constexpr ~S() {} + }; + struct T : S {}; + delete (S*)new T; // expected-note {{delete of object with dynamic type 'T' through pointer to base class type 'S' with non-virtual destructor}} + return true; + } + + constexpr int vdtor_1() { + int a; + struct S { + constexpr S(int *p) : p(p) {} + constexpr virtual ~S() { *p = 1; } + int *p; + }; + struct T : S { + // implicit destructor defined eagerly because it is constexpr and virtual + using S::S; + }; + delete (S*)new T(&a); + return a; + } + static_assert(vdtor_1() == 1); + + constexpr int vdtor_2() { + int a = 0; + struct S { constexpr virtual ~S() {} }; + struct T : S { + constexpr T(int *p) : p(p) {} + constexpr ~T() { ++*p; } + int *p; + }; + S *p = new T{&a}; + delete p; + return a; + } + static_assert(vdtor_2() == 1); + + constexpr int vdtor_3(int mode) { + int a = 0; + struct S { constexpr virtual ~S() {} }; + struct T : S { + constexpr T(int *p) : p(p) {} + constexpr ~T() { ++*p; } + int *p; + }; + S *p = new T[3]{&a, &a, &a}; // expected-note 2{{heap allocation}} + switch (mode) { + case 0: + delete p; // expected-note {{non-array delete used to delete pointer to array object of type 'T [3]'}} + break; + case 1: + // FIXME: This diagnosic isn't great; we should mention the cast to S* + // somewhere in here. + delete[] p; // expected-note {{delete of pointer to subobject '&{*new T [3]#0}[0]'}} + break; + case 2: + delete (T*)p; // expected-note {{non-array delete used to delete pointer to array object of type 'T [3]'}} + break; + case 3: + delete[] (T*)p; + break; + } + return a; + } + static_assert(vdtor_3(0) == 3); // expected-error {{}} expected-note {{in call}} + static_assert(vdtor_3(1) == 3); // expected-error {{}} expected-note {{in call}} + static_assert(vdtor_3(2) == 3); // expected-error {{}} expected-note {{in call}} + static_assert(vdtor_3(3) == 3); + + constexpr void delete_mismatch() { // expected-error {{never produces a constant expression}} + delete[] // expected-note {{array delete used to delete pointer to non-array object of type 'int'}} + new int; // expected-note {{allocation}} + } + + template + constexpr T dynarray(int elems, int i) { + T *p; + if constexpr (sizeof(T) == 1) + p = new T[elems]{"fox"}; // expected-note {{evaluated array bound 3 is too small to hold 4 explicitly initialized elements}} + else + p = new T[elems]{1, 2, 3}; // expected-note {{evaluated array bound 2 is too small to hold 3 explicitly initialized elements}} + T n = p[i]; // expected-note 4{{past-the-end}} + delete [] p; + return n; + } + static_assert(dynarray(4, 0) == 1); + static_assert(dynarray(4, 1) == 2); + static_assert(dynarray(4, 2) == 3); + static_assert(dynarray(4, 3) == 0); + static_assert(dynarray(4, 4) == 0); // expected-error {{constant expression}} expected-note {{in call}} + static_assert(dynarray(3, 2) == 3); + static_assert(dynarray(3, 3) == 0); // expected-error {{constant expression}} expected-note {{in call}} + static_assert(dynarray(2, 1) == 0); // expected-error {{constant expression}} expected-note {{in call}} + static_assert(dynarray(5, 0) == 'f'); + static_assert(dynarray(5, 1) == 'o'); + static_assert(dynarray(5, 2) == 'x'); + static_assert(dynarray(5, 3) == 0); // (from string) + static_assert(dynarray(5, 4) == 0); // (from filler) + static_assert(dynarray(5, 5) == 0); // expected-error {{constant expression}} expected-note {{in call}} + static_assert(dynarray(4, 0) == 'f'); + static_assert(dynarray(4, 1) == 'o'); + static_assert(dynarray(4, 2) == 'x'); + static_assert(dynarray(4, 3) == 0); + static_assert(dynarray(4, 4) == 0); // expected-error {{constant expression}} expected-note {{in call}} + static_assert(dynarray(3, 2) == 'x'); // expected-error {{constant expression}} expected-note {{in call}} + + constexpr bool run_dtors_on_array_filler() { + struct S { + int times_destroyed = 0; + constexpr ~S() { if (++times_destroyed != 1) throw "oops"; } + }; + delete[] new S[3]; + return true; + } + static_assert(run_dtors_on_array_filler()); + + constexpr bool erroneous_array_bound(long long n) { + delete[] new int[n]; // expected-note {{array bound -1 is negative}} expected-note {{array bound 4611686018427387904 is too large}} + return true; + } + static_assert(erroneous_array_bound(3)); + static_assert(erroneous_array_bound(0)); + static_assert(erroneous_array_bound(-1)); // expected-error {{constant expression}} expected-note {{in call}} + static_assert(erroneous_array_bound(1LL << 62)); // expected-error {{constant expression}} expected-note {{in call}} + + constexpr void double_delete() { // expected-error {{never produces a constant expression}} + int *p = new int; + delete p; + delete p; // expected-note {{delete of pointer that has already been deleted}} + } + constexpr bool super_secret_double_delete() { + struct A { + constexpr ~A() { delete this; } // expected-note {{destruction of object that is already being destroyed}} expected-note {{in call}} + }; + delete new A; // expected-note {{in call}} + return true; + } + static_assert(super_secret_double_delete()); // expected-error {{constant expression}} expected-note {{in call}} + + constexpr void use_after_free() { // expected-error {{never produces a constant expression}} + int *p = new int; + delete p; + *p = 1; // expected-note {{assignment to heap allocated object that has been deleted}} + } + constexpr void use_after_free_2() { // expected-error {{never produces a constant expression}} + struct X { constexpr void f() {} }; + X *p = new X; + delete p; + p->f(); // expected-note {{member call on heap allocated object that has been deleted}} + } + + template struct X { + std::size_t n; + char *p; + void dependent(); + }; + template void X::dependent() { + char *p; + // Ensure that we don't try to evaluate these for overflow and crash. These + // are all value-dependent expressions. + p = new char[n]; + p = new (n) char[n]; + p = new char(n); + } +} + +struct placement_new_arg {}; +void *operator new(std::size_t, placement_new_arg); +void operator delete(void*, placement_new_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()); + + constexpr bool bad(int which) { + switch (which) { + case 0: + delete new (placement_new_arg{}) int; // expected-note {{call to placement 'operator new'}} + break; + + case 1: + delete new ClassSpecificNew; // expected-note {{call to class-specific 'operator new'}} + break; + + case 2: + delete new ClassSpecificDelete; // expected-note {{call to class-specific 'operator delete'}} + break; + + case 3: + delete new DestroyingDelete; // expected-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; // expected-note {{placement new expression is not yet supported}} + break; + } + + return true; + } + static_assert(bad(0)); // expected-error {{constant expression}} expected-note {{in call}} + static_assert(bad(1)); // expected-error {{constant expression}} expected-note {{in call}} + static_assert(bad(2)); // expected-error {{constant expression}} expected-note {{in call}} + static_assert(bad(3)); // expected-error {{constant expression}} expected-note {{in call}} + static_assert(bad(4)); // expected-error {{constant expression}} expected-note {{in call}} +} + +namespace delete_random_things { + static_assert((delete new int, true)); + static_assert((delete (int*)0, true)); + int n; // expected-note {{declared here}} + static_assert((delete &n, true)); // expected-error {{}} expected-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)); // expected-error {{}} expected-note {{delete of pointer to subobject '&{*new delete_random_things::A#0}.n'}} + static_assert((delete (new int + 1), true)); // expected-error {{}} expected-note {{delete of pointer '&{*new int#0} + 1' that does not point to complete object}} + static_assert((delete[] (new int[3] + 1), true)); // expected-error {{}} expected-note {{delete of pointer to subobject '&{*new int [3]#0}[1]'}} + static_assert((delete &(int&)(int&&)0, true)); // expected-error {{}} expected-note {{delete of pointer '&0' that does not point to a heap-allocated object}} expected-note {{temporary created here}} +} + +namespace memory_leaks { + static_assert(*new bool(true)); // expected-error {{}} expected-note {{allocation performed here was not deallocated}} + + constexpr bool *f() { return new bool(true); } // expected-note {{allocation performed here was not deallocated}} + static_assert(*f()); // expected-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 } -- 2.40.0