diff options
23 files changed, 325 insertions, 75 deletions
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 6b6a8abc3ab..cbac50daf14 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -808,12 +808,19 @@ struct EvaluatedStmt { /// valid if CheckedICE is true. bool IsICE : 1; + /// Whether this variable is known to have constant destruction. That is, + /// whether running the destructor on the initial value is a side-effect + /// (and doesn't inspect any state that might have changed during program + /// execution). This is currently only computed if the destructor is + /// non-trivial. + bool HasConstantDestruction : 1; + Stmt *Value; APValue Evaluated; - EvaluatedStmt() : WasEvaluated(false), IsEvaluating(false), CheckedICE(false), - CheckingICE(false), IsICE(false) {} - + EvaluatedStmt() + : WasEvaluated(false), IsEvaluating(false), CheckedICE(false), + CheckingICE(false), IsICE(false), HasConstantDestruction(false) {} }; /// Represents a variable declaration or definition. @@ -1267,6 +1274,14 @@ public: /// to untyped APValue if the value could not be evaluated. APValue *getEvaluatedValue() const; + /// Evaluate the destruction of this variable to determine if it constitutes + /// constant destruction. + /// + /// \pre isInitICE() + /// \return \c true if this variable has constant destruction, \c false if + /// not. + bool evaluateDestruction(SmallVectorImpl<PartialDiagnosticAt> &Notes) const; + /// Determines whether it is already known whether the /// initializer is an integral constant expression or not. bool isInitKnownICE() const; @@ -1505,9 +1520,14 @@ public: // has no definition within this source file. bool isKnownToBeDefined() const; - /// Do we need to emit an exit-time destructor for this variable? + /// Is destruction of this variable entirely suppressed? If so, the variable + /// need not have a usable destructor at all. bool isNoDestroy(const ASTContext &) const; + /// Do we need to emit an exit-time destructor for this variable, and if so, + /// what kind? + QualType::DestructionKind needsDestruction(const ASTContext &Ctx) const; + // Implement isa/cast/dyncast/etc. static bool classof(const Decl *D) { return classofKind(D->getKind()); } static bool classofKind(Kind K) { return K >= firstVar && K <= lastVar; } diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 7f935d4fa6e..eb2a1f02fd8 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -145,8 +145,10 @@ def note_constexpr_access_volatile_obj : Note< "a constant expression">; def note_constexpr_volatile_here : Note< "volatile %select{temporary created|object declared|member declared}0 here">; -def note_constexpr_ltor_mutable : Note< - "read of mutable member %0 is not allowed in a constant expression">; +def note_constexpr_access_mutable : Note< + "%select{read of|read of|assignment to|increment of|decrement of|" + "member call on|dynamic_cast of|typeid applied to|destruction of}0 " + "mutable member %1 is not allowed in a constant expression">; def note_constexpr_ltor_non_const_int : Note< "read of non-const variable %0 is not allowed in a constant expression">; def note_constexpr_ltor_non_constexpr : Note< diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index adc658bb294..d1b9aea0294 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2384,6 +2384,8 @@ def err_constexpr_var_non_literal : Error< "constexpr variable cannot have non-literal type %0">; def err_constexpr_var_requires_const_init : Error< "constexpr variable %0 must be initialized by a constant expression">; +def err_constexpr_var_requires_const_destruction : Error< + "constexpr variable %0 must have constant destruction">; def err_constexpr_redecl_mismatch : Error< "%select{non-constexpr|constexpr|consteval}1 declaration of %0" " follows %select{non-constexpr|constexpr|consteval}2 declaration">; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 091b2fe3462..f6919938d5a 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -10064,7 +10064,7 @@ bool ASTContext::DeclMustBeEmitted(const Decl *D) { return false; // Variables that have destruction with side-effects are required. - if (VD->getType().isDestructedType()) + if (VD->needsDestruction(*this)) return true; // Variables that have initialization with side-effects are required. diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 19a012b332d..9ebf1c32629 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2592,6 +2592,18 @@ bool VarDecl::isNoDestroy(const ASTContext &Ctx) const { !hasAttr<AlwaysDestroyAttr>())); } +QualType::DestructionKind +VarDecl::needsDestruction(const ASTContext &Ctx) const { + if (EvaluatedStmt *Eval = Init.dyn_cast<EvaluatedStmt *>()) + if (Eval->HasConstantDestruction) + return QualType::DK_none; + + if (isNoDestroy(Ctx)) + return QualType::DK_none; + + return getType().isDestructedType(); +} + MemberSpecializationInfo *VarDecl::getMemberSpecializationInfo() const { if (isStaticDataMember()) // FIXME: Remove ? diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 1b3ace087e4..33608db6e42 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -744,6 +744,15 @@ namespace { /// evaluated, if any. APValue::LValueBase EvaluatingDecl; + enum class EvaluatingDeclKind { + None, + /// We're evaluating the construction of EvaluatingDecl. + Ctor, + /// We're evaluating the destruction of EvaluatingDecl. + Dtor, + }; + EvaluatingDeclKind IsEvaluatingDecl = EvaluatingDeclKind::None; + /// EvaluatingDeclValue - This is the value being constructed for the /// declaration whose initializer is being evaluated, if any. APValue *EvaluatingDeclValue; @@ -902,8 +911,10 @@ namespace { discardCleanups(); } - void setEvaluatingDecl(APValue::LValueBase Base, APValue &Value) { + void setEvaluatingDecl(APValue::LValueBase Base, APValue &Value, + EvaluatingDeclKind EDK = EvaluatingDeclKind::Ctor) { EvaluatingDecl = Base; + IsEvaluatingDecl = EDK; EvaluatingDeclValue = &Value; } @@ -2913,8 +2924,8 @@ static bool isReadByLvalueToRvalueConversion(QualType T) { /// Diagnose an attempt to read from any unreadable field within the specified /// type, which might be a class type. -static bool diagnoseUnreadableFields(EvalInfo &Info, const Expr *E, - QualType T) { +static bool diagnoseMutableFields(EvalInfo &Info, const Expr *E, AccessKinds AK, + QualType T) { CXXRecordDecl *RD = T->getBaseElementTypeUnsafe()->getAsCXXRecordDecl(); if (!RD) return false; @@ -2929,17 +2940,17 @@ static bool diagnoseUnreadableFields(EvalInfo &Info, const Expr *E, // FIXME: Add core issue number for the union case. if (Field->isMutable() && (RD->isUnion() || isReadByLvalueToRvalueConversion(Field->getType()))) { - Info.FFDiag(E, diag::note_constexpr_ltor_mutable, 1) << Field; + Info.FFDiag(E, diag::note_constexpr_access_mutable, 1) << AK << Field; Info.Note(Field->getLocation(), diag::note_declared_at); return true; } - if (diagnoseUnreadableFields(Info, E, Field->getType())) + if (diagnoseMutableFields(Info, E, AK, Field->getType())) return true; } for (auto &BaseSpec : RD->bases()) - if (diagnoseUnreadableFields(Info, E, BaseSpec.getType())) + if (diagnoseMutableFields(Info, E, AK, BaseSpec.getType())) return true; // All mutable fields were empty, and thus not actually read. @@ -2947,7 +2958,8 @@ static bool diagnoseUnreadableFields(EvalInfo &Info, const Expr *E, } static bool lifetimeStartedInEvaluation(EvalInfo &Info, - APValue::LValueBase Base) { + APValue::LValueBase Base, + bool MutableSubobject = false) { // A temporary we created. if (Base.getCallIndex()) return true; @@ -2956,19 +2968,42 @@ static bool lifetimeStartedInEvaluation(EvalInfo &Info, if (!Evaluating) return false; - // The variable whose initializer we're evaluating. - if (auto *BaseD = Base.dyn_cast<const ValueDecl*>()) - if (declaresSameEntity(Evaluating, BaseD)) - return true; + auto *BaseD = Base.dyn_cast<const ValueDecl*>(); - // A temporary lifetime-extended by the variable whose initializer we're - // evaluating. - if (auto *BaseE = Base.dyn_cast<const Expr *>()) - if (auto *BaseMTE = dyn_cast<MaterializeTemporaryExpr>(BaseE)) - if (declaresSameEntity(BaseMTE->getExtendingDecl(), Evaluating)) - return true; + switch (Info.IsEvaluatingDecl) { + case EvalInfo::EvaluatingDeclKind::None: + return false; - return false; + case EvalInfo::EvaluatingDeclKind::Ctor: + // The variable whose initializer we're evaluating. + if (BaseD) + return declaresSameEntity(Evaluating, BaseD); + + // A temporary lifetime-extended by the variable whose initializer we're + // evaluating. + if (auto *BaseE = Base.dyn_cast<const Expr *>()) + if (auto *BaseMTE = dyn_cast<MaterializeTemporaryExpr>(BaseE)) + return declaresSameEntity(BaseMTE->getExtendingDecl(), Evaluating); + return false; + + case EvalInfo::EvaluatingDeclKind::Dtor: + // C++2a [expr.const]p6: + // [during constant destruction] the lifetime of a and its non-mutable + // subobjects (but not its mutable subobjects) [are] considered to start + // within e. + // + // FIXME: We can meaningfully extend this to cover non-const objects, but + // we will need special handling: we should be able to access only + // subobjects of such objects that are themselves declared const. + if (!BaseD || + !(BaseD->getType().isConstQualified() || + BaseD->getType()->isReferenceType()) || + MutableSubobject) + return false; + return declaresSameEntity(Evaluating, BaseD); + } + + llvm_unreachable("unknown evaluating decl kind"); } namespace { @@ -2986,13 +3021,13 @@ struct CompleteObject { CompleteObject(APValue::LValueBase Base, APValue *Value, QualType Type) : Base(Base), Value(Value), Type(Type) {} - bool mayReadMutableMembers(EvalInfo &Info) const { + bool mayAccessMutableMembers(EvalInfo &Info, AccessKinds AK) const { // In C++14 onwards, it is permitted to read a mutable member whose // lifetime began within the evaluation. // FIXME: Should we also allow this in C++11? if (!Info.getLangOpts().CPlusPlus14) return false; - return lifetimeStartedInEvaluation(Info, Base); + return lifetimeStartedInEvaluation(Info, Base, /*MutableSubobject*/true); } explicit operator bool() const { return !Type.isNull(); } @@ -3097,9 +3132,9 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, // things we need to check: if there are any mutable subobjects, we // cannot perform this read. (This only happens when performing a trivial // copy or assignment.) - if (ObjType->isRecordType() && isRead(handler.AccessKind) && - !Obj.mayReadMutableMembers(Info) && - diagnoseUnreadableFields(Info, E, ObjType)) + if (ObjType->isRecordType() && + !Obj.mayAccessMutableMembers(Info, handler.AccessKind) && + diagnoseMutableFields(Info, E, handler.AccessKind, ObjType)) return handler.failed(); } @@ -3167,10 +3202,10 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, : O->getComplexFloatReal(), ObjType); } } else if (const FieldDecl *Field = getAsField(Sub.Entries[I])) { - if (Field->isMutable() && isRead(handler.AccessKind) && - !Obj.mayReadMutableMembers(Info)) { - Info.FFDiag(E, diag::note_constexpr_ltor_mutable, 1) - << Field; + if (Field->isMutable() && + !Obj.mayAccessMutableMembers(Info, handler.AccessKind)) { + Info.FFDiag(E, diag::note_constexpr_access_mutable, 1) + << handler.AccessKind << Field; Info.Note(Field->getLocation(), diag::note_declared_at); return handler.failed(); } @@ -3427,8 +3462,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, // the variable we're reading must be const. if (!Frame) { if (Info.getLangOpts().CPlusPlus14 && - declaresSameEntity( - VD, Info.EvaluatingDecl.dyn_cast<const ValueDecl *>())) { + lifetimeStartedInEvaluation(Info, LVal.Base)) { // OK, we can read and modify an object if we're in the process of // evaluating its initializer, because its lifetime began in this // evaluation. @@ -3518,11 +3552,14 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, // int x = ++r; // constexpr int k = r; // Therefore we use the C++14 rules in C++11 too. - const ValueDecl *VD = Info.EvaluatingDecl.dyn_cast<const ValueDecl*>(); - const ValueDecl *ED = MTE->getExtendingDecl(); + // + // Note that temporaries whose lifetimes began while evaluating a + // variable's constructor are not usable while evaluating the + // corresponding destructor, not even if they're of const-qualified + // types. if (!(BaseType.isConstQualified() && BaseType->isIntegralOrEnumerationType()) && - !(VD && VD->getCanonicalDecl() == ED->getCanonicalDecl())) { + !lifetimeStartedInEvaluation(Info, LVal.Base)) { if (!IsAccess) return CompleteObject(LVal.getLValueBase(), nullptr, BaseType); Info.FFDiag(E, diag::note_constexpr_access_static_temporary, 1) << AK; @@ -13282,6 +13319,41 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx, CheckMemoryLeaks(Info); } +bool VarDecl::evaluateDestruction( + SmallVectorImpl<PartialDiagnosticAt> &Notes) const { + assert(getEvaluatedValue() && !getEvaluatedValue()->isAbsent() && + "cannot evaluate destruction of non-constant-initialized variable"); + + Expr::EvalStatus EStatus; + EStatus.Diag = &Notes; + + // Make a copy of the value for the destructor to mutate. + APValue DestroyedValue = *getEvaluatedValue(); + + EvalInfo Info(getASTContext(), EStatus, EvalInfo::EM_ConstantExpression); + Info.setEvaluatingDecl(this, DestroyedValue, + EvalInfo::EvaluatingDeclKind::Dtor); + Info.InConstantContext = true; + + SourceLocation DeclLoc = getLocation(); + QualType DeclTy = getType(); + + LValue LVal; + LVal.set(this); + + // FIXME: Consider storing whether this variable has constant destruction in + // the EvaluatedStmt so that CodeGen can query it. + if (!HandleDestruction(Info, DeclLoc, LVal.Base, DestroyedValue, DeclTy) || + EStatus.HasSideEffects) + return false; + + if (!Info.discardCleanups()) + llvm_unreachable("Unhandled cleanup; missing full expression marker?"); + + ensureEvaluatedStmt()->HasConstantDestruction = true; + return true; +} + /// isEvaluatable - Call EvaluateAsRValue to see if this expression can be /// constant folded, but discard the result. bool Expr::isEvaluatable(const ASTContext &Ctx, SideEffectsKind SEK) const { diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp index c9ace131bbd..1a8109cedf7 100644 --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -275,7 +275,7 @@ bool CheckMutable(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { const SourceInfo &Loc = S.Current->getSource(OpPC); const FieldDecl *Field = Ptr.getField(); - S.FFDiag(Loc, diag::note_constexpr_ltor_mutable, 1) << Field; + S.FFDiag(Loc, diag::note_constexpr_access_mutable, 1) << AK_Read << Field; S.Note(Field->getLocation(), diag::note_declared_at); return false; } diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp index d1915fdad2e..a540346ad45 100644 --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -1384,6 +1384,8 @@ void TextNodeDumper::VisitVarDecl(const VarDecl *D) { break; } } + if (D->needsDestruction(D->getASTContext())) + OS << " destroyed"; if (D->isParameterPack()) OS << " pack"; } diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 7fa262ca6e8..0a0ba70d822 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -3093,7 +3093,7 @@ void CodeGenFunction::EmitDelegateCallArg(CallArgList &args, // Deactivate the cleanup for the callee-destructed param that was pushed. if (hasAggregateEvaluationKind(type) && !CurFuncIsThunk && type->getAs<RecordType>()->getDecl()->isParamDestroyedInCallee() && - type.isDestructedType()) { + param->needsDestruction(getContext())) { EHScopeStack::stable_iterator cleanup = CalleeDestructedParamCleanups.lookup(cast<ParmVarDecl>(param)); assert(cleanup.isValid() && diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index c8bb63c5c4b..76ab8852397 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -2083,7 +2083,7 @@ static bool canEmitDelegateCallArgs(CodeGenFunction &CGF, if (CGF.getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) { // If the parameters are callee-cleanup, it's not safe to forward. for (auto *P : Ctor->parameters()) - if (P->getType().isDestructedType()) + if (P->needsDestruction(CGF.getContext())) return false; // Likewise if they're inalloca. diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index a9e01619aab..5c0d52a1633 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -305,14 +305,6 @@ llvm::Constant *CodeGenModule::getOrCreateStaticVarDecl( return Addr; } -/// hasNontrivialDestruction - Determine whether a type's destruction is -/// non-trivial. If so, and the variable uses static initialization, we must -/// register its destructor to run on exit. -static bool hasNontrivialDestruction(QualType T) { - CXXRecordDecl *RD = T->getBaseElementTypeUnsafe()->getAsCXXRecordDecl(); - return RD && !RD->hasTrivialDestructor(); -} - /// AddInitializerToStaticVarDecl - Add the initializer for 'D' to the /// global variable that has already been created for it. If the initializer /// has a different type than GV does, this may free GV and return a different @@ -372,7 +364,7 @@ CodeGenFunction::AddInitializerToStaticVarDecl(const VarDecl &D, emitter.finalize(GV); - if (hasNontrivialDestruction(D.getType()) && HaveInsertPoint()) { + if (D.needsDestruction(getContext()) && HaveInsertPoint()) { // We have a constant initializer, but a nontrivial destructor. We still // need to perform a guarded "initialization" in order to register the // destructor. @@ -1994,7 +1986,7 @@ void CodeGenFunction::EmitAutoVarCleanups(const AutoVarEmission &emission) { const VarDecl &D = *emission.Variable; // Check the type for a cleanup. - if (QualType::DestructionKind dtorKind = D.getType().isDestructedType()) + if (QualType::DestructionKind dtorKind = D.needsDestruction(getContext())) emitAutoVarTypeCleanup(emission, dtorKind); // In GC mode, honor objc_precise_lifetime. @@ -2404,7 +2396,8 @@ void CodeGenFunction::EmitParmDecl(const VarDecl &D, ParamValue Arg, // cleanup. if (hasAggregateEvaluationKind(Ty) && !CurFuncIsThunk && Ty->getAs<RecordType>()->getDecl()->isParamDestroyedInCallee()) { - if (QualType::DestructionKind DtorKind = Ty.isDestructedType()) { + if (QualType::DestructionKind DtorKind = + D.needsDestruction(getContext())) { assert((DtorKind == QualType::DK_cxx_destructor || DtorKind == QualType::DK_nontrivial_c_struct) && "unexpected destructor type"); diff --git a/clang/lib/CodeGen/CGDeclCXX.cpp b/clang/lib/CodeGen/CGDeclCXX.cpp index 7a0605b8450..a54e5dcfda2 100644 --- a/clang/lib/CodeGen/CGDeclCXX.cpp +++ b/clang/lib/CodeGen/CGDeclCXX.cpp @@ -73,16 +73,10 @@ static void EmitDeclDestroy(CodeGenFunction &CGF, const VarDecl &D, // that isn't balanced out by a destructor call as intended by the // attribute. This also checks for -fno-c++-static-destructors and // bails even if the attribute is not present. - if (D.isNoDestroy(CGF.getContext())) - return; - - CodeGenModule &CGM = CGF.CGM; + QualType::DestructionKind DtorKind = D.needsDestruction(CGF.getContext()); // FIXME: __attribute__((cleanup)) ? - QualType Type = D.getType(); - QualType::DestructionKind DtorKind = Type.isDestructedType(); - switch (DtorKind) { case QualType::DK_none: return; @@ -101,6 +95,9 @@ static void EmitDeclDestroy(CodeGenFunction &CGF, const VarDecl &D, llvm::FunctionCallee Func; llvm::Constant *Argument; + CodeGenModule &CGM = CGF.CGM; + QualType Type = D.getType(); + // Special-case non-array C++ destructors, if they have the right signature. // Under some ABIs, destructors return this instead of void, and cannot be // passed directly to __cxa_atexit if the target does not allow this diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 486832bca5f..37e2533e976 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -3809,9 +3809,9 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D, return; llvm::Constant *Init = nullptr; - CXXRecordDecl *RD = ASTTy->getBaseElementTypeUnsafe()->getAsCXXRecordDecl(); bool NeedsGlobalCtor = false; - bool NeedsGlobalDtor = RD && !RD->hasTrivialDestructor(); + bool NeedsGlobalDtor = + D->needsDestruction(getContext()) == QualType::DK_cxx_destructor; const VarDecl *InitDecl; const Expr *InitExpr = D->getAnyInitializer(InitDecl); diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp index 037185390c9..35e707243ea 100644 --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -350,7 +350,7 @@ public: // If we have the only definition, we don't need a thread wrapper if we // will emit the value as a constant. if (isUniqueGVALinkage(getContext().GetGVALinkageForVariable(VD))) - return !VD->getType().isDestructedType() && InitDecl->evaluateValue(); + return !VD->needsDestruction(getContext()) && InitDecl->evaluateValue(); // Otherwise, we need a thread wrapper unless we know that every // translation unit will emit the value as a constant. We rely on diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index c03e4dc66f8..d21d2689123 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -13398,6 +13398,19 @@ void Sema::FinalizeVarWithDestructor(VarDecl *VD, const RecordType *Record) { } if (Destructor->isTrivial()) return; + + // If the destructor is constexpr, check whether the variable has constant + // destruction now. + if (Destructor->isConstexpr() && VD->evaluateValue()) { + SmallVector<PartialDiagnosticAt, 8> Notes; + if (!VD->evaluateDestruction(Notes) && VD->isConstexpr()) { + Diag(VD->getLocation(), + diag::err_constexpr_var_requires_const_destruction) << VD; + for (unsigned I = 0, N = Notes.size(); I != N; ++I) + Diag(Notes[I].first, Notes[I].second); + } + } + if (!VD->hasGlobalStorage()) return; // Emit warning for non-trivial dtor in global scope (a real global, diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp index e9cf366983f..76928182a1e 100644 --- a/clang/lib/Serialization/ASTReaderDecl.cpp +++ b/clang/lib/Serialization/ASTReaderDecl.cpp @@ -1390,10 +1390,11 @@ ASTDeclReader::RedeclarableResult ASTDeclReader::VisitVarDeclImpl(VarDecl *VD) { if (uint64_t Val = Record.readInt()) { VD->setInit(Record.readExpr()); - if (Val > 1) { // IsInitKnownICE = 1, IsInitNotICE = 2, IsInitICE = 3 + if (Val > 1) { EvaluatedStmt *Eval = VD->ensureEvaluatedStmt(); Eval->CheckedICE = true; - Eval->IsICE = Val == 3; + Eval->IsICE = (Val & 1) != 0; + Eval->HasConstantDestruction = (Val & 4) != 0; } } diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp index b71315505de..2c22587957e 100644 --- a/clang/lib/Serialization/ASTWriterDecl.cpp +++ b/clang/lib/Serialization/ASTWriterDecl.cpp @@ -968,7 +968,14 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) { Record.push_back(D->getLinkageInternal()); if (D->getInit()) { - Record.push_back(!D->isInitKnownICE() ? 1 : (D->isInitICE() ? 3 : 2)); + if (!D->isInitKnownICE()) + Record.push_back(1); + else { + Record.push_back( + 2 | + (D->isInitICE() ? 1 : 0) | + (D->ensureEvaluatedStmt()->HasConstantDestruction ? 4 : 0)); + } Record.AddStmt(D->getInit()); } else { Record.push_back(0); @@ -2140,7 +2147,7 @@ void ASTWriter::WriteDeclAbbrevs() { Abv->Add(BitCodeAbbrevOp(0)); // ImplicitParamKind Abv->Add(BitCodeAbbrevOp(0)); // EscapingByref Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 3)); // Linkage - Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 2)); // IsInitICE (local) + Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 3)); // IsInitICE (local) Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 2)); // VarKind (local enum) // Type Source Info Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); diff --git a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp index 0aaedcc0769..8d51dbde717 100644 --- a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp +++ b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++2a %s // A constexpr specifier used in an object declaration declares the object as // const. @@ -35,3 +36,19 @@ struct pixel { }; constexpr pixel ur = { 1294, 1024 }; // ok constexpr pixel origin; // expected-error {{default initialization of an object of const type 'const pixel' without a user-provided default constructor}} + +#if __cplusplus > 201702L +// A constexpr variable shall have constant destruction. +struct A { + bool ok; + constexpr A(bool ok) : ok(ok) {} + constexpr ~A() noexcept(false) { + void oops(); // expected-note 2{{declared here}} + if (!ok) oops(); // expected-note 2{{non-constexpr function}} + } +}; + +constexpr A const_dtor(true); +constexpr A non_const_dtor(false); // expected-error {{must have constant destruction}} expected-note {{in call}} +constexpr A arr_dtor[5] = {true, true, true, false, true}; // expected-error {{must have constant destruction}} expected-note {{in call to '&arr_dtor[3]->~A()'}} +#endif diff --git a/clang/test/CXX/expr/expr.const/p6-2a.cpp b/clang/test/CXX/expr/expr.const/p6-2a.cpp new file mode 100644 index 00000000000..312c2835418 --- /dev/null +++ b/clang/test/CXX/expr/expr.const/p6-2a.cpp @@ -0,0 +1,43 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s + +constexpr int non_class = 42; +constexpr int arr_non_class[5] = {1, 2, 3}; + +struct A { + int member = 1; + constexpr ~A() { member = member + 1; } +}; +constexpr A class_ = {}; +constexpr A arr_class[5] = {{}, {}}; + +struct Mutable { + mutable int member = 1; // expected-note {{declared here}} + constexpr ~Mutable() { member = member + 1; } // expected-note {{read of mutable member}} +}; +constexpr Mutable mut_member; // expected-error {{must have constant destruction}} expected-note {{in call}} + +struct MutableStore { + mutable int member = 1; // expected-note {{declared here}} + constexpr ~MutableStore() { member = 2; } // expected-note {{assignment to mutable member}} +}; +constexpr MutableStore mut_store; // expected-error {{must have constant destruction}} expected-note {{in call}} + +// Note: the constant destruction rules disallow this example even though hcm.n is a const object. +struct MutableConst { + struct HasConstMember { + const int n = 4; + }; + mutable HasConstMember hcm; // expected-note {{here}} + constexpr ~MutableConst() { + int q = hcm.n; // expected-note {{read of mutable}} + } +}; +constexpr MutableConst mc; // expected-error {{must have constant destruction}} expected-note {{in call}} + +struct Temporary { + int &&temp; + constexpr ~Temporary() { + int n = temp; // expected-note {{outside the expression that created the temporary}} + } +}; +constexpr Temporary t = {3}; // expected-error {{must have constant destruction}} expected-note {{created here}} expected-note {{in call}} diff --git a/clang/test/CodeGenCXX/attr-no-destroy-d54344.cpp b/clang/test/CodeGenCXX/attr-no-destroy-d54344.cpp index 2e004d6426d..b03791e5135 100644 --- a/clang/test/CodeGenCXX/attr-no-destroy-d54344.cpp +++ b/clang/test/CodeGenCXX/attr-no-destroy-d54344.cpp @@ -14,6 +14,7 @@ class a { public: + a(); ~a(); }; class logger_base { diff --git a/clang/test/CodeGenCXX/const-init-cxx2a.cpp b/clang/test/CodeGenCXX/const-init-cxx2a.cpp index 499a16ea6dd..1195b912c25 100644 --- a/clang/test/CodeGenCXX/const-init-cxx2a.cpp +++ b/clang/test/CodeGenCXX/const-init-cxx2a.cpp @@ -1,5 +1,58 @@ -// RUN: %clang_cc1 -verify -triple x86_64-apple-darwin -emit-llvm -o - %s -std=c++2a | FileCheck %s -// expected-no-diagnostics +// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -o - %s -std=c++2a | FileCheck %s --implicit-check-not=cxx_global_var_init --implicit-check-not=cxa_atexit + +// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-pch -o %t.pch %s -std=c++2a +// RUN: %clang_cc1 -triple x86_64-linux-gnu -include-pch %t.pch -x c++ /dev/null -emit-llvm -o - -std=c++2a | FileCheck %s --implicit-check-not=cxx_global_var_init --implicit-check-not=cxa_atexit // CHECK: @a = global i32 123, int a = (delete new int, 123); + +struct B { + constexpr B() {} + constexpr ~B() { n *= 5; } + int n = 123; +}; +// CHECK: @b = global {{.*}} i32 123 +extern constexpr B b = B(); + +// CHECK: @_ZL1c = internal global {{.*}} i32 123 +const B c; +int use_c() { return c.n; } + +struct D { + int n; + constexpr ~D() {} +}; +D d; +// CHECK: @d = global {{.*}} zeroinitializer + +D d_arr[3]; +// CHECK: @d_arr = global {{.*}} zeroinitializer + +thread_local D d_tl; +// CHECK: @d_tl = thread_local global {{.*}} zeroinitializer + +// CHECK-NOT: @llvm.global_ctors + +// CHECK-LABEL: define {{.*}} @_Z1fv( +void f() { + // CHECK-NOT: call + // CHECK: call {{.*}}memcpy + // CHECK-NOT: call + // CHECK: call {{.*}}memset + // CHECK-NOT: call + // CHECK: } + constexpr B b; + D d = D(); +} + +// CHECK-LABEL: define {{.*}} @_Z1gv( +void g() { + // CHECK-NOT: call + // CHECK-NOT: cxa_guard + // CHECK-NOT: _ZGV + // CHECK: } + static constexpr B b1; + static const B b2; + static D d; + thread_local D d_tl; +} diff --git a/clang/test/CodeGenCXX/no_destroy.cpp b/clang/test/CodeGenCXX/no_destroy.cpp index 3400b6080b5..607cbfb3a1f 100644 --- a/clang/test/CodeGenCXX/no_destroy.cpp +++ b/clang/test/CodeGenCXX/no_destroy.cpp @@ -5,10 +5,8 @@ struct NonTrivial { ~NonTrivial(); }; -// CHECK-LABEL: define internal void @__cxx_global_var_init // CHECK-NOT: __cxa_atexit{{.*}}_ZN10NonTrivialD1Ev [[clang::no_destroy]] NonTrivial nt1; -// CHECK-LABEL: define internal void @__cxx_global_var_init // CHECK-NOT: _tlv_atexit{{.*}}_ZN10NonTrivialD1Ev [[clang::no_destroy]] thread_local NonTrivial nt2; @@ -16,11 +14,9 @@ struct NonTrivial2 { ~NonTrivial2(); }; -// CHECK-LABEL: define internal void @__cxx_global_var_init -// CHECK: __cxa_atexit{{.*}}_ZN11NonTrivial2D1Ev +// CHECK: __cxa_atexit{{.*}}_ZN11NonTrivial2D1Ev{{.*}}nt21 NonTrivial2 nt21; -// CHECK-LABEL: define internal void @__cxx_global_var_init -// CHECK: _tlv_atexit{{.*}}_ZN11NonTrivial2D1Ev +// CHECK: _tlv_atexit{{.*}}_ZN11NonTrivial2D1Ev{{.*}}nt22 thread_local NonTrivial2 nt22; // CHECK-LABEL: define void @_Z1fv diff --git a/clang/test/CodeGenCXX/non-const-init-cxx2a.cpp b/clang/test/CodeGenCXX/non-const-init-cxx2a.cpp new file mode 100644 index 00000000000..120b32090fb --- /dev/null +++ b/clang/test/CodeGenCXX/non-const-init-cxx2a.cpp @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-llvm -o - %s -std=c++2a | FileCheck %s + +// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-pch -o %t.pch %s -std=c++2a +// RUN: %clang_cc1 -triple x86_64-apple-darwin -include-pch %t.pch -x c++ /dev/null -emit-llvm -o - -std=c++2a | FileCheck %s + +struct B { + constexpr B() {} + constexpr ~B() { n *= 5; } + int n = 123; +}; + +// We emit a dynamic destructor here because b.n might have been modified +// before b is destroyed. +// +// CHECK: @b = global {{.*}} i32 123 +B b = B(); + +// CHECK: define {{.*}}cxx_global_var_init +// CHECK: call {{.*}} @__cxa_atexit({{.*}} @_ZN1BD1Ev {{.*}} @b |