diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -671,6 +671,11 @@ - Implemented DR2358 allowing init captures in lambdas in default arguments. - implemented `DR2654 `_ which undeprecates all compound assignements operations on volatile qualified variables. +- Implemented DR2631. Invalid ``consteval`` calls in default arguments and default + member initializers are diagnosed when and if the default is used. + This Fixes `Issue 56379 `_ + and changes the value of ``std::source_location::current()`` + used in default parameters calls compared to previous versions of Clang. C++20 Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -1244,8 +1244,12 @@ /// This wraps up a function call argument that was created from the /// corresponding parameter's default argument, when the call did not /// explicitly supply arguments for all of the parameters. -class CXXDefaultArgExpr final : public Expr { +class CXXDefaultArgExpr final + : public Expr, + private llvm::TrailingObjects { friend class ASTStmtReader; + friend class ASTReader; + friend TrailingObjects; /// The parameter whose default is being used. ParmVarDecl *Param; @@ -1254,7 +1258,7 @@ DeclContext *UsedContext; CXXDefaultArgExpr(StmtClass SC, SourceLocation Loc, ParmVarDecl *Param, - DeclContext *UsedContext) + Expr *RewrittenExpr, DeclContext *UsedContext) : Expr(SC, Param->hasUnparsedDefaultArg() ? Param->getType().getNonReferenceType() @@ -1263,28 +1267,54 @@ Param->getDefaultArg()->getObjectKind()), Param(Param), UsedContext(UsedContext) { CXXDefaultArgExprBits.Loc = Loc; + CXXDefaultArgExprBits.HasRewrittenInit = RewrittenExpr != nullptr; + if (RewrittenExpr) + *getTrailingObjects() = RewrittenExpr; setDependence(computeDependence(this)); } + CXXDefaultArgExpr(EmptyShell Empty, bool HasRewrittenInit) + : Expr(CXXDefaultArgExprClass, Empty) { + CXXDefaultArgExprBits.HasRewrittenInit = HasRewrittenInit; + } + public: - CXXDefaultArgExpr(EmptyShell Empty) : Expr(CXXDefaultArgExprClass, Empty) {} + static CXXDefaultArgExpr *CreateEmpty(const ASTContext &C, + bool HasRewrittenInit); // \p Param is the parameter whose default argument is used by this // expression. static CXXDefaultArgExpr *Create(const ASTContext &C, SourceLocation Loc, - ParmVarDecl *Param, - DeclContext *UsedContext) { - return new (C) - CXXDefaultArgExpr(CXXDefaultArgExprClass, Loc, Param, UsedContext); - } - + ParmVarDecl *Param, Expr *RewrittenExpr, + DeclContext *UsedContext); // Retrieve the parameter that the argument was created from. const ParmVarDecl *getParam() const { return Param; } ParmVarDecl *getParam() { return Param; } - // Retrieve the actual argument to the function call. - const Expr *getExpr() const { return getParam()->getDefaultArg(); } - Expr *getExpr() { return getParam()->getDefaultArg(); } + bool hasRewrittenInit() const { + return CXXDefaultArgExprBits.HasRewrittenInit; + } + + // Retrieve the argument to the function call. + Expr *getExpr(); + const Expr *getExpr() const { + return const_cast(this)->getExpr(); + } + + Expr *getRewrittenExpr() { + return hasRewrittenInit() ? *getTrailingObjects() : nullptr; + } + + const Expr *getRewrittenExpr() const { + return const_cast(this)->getRewrittenExpr(); + } + + // Retrieve the rewritten init expression (for an init expression containing + // immediate calls) with the top level FullExpr and ConstantExpr stripped off. + Expr *getAdjustedRewrittenExpr(); + const Expr *getAdjustedRewrittenExpr() const { + return const_cast(this)->getAdjustedRewrittenExpr(); + } const DeclContext *getUsedContext() const { return UsedContext; } DeclContext *getUsedContext() { return UsedContext; } @@ -1321,10 +1351,13 @@ /// is implicitly used in a mem-initializer-list in a constructor /// (C++11 [class.base.init]p8) or in aggregate initialization /// (C++1y [dcl.init.aggr]p7). -class CXXDefaultInitExpr : public Expr { - friend class ASTReader; - friend class ASTStmtReader; +class CXXDefaultInitExpr final + : public Expr, + private llvm::TrailingObjects { + friend class ASTStmtReader; + friend class ASTReader; + friend TrailingObjects; /// The field whose default is being used. FieldDecl *Field; @@ -1332,16 +1365,25 @@ DeclContext *UsedContext; CXXDefaultInitExpr(const ASTContext &Ctx, SourceLocation Loc, - FieldDecl *Field, QualType Ty, DeclContext *UsedContext); + FieldDecl *Field, QualType Ty, DeclContext *UsedContext, + Expr *RewrittenInitExpr); - CXXDefaultInitExpr(EmptyShell Empty) : Expr(CXXDefaultInitExprClass, Empty) {} + CXXDefaultInitExpr(EmptyShell Empty, bool HasRewrittenInit) + : Expr(CXXDefaultInitExprClass, Empty) { + CXXDefaultInitExprBits.HasRewrittenInit = HasRewrittenInit; + } public: + static CXXDefaultInitExpr *CreateEmpty(const ASTContext &C, + bool HasRewrittenInit); /// \p Field is the non-static data member whose default initializer is used /// by this expression. static CXXDefaultInitExpr *Create(const ASTContext &Ctx, SourceLocation Loc, - FieldDecl *Field, DeclContext *UsedContext) { - return new (Ctx) CXXDefaultInitExpr(Ctx, Loc, Field, Field->getType(), UsedContext); + FieldDecl *Field, DeclContext *UsedContext, + Expr *RewrittenInitExpr); + + bool hasRewrittenInit() const { + return CXXDefaultInitExprBits.HasRewrittenInit; } /// Get the field whose initializer will be used. @@ -1349,13 +1391,23 @@ const FieldDecl *getField() const { return Field; } /// Get the initialization expression that will be used. + Expr *getExpr(); const Expr *getExpr() const { - assert(Field->getInClassInitializer() && "initializer hasn't been parsed"); - return Field->getInClassInitializer(); + return const_cast(this)->getExpr(); } - Expr *getExpr() { - assert(Field->getInClassInitializer() && "initializer hasn't been parsed"); - return Field->getInClassInitializer(); + + /// Retrieve the initializing expression with evaluated immediate calls, if + /// any. + const Expr *getRewrittenExpr() const { + assert(hasRewrittenInit() && "expected a rewritten init expression"); + return *getTrailingObjects(); + } + + /// Retrieve the initializing expression with evaluated immediate calls, if + /// any. + Expr *getRewrittenExpr() { + assert(hasRewrittenInit() && "expected a rewritten init expression"); + return *getTrailingObjects(); } const DeclContext *getUsedContext() const { return UsedContext; } diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -690,6 +690,9 @@ unsigned : NumExprBits; + /// Whether this CXXDefaultArgExpr rewrote its argument and stores a copy. + unsigned HasRewrittenInit : 1; + /// The location where the default argument expression was used. SourceLocation Loc; }; @@ -700,6 +703,10 @@ unsigned : NumExprBits; + /// Whether this CXXDefaultInitExprBitfields rewrote its argument and stores + /// a copy. + unsigned HasRewrittenInit : 1; + /// The location where the default initializer expression was used. SourceLocation Loc; }; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2646,6 +2646,10 @@ " of an immediate invocation">; def err_invalid_consteval_call : Error< "call to consteval function %q0 is not a constant expression">; +def note_invalid_consteval_initializer : Note< + "in the default initalizer of %0">; +def note_invalid_consteval_initializer_here : Note< + "initialized here %0">; def err_invalid_consteval_decl_kind : Error< "%0 cannot be declared consteval">; def err_invalid_constexpr : Error< diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1330,6 +1330,25 @@ bool InDiscardedStatement; bool InImmediateFunctionContext; + bool IsCurrentlyCheckingDefaultArgumentOrInitializer = false; + + // When evaluating immediate functions in the initializer of a default + // argument or default member initializer, this is the declaration whose + // default initializer is being evaluated and the location of the call + // or constructor definition. + struct InitializationContext { + InitializationContext(SourceLocation Loc, ValueDecl *Decl, + DeclContext *Context) + : Loc(Loc), Decl(Decl), Context(Context) { + assert(Decl && Context && "invalid initialization context"); + } + + SourceLocation Loc; + ValueDecl *Decl = nullptr; + DeclContext *Context = nullptr; + }; + llvm::Optional DelayedDefaultInitializationContext; + ExpressionEvaluationContextRecord(ExpressionEvaluationContext Context, unsigned NumCleanupObjects, CleanupInfo ParentCleanup, @@ -6211,19 +6230,22 @@ bool IsStdInitListInitialization, bool RequiresZeroInit, unsigned ConstructKind, SourceRange ParenRange); + ExprResult ConvertMemberDefaultInitExpression(FieldDecl *FD, Expr *InitExpr, + SourceLocation InitLoc); + ExprResult BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field); /// Instantiate or parse a C++ default argument expression as necessary. /// Return true on error. bool CheckCXXDefaultArgExpr(SourceLocation CallLoc, FunctionDecl *FD, - ParmVarDecl *Param); + ParmVarDecl *Param, Expr *Init = nullptr, + bool SkipImmediateInvocations = true); /// BuildCXXDefaultArgExpr - Creates a CXXDefaultArgExpr, instantiating /// the default expr if needed. - ExprResult BuildCXXDefaultArgExpr(SourceLocation CallLoc, - FunctionDecl *FD, - ParmVarDecl *Param); + ExprResult BuildCXXDefaultArgExpr(SourceLocation CallLoc, FunctionDecl *FD, + ParmVarDecl *Param, Expr *Init = nullptr); /// FinalizeVarWithDestructor - Prepare for calling destructor on the /// constructed variable. @@ -9636,6 +9658,48 @@ return ExprEvalContexts.back().isImmediateFunctionContext(); } + bool isCheckingDefaultArgumentOrInitializer() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + const ExpressionEvaluationContextRecord &Ctx = ExprEvalContexts.back(); + return (Ctx.Context == + ExpressionEvaluationContext::PotentiallyEvaluatedIfUsed) || + Ctx.IsCurrentlyCheckingDefaultArgumentOrInitializer; + } + + llvm::Optional + InnermostDeclarationWithDelayedImmediateInvocations() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + for (const auto &Ctx : llvm::reverse(ExprEvalContexts)) { + if (Ctx.Context == ExpressionEvaluationContext::PotentiallyEvaluated && + Ctx.DelayedDefaultInitializationContext) + return Ctx.DelayedDefaultInitializationContext; + if (Ctx.isConstantEvaluated() || Ctx.isImmediateFunctionContext() || + Ctx.isUnevaluated()) + break; + } + return std::nullopt; + } + + llvm::Optional + OutermostDeclarationWithDelayedImmediateInvocations() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + llvm::Optional + Res; + for (auto &Ctx : llvm::reverse(ExprEvalContexts)) { + if (Ctx.Context == ExpressionEvaluationContext::PotentiallyEvaluated && + !Ctx.DelayedDefaultInitializationContext && Res) + break; + if (Ctx.isConstantEvaluated() || Ctx.isImmediateFunctionContext() || + Ctx.isUnevaluated()) + break; + Res = Ctx.DelayedDefaultInitializationContext; + } + return Res; + } + /// RAII class used to determine whether SFINAE has /// trapped any errors that occur during template argument /// deduction. diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -7687,9 +7687,16 @@ if (Error Err = ImportDefaultArgOfParmVarDecl(*FromParam, ToParam)) return std::move(Err); } - + Expr *RewrittenInit = nullptr; + if (E->hasRewrittenInit()) { + ExpectedExpr ExprOrErr = import(E->getRewrittenExpr()); + if (!ExprOrErr) + return ExprOrErr.takeError(); + RewrittenInit = ExprOrErr.get(); + } return CXXDefaultArgExpr::Create(Importer.getToContext(), *ToUsedLocOrErr, - *ToParamOrErr, *UsedContextOrErr); + *ToParamOrErr, RewrittenInit, + *UsedContextOrErr); } ExpectedStmt @@ -8381,8 +8388,16 @@ ToField->setInClassInitializer(*ToInClassInitializerOrErr); } + Expr *RewrittenInit = nullptr; + if (E->hasRewrittenInit()) { + ExpectedExpr ExprOrErr = import(E->getRewrittenExpr()); + if (!ExprOrErr) + return ExprOrErr.takeError(); + RewrittenInit = ExprOrErr.get(); + } + return CXXDefaultInitExpr::Create(Importer.getToContext(), *ToBeginLocOrErr, - ToField, *UsedContextOrErr); + ToField, *UsedContextOrErr, RewrittenInit); } ExpectedStmt ASTNodeImporter::VisitCXXNamedCastExpr(CXXNamedCastExpr *E) { diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2897,8 +2897,7 @@ Expr *Arg = getInit(); if (auto *E = dyn_cast_or_null(Arg)) - if (!isa(E)) - return E->getSubExpr(); + return E->getSubExpr(); return Arg; } diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp --- a/clang/lib/AST/ExprCXX.cpp +++ b/clang/lib/AST/ExprCXX.cpp @@ -949,9 +949,43 @@ return cast(getCalleeDecl())->getLiteralIdentifier(); } +CXXDefaultArgExpr *CXXDefaultArgExpr::CreateEmpty(const ASTContext &C, + bool HasRewrittenInit) { + size_t Size = totalSizeToAlloc(HasRewrittenInit); + auto *Mem = C.Allocate(Size, alignof(CXXDefaultArgExpr)); + return new (Mem) CXXDefaultArgExpr(EmptyShell(), HasRewrittenInit); +} + +CXXDefaultArgExpr *CXXDefaultArgExpr::Create(const ASTContext &C, + SourceLocation Loc, + ParmVarDecl *Param, + Expr *RewrittenExpr, + DeclContext *UsedContext) { + size_t Size = totalSizeToAlloc(RewrittenExpr != nullptr); + auto *Mem = C.Allocate(Size, alignof(CXXDefaultArgExpr)); + return new (Mem) CXXDefaultArgExpr(CXXDefaultArgExprClass, Loc, Param, + RewrittenExpr, UsedContext); +} + +Expr *CXXDefaultArgExpr::getExpr() { + return CXXDefaultArgExprBits.HasRewrittenInit ? getAdjustedRewrittenExpr() + : getParam()->getDefaultArg(); +} + +Expr *CXXDefaultArgExpr::getAdjustedRewrittenExpr() { + assert(hasRewrittenInit() && + "expected this CXXDefaultArgExpr to have a rewritten init."); + Expr *Init = getRewrittenExpr(); + if (auto *E = dyn_cast_if_present(Init)) + if (!isa(E)) + return E->getSubExpr(); + return Init; +} + CXXDefaultInitExpr::CXXDefaultInitExpr(const ASTContext &Ctx, SourceLocation Loc, FieldDecl *Field, - QualType Ty, DeclContext *UsedContext) + QualType Ty, DeclContext *UsedContext, + Expr *RewrittenInitExpr) : Expr(CXXDefaultInitExprClass, Ty.getNonLValueExprType(Ctx), Ty->isLValueReferenceType() ? VK_LValue : Ty->isRValueReferenceType() ? VK_XValue @@ -959,11 +993,43 @@ /*FIXME*/ OK_Ordinary), Field(Field), UsedContext(UsedContext) { CXXDefaultInitExprBits.Loc = Loc; + CXXDefaultInitExprBits.HasRewrittenInit = RewrittenInitExpr != nullptr; + + if (CXXDefaultInitExprBits.HasRewrittenInit) + *getTrailingObjects() = RewrittenInitExpr; + assert(Field->hasInClassInitializer()); setDependence(computeDependence(this)); } +CXXDefaultInitExpr *CXXDefaultInitExpr::CreateEmpty(const ASTContext &C, + bool HasRewrittenInit) { + size_t Size = totalSizeToAlloc(HasRewrittenInit); + auto *Mem = C.Allocate(Size, alignof(CXXDefaultInitExpr)); + return new (Mem) CXXDefaultInitExpr(EmptyShell(), HasRewrittenInit); +} + +CXXDefaultInitExpr *CXXDefaultInitExpr::Create(const ASTContext &Ctx, + SourceLocation Loc, + FieldDecl *Field, + DeclContext *UsedContext, + Expr *RewrittenInitExpr) { + + size_t Size = totalSizeToAlloc(RewrittenInitExpr != nullptr); + auto *Mem = Ctx.Allocate(Size, alignof(CXXDefaultInitExpr)); + return new (Mem) CXXDefaultInitExpr(Ctx, Loc, Field, Field->getType(), + UsedContext, RewrittenInitExpr); +} + +Expr *CXXDefaultInitExpr::getExpr() { + assert(Field->getInClassInitializer() && "initializer hasn't been parsed"); + if (hasRewrittenInit()) + return getRewrittenExpr(); + + return Field->getInClassInitializer(); +} + CXXTemporary *CXXTemporary::Create(const ASTContext &C, const CXXDestructorDecl *Destructor) { return new (C) CXXTemporary(Destructor); diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp --- a/clang/lib/Parse/ParseCXXInlineMethods.cpp +++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp @@ -648,6 +648,11 @@ Actions.ActOnStartCXXInClassMemberInitializer(); + // The initializer isn't actually potentially evaluated unless it is + // used. + EnterExpressionEvaluationContext Eval( + Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluatedIfUsed); + ExprResult Init = ParseCXXMemberInitializer(MI.Field, /*IsFunction=*/false, EqualLoc); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -3191,7 +3191,11 @@ "Data member initializer not starting with '=' or '{'"); EnterExpressionEvaluationContext Context( - Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, D); + Actions, + isa_and_present(D) + ? Sema::ExpressionEvaluationContext::PotentiallyEvaluatedIfUsed + : Sema::ExpressionEvaluationContext::PotentiallyEvaluated, + D); if (TryConsumeToken(tok::equal, EqualLoc)) { if (Tok.is(tok::kw_delete)) { // In principle, an initializer of '= delete p;' is legal, but it will diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -4059,6 +4059,21 @@ return ConstraintExpr; } +ExprResult Sema::ConvertMemberDefaultInitExpression(FieldDecl *FD, + Expr *InitExpr, + SourceLocation InitLoc) { + InitializedEntity Entity = + InitializedEntity::InitializeMemberFromDefaultMemberInitializer(FD); + InitializationKind Kind = + FD->getInClassInitStyle() == ICIS_ListInit + ? InitializationKind::CreateDirectList(InitExpr->getBeginLoc(), + InitExpr->getBeginLoc(), + InitExpr->getEndLoc()) + : InitializationKind::CreateCopy(InitExpr->getBeginLoc(), InitLoc); + InitializationSequence Seq(*this, Entity, Kind, InitExpr); + return Seq.Perform(*this, Entity, Kind, InitExpr); +} + /// This is invoked after parsing an in-class initializer for a /// non-static C++ class member, and after instantiating an in-class initializer /// in a class template. Such actions are deferred until the class is complete. @@ -4087,31 +4102,18 @@ ExprResult Init = InitExpr; if (!FD->getType()->isDependentType() && !InitExpr->isTypeDependent()) { - InitializedEntity Entity = - InitializedEntity::InitializeMemberFromDefaultMemberInitializer(FD); - InitializationKind Kind = - FD->getInClassInitStyle() == ICIS_ListInit - ? InitializationKind::CreateDirectList(InitExpr->getBeginLoc(), - InitExpr->getBeginLoc(), - InitExpr->getEndLoc()) - : InitializationKind::CreateCopy(InitExpr->getBeginLoc(), InitLoc); - InitializationSequence Seq(*this, Entity, Kind, InitExpr); - Init = Seq.Perform(*this, Entity, Kind, InitExpr); + Init = ConvertMemberDefaultInitExpression(FD, InitExpr, InitLoc); + // C++11 [class.base.init]p7: + // The initialization of each base and member constitutes a + // full-expression. + if (!Init.isInvalid()) + Init = ActOnFinishFullExpr(Init.get(), /*DiscarededValue=*/false); if (Init.isInvalid()) { FD->setInvalidDecl(); return; } } - // C++11 [class.base.init]p7: - // The initialization of each base and member constitutes a - // full-expression. - Init = ActOnFinishFullExpr(Init.get(), InitLoc, /*DiscardedValue*/ false); - if (Init.isInvalid()) { - FD->setInvalidDecl(); - return; - } - InitExpr = Init.get(); FD->setInClassInitializer(InitExpr); @@ -15651,70 +15653,6 @@ Constructor); } -ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { - assert(Field->hasInClassInitializer()); - - // If we already have the in-class initializer nothing needs to be done. - if (Field->getInClassInitializer()) - return CXXDefaultInitExpr::Create(Context, Loc, Field, CurContext); - - // If we might have already tried and failed to instantiate, don't try again. - if (Field->isInvalidDecl()) - return ExprError(); - - // Maybe we haven't instantiated the in-class initializer. Go check the - // pattern FieldDecl to see if it has one. - CXXRecordDecl *ParentRD = cast(Field->getParent()); - - if (isTemplateInstantiation(ParentRD->getTemplateSpecializationKind())) { - CXXRecordDecl *ClassPattern = ParentRD->getTemplateInstantiationPattern(); - DeclContext::lookup_result Lookup = - ClassPattern->lookup(Field->getDeclName()); - - FieldDecl *Pattern = nullptr; - for (auto *L : Lookup) { - if (isa(L)) { - Pattern = cast(L); - break; - } - } - assert(Pattern && "We must have set the Pattern!"); - - if (!Pattern->hasInClassInitializer() || - InstantiateInClassInitializer(Loc, Field, Pattern, - getTemplateInstantiationArgs(Field))) { - // Don't diagnose this again. - Field->setInvalidDecl(); - return ExprError(); - } - return CXXDefaultInitExpr::Create(Context, Loc, Field, CurContext); - } - - // DR1351: - // If the brace-or-equal-initializer of a non-static data member - // invokes a defaulted default constructor of its class or of an - // enclosing class in a potentially evaluated subexpression, the - // program is ill-formed. - // - // This resolution is unworkable: the exception specification of the - // default constructor can be needed in an unevaluated context, in - // particular, in the operand of a noexcept-expression, and we can be - // unable to compute an exception specification for an enclosed class. - // - // Any attempt to resolve the exception specification of a defaulted default - // constructor before the initializer is lexically complete will ultimately - // come here at which point we can diagnose it. - RecordDecl *OutermostClass = ParentRD->getOuterLexicalRecordContext(); - Diag(Loc, diag::err_default_member_initializer_not_yet_parsed) - << OutermostClass << Field; - Diag(Field->getEndLoc(), - diag::note_default_member_initializer_not_yet_parsed); - // Recover by marking the field invalid, unless we're in a SFINAE context. - if (!isSFINAEContext()) - Field->setInvalidDecl(); - return ExprError(); -} - void Sema::FinalizeVarWithDestructor(VarDecl *VD, const RecordType *Record) { if (VD->isInvalidDecl()) return; // If initializing the variable failed, don't also diagnose problems with diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -5860,8 +5860,10 @@ } bool Sema::CheckCXXDefaultArgExpr(SourceLocation CallLoc, FunctionDecl *FD, - ParmVarDecl *Param) { + ParmVarDecl *Param, Expr *RewrittenInit, + bool SkipImmediateInvocations) { if (Param->hasUnparsedDefaultArg()) { + assert(!RewrittenInit && "Should not have a rewritten init expression yet"); // If we've already cleared out the location for the default argument, // that means we're parsing it right now. if (!UnparsedDefaultArgLocs.count(Param)) { @@ -5878,11 +5880,14 @@ return true; } - if (Param->hasUninstantiatedDefaultArg() && - InstantiateDefaultArgument(CallLoc, FD, Param)) - return true; + if (Param->hasUninstantiatedDefaultArg()) { + assert(!RewrittenInit && "Should not have a rewitten init expression yet"); + if (InstantiateDefaultArgument(CallLoc, FD, Param)) + return true; + } - assert(Param->hasInit() && "default argument but no initializer?"); + Expr *Init = RewrittenInit ? RewrittenInit : Param->getInit(); + assert(Init && "default argument but no initializer?"); // If the default expression creates temporaries, we need to // push them to the current stack of expression temporaries so they'll @@ -5891,34 +5896,258 @@ // bound temporaries; see the comment in PR5810. // We don't need to do that with block decls, though, because // blocks in default argument expression can never capture anything. - if (auto Init = dyn_cast(Param->getInit())) { + if (auto *InitWithCleanup = dyn_cast(Init)) { // Set the "needs cleanups" bit regardless of whether there are // any explicit objects. - Cleanup.setExprNeedsCleanups(Init->cleanupsHaveSideEffects()); - + Cleanup.setExprNeedsCleanups(InitWithCleanup->cleanupsHaveSideEffects()); // Append all the objects to the cleanup list. Right now, this // should always be a no-op, because blocks in default argument // expressions should never be able to capture anything. - assert(!Init->getNumObjects() && + assert(!InitWithCleanup->getNumObjects() && "default argument expression has capturing blocks?"); } - - // We already type-checked the argument, so we know it works. - // Just mark all of the declarations in this potentially-evaluated expression - // as being "referenced". EnterExpressionEvaluationContext EvalContext( *this, ExpressionEvaluationContext::PotentiallyEvaluated, Param); - MarkDeclarationsReferencedInExpr(Param->getDefaultArg(), - /*SkipLocalVariables=*/true); + ExprEvalContexts.back().IsCurrentlyCheckingDefaultArgumentOrInitializer = + SkipImmediateInvocations; + MarkDeclarationsReferencedInExpr(Init, /*SkipLocalVariables*/ true); return false; } +struct ImmediateCallVisitor : public RecursiveASTVisitor { + bool HasImmediateCalls = false; + + bool shouldVisitImplicitCode() const { return true; } + + bool VisitCallExpr(CallExpr *E) { + if (const FunctionDecl *FD = E->getDirectCallee()) + HasImmediateCalls |= FD->isConsteval(); + return RecursiveASTVisitor::VisitStmt(E); + } + + // SourceLocExpr are not immediate invocations + // but CXXDefaultInitExpr/CXXDefaultArgExpr containing a SourceLocExpr + // need to be rebuilt so that they refer to the correct SourceLocation and + // DeclContext. + bool VisitSourceLocExpr(SourceLocExpr *E) { + HasImmediateCalls = true; + return RecursiveASTVisitor::VisitStmt(E); + } + + // A nested lambda might have parameters with immediate invocations + // in their default arguments. + // The compound statement is not visited (as it does not constitute a + // subexpression). + // FIXME: We should consider visiting and transforming captures + // with init expressions. + bool VisitLambdaExpr(LambdaExpr *E) { + return VisitCXXMethodDecl(E->getCallOperator()); + } + + // Blocks don't support default parameters, and, as for lambdas, + // we don't consider their body a subexpression. + bool VisitBlockDecl(BlockDecl *B) { return false; } + + bool VisitCompoundStmt(CompoundStmt *B) { return false; } + + bool VisitCXXDefaultArgExpr(CXXDefaultArgExpr *E) { + return TraverseStmt(E->getExpr()); + } + + bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *E) { + return TraverseStmt(E->getExpr()); + } +}; + +struct EnsureImmediateInvocationInDefaultArgs + : TreeTransform { + EnsureImmediateInvocationInDefaultArgs(Sema &SemaRef) + : TreeTransform(SemaRef) {} + + // Lambda can only have immediate invocations in the default + // args of their parameters, which is transformed upon calling the closure. + // The body is not a subexpression, so we have nothing to do. + // FIXME: Immediate calls in capture initializers should be transformed. + ExprResult TransformLambdaExpr(LambdaExpr *E) { return E; } + ExprResult TransformBlockExpr(BlockExpr *E) { return E; } + + // Make sure we don't rebuild the this pointer as it would + // cause it to incorrectly point it to the outermost class + // in the case of nested struct initialization. + ExprResult TransformCXXThisExpr(CXXThisExpr *E) { return E; } +}; + ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc, - FunctionDecl *FD, ParmVarDecl *Param) { + FunctionDecl *FD, ParmVarDecl *Param, + Expr *Init) { assert(Param->hasDefaultArg() && "can't build nonexistent default arg"); - if (CheckCXXDefaultArgExpr(CallLoc, FD, Param)) + + bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer(); + + llvm::Optional + InitializationContext = + OutermostDeclarationWithDelayedImmediateInvocations(); + if (!InitializationContext.has_value()) + InitializationContext.emplace(CallLoc, Param, CurContext); + + if (!Init && !Param->hasUnparsedDefaultArg()) { + // Mark that we are replacing a default argument first. + // If we are instantiating a template we won't have to + // retransform immediate calls. + EnterExpressionEvaluationContext EvalContext( + *this, ExpressionEvaluationContext::PotentiallyEvaluated, Param); + + if (Param->hasUninstantiatedDefaultArg()) { + if (InstantiateDefaultArgument(CallLoc, FD, Param)) + return ExprError(); + } + // CWG2631 + // An immediate invocation that is not evaluated where it appears is + // evaluated and checked for whether it is a constant expression at the + // point where the enclosing initializer is used in a function call. + ImmediateCallVisitor V; + if (!NestedDefaultChecking) + V.TraverseDecl(Param); + if (V.HasImmediateCalls) { + ExprEvalContexts.back().DelayedDefaultInitializationContext = { + CallLoc, Param, CurContext}; + EnsureImmediateInvocationInDefaultArgs Immediate(*this); + ExprResult Res = Immediate.TransformInitializer(Param->getInit(), + /*NotCopy=*/false); + if (Res.isInvalid()) + return ExprError(); + Res = ConvertParamDefaultArgument(Param, Res.get(), + Res.get()->getBeginLoc()); + if (Res.isInvalid()) + return ExprError(); + Init = Res.get(); + } + } + + if (CheckCXXDefaultArgExpr( + CallLoc, FD, Param, Init, + /*SkipImmediateInvocations=*/NestedDefaultChecking)) + return ExprError(); + + return CXXDefaultArgExpr::Create(Context, InitializationContext->Loc, Param, + Init, InitializationContext->Context); +} + +ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { + assert(Field->hasInClassInitializer()); + + // If we might have already tried and failed to instantiate, don't try again. + if (Field->isInvalidDecl()) return ExprError(); - return CXXDefaultArgExpr::Create(Context, CallLoc, Param, CurContext); + + auto *ParentRD = cast(Field->getParent()); + + llvm::Optional + InitializationContext = + OutermostDeclarationWithDelayedImmediateInvocations(); + if (!InitializationContext.has_value()) + InitializationContext.emplace(Loc, Field, CurContext); + + Expr *Init = nullptr; + + bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer(); + + EnterExpressionEvaluationContext EvalContext( + *this, ExpressionEvaluationContext::PotentiallyEvaluated, Field); + + if (!Field->getInClassInitializer()) { + // Maybe we haven't instantiated the in-class initializer. Go check the + // pattern FieldDecl to see if it has one. + if (isTemplateInstantiation(ParentRD->getTemplateSpecializationKind())) { + CXXRecordDecl *ClassPattern = ParentRD->getTemplateInstantiationPattern(); + DeclContext::lookup_result Lookup = + ClassPattern->lookup(Field->getDeclName()); + + FieldDecl *Pattern = nullptr; + for (auto *L : Lookup) { + if ((Pattern = dyn_cast(L))) + break; + } + assert(Pattern && "We must have set the Pattern!"); + if (!Pattern->hasInClassInitializer() || + InstantiateInClassInitializer(Loc, Field, Pattern, + getTemplateInstantiationArgs(Field))) { + Field->setInvalidDecl(); + return ExprError(); + } + } + } + + // CWG2631 + // An immediate invocation that is not evaluated where it appears is + // evaluated and checked for whether it is a constant expression at the + // point where the enclosing initializer is used in a [...] a constructor + // definition, or an aggregate initialization. + ImmediateCallVisitor V; + if (!NestedDefaultChecking) + V.TraverseDecl(Field); + if (V.HasImmediateCalls) { + ExprEvalContexts.back().DelayedDefaultInitializationContext = {Loc, Field, + CurContext}; + ExprEvalContexts.back().IsCurrentlyCheckingDefaultArgumentOrInitializer = + NestedDefaultChecking; + + EnsureImmediateInvocationInDefaultArgs Immediate(*this); + + ExprResult Res = + Immediate.TransformInitializer(Field->getInClassInitializer(), + /*CXXDirectInit=*/false); + if (!Res.isInvalid()) + Res = ConvertMemberDefaultInitExpression(Field, Res.get(), Loc); + if (Res.isInvalid()) { + Field->setInvalidDecl(); + return ExprError(); + } + Init = Res.get(); + } + + if (Field->getInClassInitializer()) { + Expr *E = Init ? Init : Field->getInClassInitializer(); + if (!NestedDefaultChecking) + MarkDeclarationsReferencedInExpr(E, /*SkipLocalVariables=*/false); + // C++11 [class.base.init]p7: + // The initialization of each base and member constitutes a + // full-expression. + ExprResult Res = ActOnFinishFullExpr(E, /*DiscardedValue=*/false); + if (Res.isInvalid()) { + Field->setInvalidDecl(); + return ExprError(); + } + Init = Res.get(); + + return CXXDefaultInitExpr::Create(Context, InitializationContext->Loc, + Field, InitializationContext->Context, + Init); + } + + // DR1351: + // If the brace-or-equal-initializer of a non-static data member + // invokes a defaulted default constructor of its class or of an + // enclosing class in a potentially evaluated subexpression, the + // program is ill-formed. + // + // This resolution is unworkable: the exception specification of the + // default constructor can be needed in an unevaluated context, in + // particular, in the operand of a noexcept-expression, and we can be + // unable to compute an exception specification for an enclosed class. + // + // Any attempt to resolve the exception specification of a defaulted default + // constructor before the initializer is lexically complete will ultimately + // come here at which point we can diagnose it. + RecordDecl *OutermostClass = ParentRD->getOuterLexicalRecordContext(); + Diag(Loc, diag::err_default_member_initializer_not_yet_parsed) + << OutermostClass << Field; + Diag(Field->getEndLoc(), + diag::note_default_member_initializer_not_yet_parsed); + // Recover by marking the field invalid, unless we're in a SFINAE context. + if (!isSFINAEContext()) + Field->setInvalidDecl(); + return ExprError(); } Sema::VariadicCallType @@ -17547,6 +17776,7 @@ ExprResult Sema::CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl) { if (isUnevaluatedContext() || !E.isUsable() || !Decl || !Decl->isConsteval() || isConstantEvaluated() || + isCheckingDefaultArgumentOrInitializer() || RebuildingImmediateInvocation || isImmediateFunctionContext()) return E; @@ -17592,8 +17822,14 @@ FD = Call->getConstructor(); else llvm_unreachable("unhandled decl kind"); - assert(FD->isConsteval()); + assert(FD && FD->isConsteval()); SemaRef.Diag(CE->getBeginLoc(), diag::err_invalid_consteval_call) << FD; + if (auto Context = + SemaRef.InnermostDeclarationWithDelayedImmediateInvocations()) { + SemaRef.Diag(Context->Loc, diag::note_invalid_consteval_initializer) + << Context->Decl; + SemaRef.Diag(Context->Decl->getBeginLoc(), diag::note_declared_at); + } for (auto &Note : Notes) SemaRef.Diag(Note.first, Note.second); return; @@ -18178,6 +18414,16 @@ }); } + // If a constructor was defined in the context of a default parameter + // or of another default member initializer (ie a PotentiallyEvaluatedIfUsed + // context), its initializers may not be referenced yet. + if (CXXConstructorDecl *Constructor = dyn_cast(Func)) { + for (CXXCtorInitializer *Init : Constructor->inits()) { + if (Init->isInClassMemberInitializer()) + MarkDeclarationsReferencedInExpr(Init->getInit()); + } + } + // C++14 [except.spec]p17: // An exception-specification is considered to be needed when: // - the function is odr-used or, if it appears in an unevaluated operand, @@ -19606,7 +19852,10 @@ switch (OdrUse) { case OdrUseContext::None: - assert((!E || isa(E)) && + // In some cases, a variable may not have been marked unevaluated, if it + // appears in a defaukt initializer. + assert((!E || isa(E) || + SemaRef.isUnevaluatedContext()) && "missing non-odr-use marking for unevaluated decl ref"); break; @@ -19739,7 +19988,8 @@ if (auto *FD = dyn_cast(E->getDecl())) if (!isUnevaluatedContext() && !isConstantEvaluated() && - !isImmediateFunctionContext() && FD->isConsteval() && + !isImmediateFunctionContext() && + !isCheckingDefaultArgumentOrInitializer() && FD->isConsteval() && !RebuildingImmediateInvocation && !FD->isDependentContext()) ExprEvalContexts.back().ReferenceToConsteval.insert(E); MarkExprReferenced(*this, E->getLocation(), E->getDecl(), E, OdrUse, diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -1982,9 +1982,9 @@ assert(!cast(E->getParam()->getDeclContext())-> getDescribedFunctionTemplate() && "Default arg expressions are never formed in dependent cases."); - return SemaRef.BuildCXXDefaultArgExpr(E->getUsedLocation(), - cast(E->getParam()->getDeclContext()), - E->getParam()); + return SemaRef.BuildCXXDefaultArgExpr( + E->getUsedLocation(), cast(E->getParam()->getDeclContext()), + E->getParam()); } template @@ -3405,6 +3405,8 @@ ContextRAII SavedContext(*this, Instantiation->getParent()); EnterExpressionEvaluationContext EvalContext( *this, Sema::ExpressionEvaluationContext::PotentiallyEvaluated); + ExprEvalContexts.back().DelayedDefaultInitializationContext = { + PointOfInstantiation, Instantiation, CurContext}; LocalInstantiationScope Scope(*this, true); diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -3210,9 +3210,10 @@ /// By default, builds a new default-argument expression, which does not /// require any semantic analysis. Subclasses may override this routine to /// provide different behavior. - ExprResult RebuildCXXDefaultArgExpr(SourceLocation Loc, ParmVarDecl *Param) { + ExprResult RebuildCXXDefaultArgExpr(SourceLocation Loc, ParmVarDecl *Param, + Expr *RewrittenExpr) { return CXXDefaultArgExpr::Create(getSema().Context, Loc, Param, - getSema().CurContext); + RewrittenExpr, getSema().CurContext); } /// Build a new C++11 default-initialization expression. @@ -3222,8 +3223,7 @@ /// routine to provide different behavior. ExprResult RebuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { - return CXXDefaultInitExpr::Create(getSema().Context, Loc, Field, - getSema().CurContext); + return getSema().BuildCXXDefaultInitExpr(Loc, Field); } /// Build a new C++ zero-initialization expression. @@ -12163,11 +12163,20 @@ if (!Param) return ExprError(); + ExprResult InitRes; + if (E->hasRewrittenInit()) { + InitRes = getDerived().TransformExpr(E->getRewrittenExpr()); + if (InitRes.isInvalid()) + return ExprError(); + } + if (!getDerived().AlwaysRebuild() && Param == E->getParam() && - E->getUsedContext() == SemaRef.CurContext) + E->getUsedContext() == SemaRef.CurContext && + InitRes.get() == E->getRewrittenExpr()) return E; - return getDerived().RebuildCXXDefaultArgExpr(E->getUsedLocation(), Param); + return getDerived().RebuildCXXDefaultArgExpr(E->getUsedLocation(), Param, + InitRes.get()); } template diff --git a/clang/lib/Sema/UsedDeclVisitor.h b/clang/lib/Sema/UsedDeclVisitor.h --- a/clang/lib/Sema/UsedDeclVisitor.h +++ b/clang/lib/Sema/UsedDeclVisitor.h @@ -82,11 +82,28 @@ void VisitCXXConstructExpr(CXXConstructExpr *E) { asImpl().visitUsedDecl(E->getBeginLoc(), E->getConstructor()); + CXXConstructorDecl *D = E->getConstructor(); + for (const CXXCtorInitializer *Init : D->inits()) { + if (Init->isInClassMemberInitializer()) + asImpl().Visit(Init->getInit()); + } Inherited::VisitCXXConstructExpr(E); } void VisitCXXDefaultArgExpr(CXXDefaultArgExpr *E) { asImpl().Visit(E->getExpr()); + Inherited::VisitCXXDefaultArgExpr(E); + } + + void VisitCXXDefaultInitExpr(CXXDefaultInitExpr *E) { + asImpl().Visit(E->getExpr()); + Inherited::VisitCXXDefaultInitExpr(E); + } + + void VisitInitListExpr(InitListExpr *ILE) { + if (ILE->hasArrayFiller()) + asImpl().Visit(ILE->getArrayFiller()); + Inherited::VisitInitListExpr(ILE); } void visitUsedDecl(SourceLocation Loc, Decl *D) { diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -1832,13 +1832,19 @@ E->Param = readDeclAs(); E->UsedContext = readDeclAs(); E->CXXDefaultArgExprBits.Loc = readSourceLocation(); + E->CXXDefaultArgExprBits.HasRewrittenInit = Record.readInt(); + if (E->CXXDefaultArgExprBits.HasRewrittenInit) + *E->getTrailingObjects() = Record.readSubExpr(); } void ASTStmtReader::VisitCXXDefaultInitExpr(CXXDefaultInitExpr *E) { VisitExpr(E); + E->CXXDefaultInitExprBits.HasRewrittenInit = Record.readInt(); E->Field = readDeclAs(); E->UsedContext = readDeclAs(); E->CXXDefaultInitExprBits.Loc = readSourceLocation(); + if (E->CXXDefaultInitExprBits.HasRewrittenInit) + *E->getTrailingObjects() = Record.readSubExpr(); } void ASTStmtReader::VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *E) { @@ -3837,11 +3843,13 @@ break; case EXPR_CXX_DEFAULT_ARG: - S = new (Context) CXXDefaultArgExpr(Empty); + S = CXXDefaultArgExpr::CreateEmpty( + Context, /*HasRewrittenInit=*/Record[ASTStmtReader::NumExprFields]); break; case EXPR_CXX_DEFAULT_INIT: - S = new (Context) CXXDefaultInitExpr(Empty); + S = CXXDefaultInitExpr::CreateEmpty( + Context, /*HasRewrittenInit=*/Record[ASTStmtReader::NumExprFields]); break; case EXPR_CXX_BIND_TEMPORARY: diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -1745,14 +1745,20 @@ Record.AddDeclRef(E->getParam()); Record.AddDeclRef(cast_or_null(E->getUsedContext())); Record.AddSourceLocation(E->getUsedLocation()); + Record.push_back(E->hasRewrittenInit()); + if (E->hasRewrittenInit()) + Record.AddStmt(E->getRewrittenExpr()); Code = serialization::EXPR_CXX_DEFAULT_ARG; } void ASTStmtWriter::VisitCXXDefaultInitExpr(CXXDefaultInitExpr *E) { VisitExpr(E); + Record.push_back(E->hasRewrittenInit()); Record.AddDeclRef(E->getField()); Record.AddDeclRef(cast_or_null(E->getUsedContext())); Record.AddSourceLocation(E->getExprLoc()); + if (E->hasRewrittenInit()) + Record.AddStmt(E->getRewrittenExpr()); Code = serialization::EXPR_CXX_DEFAULT_INIT; } diff --git a/clang/test/AST/ast-dump-records.cpp b/clang/test/AST/ast-dump-records.cpp --- a/clang/test/AST/ast-dump-records.cpp +++ b/clang/test/AST/ast-dump-records.cpp @@ -288,3 +288,29 @@ // CHECK: CXXRecordDecl 0x{{[^ ]*}} line:[[@LINE-1]]:8 struct Derived6 definition // CHECK: virtual public 'Bases'... }; + +class NonTrivial { +// CHECK: |-CXXRecordDecl {{.*}} referenced class NonTrivial definition + public: + NonTrivial(); +// CHECK: | |-CXXConstructorDecl {{.*}} referenced NonTrivial 'void ()' + ~NonTrivial(); +// CHECK: | |-CXXDestructorDecl {{.*}} referenced ~NonTrivial 'void () noexcept' +}; + +struct CheckFullExpression { +// CHECK: |-CXXRecordDecl {{.*}} struct CheckFullExpression definition + NonTrivial value = NonTrivial(); +// CHECK: | |-FieldDecl {{.*}} value 'NonTrivial':'NonTrivial' +// CHECK-NEXT: | | `-ExprWithCleanups {{.*}} 'NonTrivial':'NonTrivial' +// CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'NonTrivial':'NonTrivial' (CXXTemporary{{.*}}) +// CHECK-NEXT: | | `-CXXTemporaryObjectExpr {{.*}} 'NonTrivial':'NonTrivial' 'void ()' +}; + +struct CheckNoCleanup { +// CHECK: `-CXXRecordDecl {{.*}} struct CheckNoCleanup definition + static constexpr char kConstant = '+'; +// CHECK: `-VarDecl {{.*}} kConstant 'const char' static inline constexpr cinit +// CHECK-NEXT: |-value: Int 43 +// CHECK-NEXT: `-CharacterLiteral {{.*}} 'char' 43 +}; diff --git a/clang/test/CXX/class/class.local/p1-0x.cpp b/clang/test/CXX/class/class.local/p1-0x.cpp --- a/clang/test/CXX/class/class.local/p1-0x.cpp +++ b/clang/test/CXX/class/class.local/p1-0x.cpp @@ -11,8 +11,8 @@ int x = 3; // expected-note{{'x' declared here}} struct C { int& x2 = x; // expected-error{{reference to local variable 'x' declared in enclosing lambda expression}} - }; + }c; // expected-note {{required here}} }; - C(); + C(); // expected-note {{required here}} } diff --git a/clang/test/CXX/drs/dr26xx.cpp b/clang/test/CXX/drs/dr26xx.cpp --- a/clang/test/CXX/drs/dr26xx.cpp +++ b/clang/test/CXX/drs/dr26xx.cpp @@ -103,3 +103,19 @@ brachiosaur |= neck; // OK } } + +namespace dr2631 { // dr2631: 16 + constexpr int g(); + consteval int f() { + return g(); + } + int k(int x = f()) { + return x; + } + constexpr int g() { + return 42; + } + int test() { + return k(); + } +} diff --git a/clang/test/CodeGenCXX/builtin-source-location.cpp b/clang/test/CodeGenCXX/builtin-source-location.cpp --- a/clang/test/CodeGenCXX/builtin-source-location.cpp +++ b/clang/test/CodeGenCXX/builtin-source-location.cpp @@ -1,4 +1,6 @@ // RUN: %clang_cc1 -no-opaque-pointers -std=c++2a -fblocks %s -triple x86_64-unknown-unknown -emit-llvm -o %t.ll +// RUN: %clang_cc1 -no-opaque-pointers -std=c++14 -fblocks %s -triple x86_64-unknown-unknown -emit-llvm -o %t.ll + // This needs to be performed before #line directives which alter filename // RUN: %clang_cc1 -no-opaque-pointers -fno-file-reproducible -fmacro-prefix-map=%p=/UNLIKELY/PATH -emit-llvm -o - %s | FileCheck %s --check-prefix=CHECK-PREFIX-MAP diff --git a/clang/test/CodeGenCXX/default-arguments-with-immediate.cpp b/clang/test/CodeGenCXX/default-arguments-with-immediate.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/default-arguments-with-immediate.cpp @@ -0,0 +1,54 @@ +// RUN: %clang_cc1 -std=c++2a -triple x86_64-elf-gnu %s -emit-llvm -o - | FileCheck %s + +consteval int immediate() { return 0;} +static int ext(); +void f(int a = immediate() + ext()); + +void test_function() { + f(); + f(0); + // CHECK: call noundef i32 @_ZL3extv() + // CHECK: add + // CHECK: call {{.*}} @_Z1fi + // CHECK: call {{.*}} @_Z1fi +} + +// CHECK: define {{.*}} i32 @_ZL3extv() + +static constexpr int not_immediate(); +struct A { + int a = immediate() + not_immediate(); +}; + +void test_member() { + // CHECK: call void @_ZN1AC2Ev + A defaulted; + // CHECK-NOT: call void @_ZN1AC2Ev + A provided{0}; +} + +// CHECK: define {{.*}} void @_ZN1AC2Ev{{.*}} +// CHECK: %call = call noundef i32 @_ZL13not_immediatev() + +int never_referenced() {return 42;}; + + +namespace not_used { + +struct A { + int a = immediate() + never_referenced(); +}; +void f(int a = immediate() + never_referenced()); + +void g() { + A a{0}; + f(0); +} + +} + +static int ext() {return 0;} +static constexpr int not_immediate() {return 0;} + +// CHECK-NOT: define {{.*}} i32 _ZL16never_referencedv()( +// CHECK: define {{.*}} i32 @_ZL13not_immediatev() diff --git a/clang/test/CodeGenCXX/meminit-initializers-odr.cpp b/clang/test/CodeGenCXX/meminit-initializers-odr.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/meminit-initializers-odr.cpp @@ -0,0 +1,140 @@ +// RUN: %clang_cc1 -std=c++20 %s -triple x86_64-linux-gnu -emit-llvm -o - | FileCheck %s + +struct ThisShouldBeCalled { + ThisShouldBeCalled() {} +}; + +template +struct ThisShouldBeCalledTPL { + ThisShouldBeCalledTPL() {} +}; + +consteval int f () { + return 42; +} + +struct WithConsteval { + WithConsteval(int x = f()) {} +}; + +template +struct WithConstevalTPL { + WithConstevalTPL(T x = f()) {} +}; + + +struct Base { + ThisShouldBeCalled y = {}; +}; + +struct S : Base { + ThisShouldBeCalledTPL A = {}; + WithConsteval B = {}; + WithConstevalTPL C = {}; +}; +void Do(S = S{}) {} + +void test() { + Do(); +} + +// CHECK-LABEL: @_ZN18ThisShouldBeCalledC2Ev +// CHECK-LABEL: @_ZN21ThisShouldBeCalledTPLIiEC2Ev +// CHECK-LABEL: @_ZN13WithConstevalC2Ei +// CHECK-LABEL: @_ZN16WithConstevalTPLIdEC2Ed + +namespace check_arrays { + +template +struct inner { + inner() {} +}; + +struct S { + inner a {}; +}; + +class C { + S s[1]{}; +}; + +int f() { + C c; + return 0; +} + +// CHECK-LABEL: @_ZN12check_arrays5innerIiEC2Ev + +} + +namespace check_field_inits_in_base_constructors { + +template +struct ShouldBeODRUsed { + ShouldBeODRUsed() {} +}; +class k { +// The private here is important, +// otherwise it would be aggregate initialized. +private: + ShouldBeODRUsed a = {}; +}; + +struct b { + k c{}; +}; +void test() { b d; } + +// CHECK-LABEL: @_ZN38check_field_inits_in_base_constructors15ShouldBeODRUsedINS_1kEEC2Ev + +} + +namespace check_referenced_when_defined_in_default_parameter { + +template +struct Test { + Test(auto&&) {} +}; + +struct Options { + Test identity = [](bool x) -> bool { return x; }; +}; + +struct Wrapper { + Wrapper(const Options& options = Options()); +}; + +void Func() { Options options; } + +// CHECK-LABEL: @_ZN50check_referenced_when_defined_in_default_parameter7OptionsC2Ev +// CHECK-LABEL: @_ZN50check_referenced_when_defined_in_default_parameter4TestIFbbEEC1INS_7Options8identityMUlbE_EEEOT_ +// CHECK-LABEL: @_ZN50check_referenced_when_defined_in_default_parameter4TestIFbbEEC2INS_7Options8identityMUlbE_EEEOT_ + +} + +namespace lambda_body { +template +int templated_func() { + return 0; +} +struct test_body { + int mem = templated_func(); +}; +struct test_capture { + int mem = templated_func(); +}; + +struct S { + int a = [_ = test_capture{}] { (void)test_body{}; return 0;}(); +}; + +void test() { + S s; +} + +// CHECK-LABEL: define{{.*}} @_ZN11lambda_body14templated_funcIdEEiv +// CHECK-LABEL: define{{.*}} @_ZNK11lambda_body1S1aMUlvE_clEv +// CHECK-LABEL: define{{.*}} @_ZN11lambda_body14templated_funcIiEEiv + + +} diff --git a/clang/test/PCH/default-argument-with-immediate-calls.cpp b/clang/test/PCH/default-argument-with-immediate-calls.cpp new file mode 100644 --- /dev/null +++ b/clang/test/PCH/default-argument-with-immediate-calls.cpp @@ -0,0 +1,34 @@ +// RUN: %clang_cc1 -std=c++20 -emit-pch %s -o %t +// RUN: %clang_cc1 -std=c++20 -include-pch %t -verify %s +// expected-no-diagnostics + +#ifndef HEADER_INCLUDED +#define HEADER_INCLUDED + +consteval int immediate(); +int regular_function() { + return 0; +} + +struct S { + int a = immediate() + regular_function(); +}; + +int f(int arg = immediate()) { + return arg; +} + +#else + +consteval int immediate() { + return 0; +} + +void test() { + f(0); + f(); + S s{0}; + S t{0}; +} + +#endif diff --git a/clang/test/SemaCXX/cxx11-default-member-initializers.cpp b/clang/test/SemaCXX/cxx11-default-member-initializers.cpp --- a/clang/test/SemaCXX/cxx11-default-member-initializers.cpp +++ b/clang/test/SemaCXX/cxx11-default-member-initializers.cpp @@ -1,4 +1,6 @@ // RUN: %clang_cc1 -std=c++11 -verify %s -pedantic +// RUN: %clang_cc1 -std=c++20 -verify %s -pedantic + namespace PR31692 { struct A { @@ -12,3 +14,95 @@ // A::X can now be default-constructed. static_assert(__is_constructible(A::X), ""); } + + +struct S { +} constexpr s; +struct C { + C(S); +}; +class MemInit { + C m = s; +}; + +#if __cplusplus >= 202002L +// This test ensures cleanup expressions are correctly produced +// in the presence of default member initializers. +namespace PR136554 { +struct string { + constexpr string(const char*) {}; + constexpr ~string(); +}; +struct S; +struct optional { + template + constexpr optional(U &&) {} +}; +struct S { + string a; + optional b; + int defaulted = 0; +} test { + "", { + { "", 0 } + } +}; + +// Ensure that the this pointer is +// transformed without crashing +consteval int immediate() { return 0;} +struct StructWithThisInInitializer { + int member() const { + return 0; + } + int m = member() + immediate(); + int m2 = this->member() + immediate(); +}; + +template +struct StructWithThisInInitializerTPL { + template + int member() const { + return 0; + } + int m = member() + immediate(); + int m2 = this->member() + immediate(); +}; + +void test_this() { + (void)StructWithThisInInitializer{}; + (void)StructWithThisInInitializerTPL{}; +} + +struct ReferenceToNestedMembers { + int m; + int a = ((void)immediate(), m); // ensure g is found in the correct scope + int b = ((void)immediate(), this->m); // ensure g is found in the correct scope +}; +struct ReferenceToNestedMembersTest { + void* m = nullptr; + ReferenceToNestedMembers j{0}; +} test_reference_to_nested_members; + +} + + +namespace odr_in_unevaluated_context { +template struct f { + using type = bool; +}; + +template ::type = false> int l; +int m; +struct p { + // This used to crash because m is first marked odr used + // during parsing, but subsequently used in an unevaluated context + // without being transformed. + int o = m; + p() {} +}; + +int i = l

; +} + +#endif diff --git a/clang/test/SemaCXX/cxx2a-consteval-default-params.cpp b/clang/test/SemaCXX/cxx2a-consteval-default-params.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/cxx2a-consteval-default-params.cpp @@ -0,0 +1,81 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++2b %s + +consteval int undefined(); // expected-note 4 {{declared here}} + +void check_lambdas_unused( + int a = [] + { + // The body of a lambda is not a subexpression of the lambda + // so this is immediately evaluated even if the parameter + // is never used. + return undefined(); // expected-error {{not a constant expression}} \ + // expected-note {{undefined function 'undefined'}} + }(), + int b = [](int no_error = undefined()) { + return no_error; + }(0), + int c = [](int defaulted = undefined()) { + return defaulted; + }() +) {} + +int check_lambdas_used( + int b = [](int no_error = undefined()) { + return no_error; + }(0), + int c = [](int defaulted = undefined()) { // expected-error {{not a constant expression}} \ + // expected-note {{declared here}} \ + // expected-note {{undefined function 'undefined'}} + return defaulted; + }(), // expected-note {{in the default initalizer of 'defaulted'}} + int d = [](int defaulted = sizeof(undefined())) { + return defaulted; + }() +) { + return 0; +} + +int test_check_lambdas_used = check_lambdas_used(); + +struct UnusedInitWithLambda { + int a = [] { + return undefined(); // expected-error {{not a constant expression}} \ + // expected-note {{undefined function 'undefined'}} + }(); + // UnusedInitWithLambda is never constructed, so the initializer + // of b and undefined() are never evaluated. + int b = [](int no_error = undefined()) { + return no_error; + }(); +}; + +consteval int ub(int n) { + return 0/n; // expected-note {{division}} +} + +struct InitWithLambda { + int b = [](int error = undefined()) { // expected-error {{not a constant expression}} \ + // expected-note {{declared here}} \ + // expected-note {{undefined function 'undefined'}} + return error; + }(); // expected-note {{in the default initalizer of 'error'}} + int c = [](int error = sizeof(undefined()) + ub(0)) { // expected-error {{'ub' is not a constant expression}} \ + // expected-note {{declared here}} \ + // expected-note {{in call to 'ub(0)}} + return error; + }(); // expected-note {{in the default initalizer of 'error'}} +} i; // expected-note {{in implicit default constructor}} + +namespace ShouldNotCrash { + template + struct F { + template + F(const U&) {} + }; + struct A { + static constexpr auto x = [] {}; + F f = x; + }; + void f(A a = A()) { } +} diff --git a/clang/test/SemaCXX/source_location.cpp b/clang/test/SemaCXX/source_location.cpp --- a/clang/test/SemaCXX/source_location.cpp +++ b/clang/test/SemaCXX/source_location.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -std=c++1z -fcxx-exceptions -fexceptions -verify %s +// RUN: %clang_cc1 -std=c++2a -fcxx-exceptions -DUSE_CONSTEVAL -fexceptions -verify %s // expected-no-diagnostics #define assert(...) ((__VA_ARGS__) ? ((void)0) : throw 42) @@ -8,15 +9,22 @@ template struct Printer; +#ifdef USE_CONSTEVAL +#define SOURCE_LOC_EVAL_KIND consteval +#else +#define SOURCE_LOC_EVAL_KIND constexpr +#endif + namespace std { class source_location { struct __impl; public: - static constexpr source_location current(const __impl *__p = __builtin_source_location()) noexcept { - source_location __loc; - __loc.__m_impl = __p; - return __loc; + static SOURCE_LOC_EVAL_KIND source_location + current(const __impl *__p = __builtin_source_location()) noexcept { + source_location __loc; + __loc.__m_impl = __p; + return __loc; } constexpr source_location() = default; constexpr source_location(source_location const &) = default; @@ -593,3 +601,73 @@ } static_assert(test()); } + +namespace Lambda { +#line 8000 "TestLambda.cpp" +constexpr int nested_lambda(int l = []{ + return SL::current().line(); +}()) { + return l; +} +static_assert(nested_lambda() == __LINE__ - 4); + +constexpr int lambda_param(int l = [](int l = SL::current().line()) { + return l; +}()) { + return l; +} +static_assert(lambda_param() == __LINE__); + + +} + +constexpr int compound_literal_fun(int a = + (int){ SL::current().line() } +) { return a ;} +static_assert(compound_literal_fun() == __LINE__); + +struct CompoundLiteral { + int a = (int){ SL::current().line() }; +}; +static_assert(CompoundLiteral{}.a == __LINE__); + + +// FIXME +// Init captures are subexpressions of the lambda expression +// so according to the standard immediate invocations in init captures +// should be evaluated at the call site. +// However Clang does not yet implement this as it would introduce +// a fair bit of complexity. +// We intend to implement that functionality once we find real world +// use cases that require it. +constexpr int test_init_capture(int a = + [b = SL::current().line()] { return b; }()) { + return a; +} +#ifdef USE_CONSTEVAL +static_assert(test_init_capture() == __LINE__ - 4); +#else +static_assert(test_init_capture() == __LINE__ ); +#endif + +namespace check_immediate_invocations_in_templates { + +template +struct G { + T line = __builtin_LINE(); +}; +template +struct S { + int i = G{}.line; +}; +static_assert(S{}.i != // intentional new line + S{}.i); + +template +constexpr int f(int i = G{}.line) { + return i; +} + +static_assert(f() != // intentional new line + f()); +} diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html --- a/clang/www/cxx_dr_status.html +++ b/clang/www/cxx_dr_status.html @@ -15593,7 +15593,7 @@ 2631 DR Immediate function evaluations in default arguments - Unknown + Clang 16 2632