https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/219288
>From cd9fbd40edaaddd8d2c71060625b34ca06b4fd7d Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> Date: Thu, 27 Aug 2026 12:44:02 -0700 Subject: [PATCH 1/4] [clang][Sema] Separate aggregate default member initializer evaluation A default member initializer used by a constructor is a separate full-expression, while one used during aggregate initialization belongs to the full-expression containing the aggregate initialization. Split the two building paths so aggregate initialization rebuilds the initializer in the surrounding evaluation context. Signed-off-by: yronglin <[email protected]> --- clang/docs/ReleaseNotes.md | 4 + clang/include/clang/AST/ParentMap.h | 31 +++ clang/include/clang/Sema/Sema.h | 23 +- clang/lib/AST/ByteCode/Compiler.cpp | 64 +++++- clang/lib/AST/ParentMap.cpp | 17 ++ clang/lib/Analysis/CFG.cpp | 55 ++++- clang/lib/Analysis/ReachableCode.cpp | 67 ++---- clang/lib/Sema/SemaDeclCXX.cpp | 10 +- clang/lib/Sema/SemaExpr.cpp | 210 +++++++++++------- clang/lib/Sema/SemaInit.cpp | 28 +-- clang/lib/Sema/TreeTransform.h | 2 +- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 57 +++-- clang/test/AST/ByteCode/records.cpp | 23 +- clang/test/AST/ast-dump-default-init.cpp | 67 +++--- clang/test/AST/ast-dump-recovery.cpp | 2 +- .../Analysis/lifetime-extended-regions.cpp | 7 +- .../aggregate-default-member-initializers.cpp | 42 ++++ .../aggregate-default-member-initializers.cpp | 103 +++++++++ clang/test/SemaCXX/cxx2c-placeholder-vars.cpp | 8 +- clang/test/SemaCXX/warn-unreachable.cpp | 104 +++++++++ .../UncheckedOptionalAccessModelTest.cpp | 18 ++ 21 files changed, 708 insertions(+), 234 deletions(-) create mode 100644 clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp create mode 100644 clang/test/SemaCXX/aggregate-default-member-initializers.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 35aae605d8476..50cfa0e75188b 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -468,6 +468,10 @@ features cannot lower the translation-unit ABI level; #### Bug Fixes to C++ Support +- Fixed the destruction timing of temporaries created by default member + initializers during aggregate initialization. Such an initializer is part of + the full-expression containing the aggregate initialization. (#GH85601) + - Fixed an issue where `__typeof__` incorrectly rejected cv-qualified function types. - Fixed a bug where top-level CV qualifiers (such as ``const``) were dropped from pointers modified by Microsoft pointer attributes (like ``__ptr32`` and ``__ptr64``) and WebAssembly's ``__funcref``. diff --git a/clang/include/clang/AST/ParentMap.h b/clang/include/clang/AST/ParentMap.h index 86e2f048a3445..5853a0be0a483 100644 --- a/clang/include/clang/AST/ParentMap.h +++ b/clang/include/clang/AST/ParentMap.h @@ -13,6 +13,8 @@ #ifndef LLVM_CLANG_AST_PARENTMAP_H #define LLVM_CLANG_AST_PARENTMAP_H +#include "llvm/Support/Casting.h" + namespace clang { class Stmt; class Expr; @@ -39,6 +41,25 @@ class ParentMap { Stmt *getParentIgnoreParenImpCasts(Stmt *) const; Stmt *getOuterParenParent(Stmt *) const; + template <typename... Ts> Stmt *getOuterMostAncestor(Stmt *S) const { + Stmt *Res = nullptr; + while (S) { + if (llvm::isa<Ts...>(S)) + Res = S; + S = getParent(S); + } + return Res; + } + + template <typename... Ts> Stmt *getInnerMostAncestor(Stmt *S) const { + while (S) { + if (llvm::isa<Ts...>(S)) + return S; + S = getParent(S); + } + return nullptr; + } + const Stmt *getParent(const Stmt* S) const { return getParent(const_cast<Stmt*>(S)); } @@ -51,6 +72,16 @@ class ParentMap { return getParentIgnoreParenCasts(const_cast<Stmt*>(S)); } + template <typename... Ts> + const Stmt *getOuterMostAncestor(const Stmt *S) const { + return getOuterMostAncestor<Ts...>(const_cast<Stmt *>(S)); + } + + template <typename... Ts> + const Stmt *getInnerMostAncestor(const Stmt *S) const { + return getInnerMostAncestor<Ts...>(const_cast<Stmt *>(S)); + } + bool hasParent(const Stmt *S) const { return getParent(S) != nullptr; } bool isConsumedExpr(Expr *E) const; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index dcf112fd8eaa4..10a764a403e37 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -5543,6 +5543,10 @@ class Sema final : public SemaBase { ExprResult ConvertMemberDefaultInitExpression(FieldDecl *FD, Expr *InitExpr, SourceLocation InitLoc); + ExprResult ConvertMemberDefaultInitExpression(FieldDecl *FD, + const InitializedEntity &Entity, + Expr *InitExpr, + SourceLocation InitLoc); /// FinalizeVarWithDestructor - Prepare for calling destructor on the /// constructed variable. @@ -7712,7 +7716,24 @@ class Sema final : public SemaBase { /// Emit a warning for all pending noderef expressions that we recorded. void WarnOnPendingNoDerefs(ExpressionEvaluationContextRecord &Rec); - ExprResult BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field); +private: + /// Shared logic for building default member initializer which used in a + /// constructor or an aggregate initialization. + /// + /// + /// The caller enters that evaluation context and decides whether the result + /// is finished as a full-expression. \p NestedDefaultChecking and + /// \p NeedRebuild have to be sampled before entering it. + ExprResult BuildCXXDefaultInitInternal(SourceLocation Loc, FieldDecl *Field, + const InitializedEntity &Entity, + bool NestedDefaultChecking, + bool NeedRebuild); + +public: + ExprResult BuildCXXCtorDefaultInitExpr(SourceLocation Loc, FieldDecl *Field); + ExprResult + BuildCXXAggregateDefaultInitExpr(SourceLocation Loc, FieldDecl *Field, + const InitializedEntity &MemberEntity); /// Instantiate or parse a C++ default argument expression as necessary. /// Return true on error. diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index c182639ea07f8..6b02a57a0deda 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -50,6 +50,22 @@ static bool isSideEffectFree(const Expr *E) { return false; } +static bool containsDefaultInitExpr(const Expr *E) { + class Finder final : public ConstDynamicRecursiveASTVisitor { + public: + Finder() { ShouldVisitImplicitCode = true; } + + bool VisitCXXDefaultInitExpr(const CXXDefaultInitExpr *) override { + Found = true; + return true; + } + + bool Found = false; + } F; + F.TraverseStmt(E); + return F.Found; +} + /// Scope chain managing the variable lifetimes. template <class Emitter> class VariableScope { public: @@ -265,7 +281,9 @@ template <class Emitter> class InitStackScope final { public: InitStackScope(Compiler<Emitter> *Ctx, bool Active) : Ctx(Ctx), OldValue(Ctx->InitStackActive), Active(Active) { - Ctx->InitStackActive = Active; + // An explicit initializer nested in a default member initializer still + // needs the surrounding default initializer's `this` reconstruction. + Ctx->InitStackActive = OldValue || Active; if (Active) Ctx->InitStack.push_back(InitLink::DIE()); } @@ -3475,6 +3493,18 @@ bool Compiler<Emitter>::VisitExprWithCleanups(const ExprWithCleanups *E) { LocalScope<Emitter> ES(this, ScopeKind::FullExpression); const Expr *SubExpr = E->getSubExpr(); + if (DiscardResult && !SubExpr->isGLValue() && + !canClassify(SubExpr->getType()) && containsDefaultInitExpr(SubExpr)) { + UnsignedOrNone LocalIndex = + allocateLocal(SubExpr, QualType(), ScopeKind::FullExpression); + if (!LocalIndex) + return false; + InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex)); + if (!this->emitGetPtrLocal(*LocalIndex, E)) + return false; + return this->visitInitializerPop(SubExpr) && ES.destroyLocals(E); + } + return this->delegate(SubExpr) && ES.destroyLocals(E); } @@ -3541,8 +3571,12 @@ bool Compiler<Emitter>::VisitMaterializeTemporaryExpr( // Non-primitive values. if (!this->emitGetPtrGlobal(*GlobalIndex, E)) return false; + if (!this->emitStartInit(E)) + return false; if (!this->visitInitializer(Inner)) return false; + if (!this->emitEndInit(E)) + return false; if (IsStatic) { assert(TempDecl); return this->emitInitGlobalTempComp(TempDecl, E); @@ -3585,7 +3619,11 @@ bool Compiler<Emitter>::VisitMaterializeTemporaryExpr( if (!this->emitGetPtrLocal(*LocalIndex, E)) return false; - return this->visitInitializer(Inner); + if (!this->emitStartInit(E)) + return false; + if (!this->visitInitializer(Inner)) + return false; + return this->emitEndInit(E); } return false; } @@ -4888,6 +4926,22 @@ bool Compiler<Emitter>::VisitStmtExpr(const StmtExpr *E) { } template <class Emitter> bool Compiler<Emitter>::discard(const Expr *E) { + // A discarded composite prvalue still needs a result object when a default + // member initializer refers to previously initialized subobjects. Let an + // ExprWithCleanups establish its full-expression scope before allocating + // that object. + if (!isa<ExprWithCleanups>(E) && !E->isGLValue() && + !canClassify(E->getType()) && containsDefaultInitExpr(E)) { + UnsignedOrNone LocalIndex = + allocateLocal(E, QualType(), ScopeKind::FullExpression); + if (!LocalIndex) + return false; + InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex)); + if (!this->emitGetPtrLocal(*LocalIndex, E)) + return false; + return this->visitInitializerPop(E); + } + OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/true, /*NewInitializing=*/false, /*ToLValue=*/false); return this->Visit(E); @@ -5451,8 +5505,14 @@ bool Compiler<Emitter>::visitExpr(const Expr *E, bool DestroyToplevelScope) { if (!this->emitGetPtrLocal(*LocalOffset, E)) return false; + // A const-qualified result object is writable while it is being + // initialized, just like an object evaluated through visitVarDecl(). + if (!this->emitStartInit(E)) + return false; if (!visitInitializer(E)) return false; + if (!this->emitEndInit(E)) + return false; // We are destroying the locals AFTER the Ret op. // The Ret op needs to copy the (alive) values, but the // destructors may still turn the entire expression invalid. diff --git a/clang/lib/AST/ParentMap.cpp b/clang/lib/AST/ParentMap.cpp index e62e71bf5a514..580613b2618fb 100644 --- a/clang/lib/AST/ParentMap.cpp +++ b/clang/lib/AST/ParentMap.cpp @@ -13,6 +13,7 @@ #include "clang/AST/ParentMap.h" #include "clang/AST/Decl.h" #include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/StmtObjC.h" #include "llvm/ADT/DenseMap.h" @@ -103,6 +104,22 @@ static void BuildParentMap(MapTy& M, Stmt* S, BuildParentMap(M, SubStmt, OVMode); } break; + case Stmt::CXXDefaultArgExprClass: + if (auto *Arg = dyn_cast<CXXDefaultArgExpr>(S)) { + if (Arg->hasRewrittenInit()) { + M[Arg->getExpr()] = S; + BuildParentMap(M, Arg->getExpr(), OVMode); + } + } + break; + case Stmt::CXXDefaultInitExprClass: + if (auto *Init = dyn_cast<CXXDefaultInitExpr>(S)) { + if (Init->hasRewrittenInit()) { + M[Init->getExpr()] = S; + BuildParentMap(M, Init->getExpr(), OVMode); + } + } + break; default: for (Stmt *SubStmt : S->children()) { if (SubStmt) { diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index 5263114ebca28..f92e6f3dcbf47 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -581,6 +581,10 @@ class CFGBuilder { private: // Visitors to walk an AST and construct the CFG. + CFGBlock *VisitCXXDefaultArgExpr(CXXDefaultArgExpr *Default, + AddStmtChoice asc); + CFGBlock *VisitCXXDefaultInitExpr(CXXDefaultInitExpr *Default, + AddStmtChoice asc); CFGBlock *VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc); CFGBlock *VisitAddrLabelExpr(AddrLabelExpr *A, AddStmtChoice asc); CFGBlock *VisitAttributedStmt(AttributedStmt *A, AddStmtChoice asc); @@ -2405,16 +2409,10 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc, asc, ExternallyDestructed); case Stmt::CXXDefaultArgExprClass: + return VisitCXXDefaultArgExpr(cast<CXXDefaultArgExpr>(S), asc); + case Stmt::CXXDefaultInitExprClass: - // FIXME: The expression inside a CXXDefaultArgExpr is owned by the - // called function's declaration, not by the caller. If we simply add - // this expression to the CFG, we could end up with the same Expr - // appearing multiple times (PR13385). - // - // It's likewise possible for multiple CXXDefaultInitExprs for the same - // expression to be used in the same function (through aggregate - // initialization). - return VisitStmt(S, asc); + return VisitCXXDefaultInitExpr(cast<CXXDefaultInitExpr>(S), asc); case Stmt::CXXBindTemporaryExprClass: return VisitCXXBindTemporaryExpr(cast<CXXBindTemporaryExpr>(S), asc); @@ -2597,6 +2595,45 @@ CFGBlock *CFGBuilder::VisitCallExprChildren(CallExpr *C) { return VisitChildren(C); } +CFGBlock *CFGBuilder::VisitCXXDefaultArgExpr(CXXDefaultArgExpr *Arg, + AddStmtChoice asc) { + if (Arg->hasRewrittenInit()) { + if (asc.alwaysAdd(*this, Arg)) { + autoCreateBlock(); + appendStmt(Block, Arg); + } + return VisitStmt(Arg->getExpr()->IgnoreParens(), asc); + } + + // We can't add the default argument if it's not rewritten because the + // expression inside a CXXDefaultArgExpr is owned by the called function's + // declaration, not by the caller. We could end up with the same expression + // appearing multiple times. + return VisitStmt(Arg, asc); +} + +CFGBlock *CFGBuilder::VisitCXXDefaultInitExpr(CXXDefaultInitExpr *Init, + AddStmtChoice asc) { + if (Init->hasRewrittenInit()) { + if (asc.alwaysAdd(*this, Init)) { + autoCreateBlock(); + appendStmt(Block, Init); + } + + // Unlike CXXDefaultArgExpr::getExpr, which strips off the top-level + // FullExpr and ConstantExpr, CXXDefaultInitExpr::getExpr does not do this, + // so the top level cannot be a ParenExpr. Use Visit rather than VisitStmt + // so that control flow inside the initializer (a conditional operator, for + // instance) is decomposed into blocks instead of being laid out linearly. + return Visit(Init->getExpr(), asc); + } + + // We can't add the default initializer if it's not rewritten because multiple + // CXXDefaultInitExprs can refer to the same subexpression in the same + // function (through aggregate initialization). + return VisitStmt(Init, asc); +} + CFGBlock *CFGBuilder::VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc) { if (asc.alwaysAdd(*this, ILE)) { autoCreateBlock(); diff --git a/clang/lib/Analysis/ReachableCode.cpp b/clang/lib/Analysis/ReachableCode.cpp index 4a9ab5d9f0f73..7d17a0cb4cfd4 100644 --- a/clang/lib/Analysis/ReachableCode.cpp +++ b/clang/lib/Analysis/ReachableCode.cpp @@ -25,6 +25,7 @@ #include "clang/Basic/SourceManager.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/BitVector.h" +#include <cstddef> #include <optional> using namespace clang; @@ -396,6 +397,7 @@ namespace { SmallVector<const CFGBlock *, 10> WorkList; Preprocessor &PP; ASTContext &C; + AnalysisDeclContext &AC; typedef SmallVector<std::pair<const CFGBlock *, const Stmt *>, 12> DeferredLocsTy; @@ -403,10 +405,10 @@ namespace { DeferredLocsTy DeferredLocs; public: - DeadCodeScan(llvm::BitVector &reachable, Preprocessor &PP, ASTContext &C) - : Visited(reachable.size()), - Reachable(reachable), - PP(PP), C(C) {} + DeadCodeScan(llvm::BitVector &reachable, Preprocessor &PP, + AnalysisDeclContext &AC) + : Visited(reachable.size()), Reachable(reachable), PP(PP), + C(AC.getASTContext()), AC(AC) {} void enqueue(const CFGBlock *block); unsigned scanBackwards(const CFGBlock *Start, @@ -453,47 +455,8 @@ bool DeadCodeScan::isDeadCodeRoot(const clang::CFGBlock *Block) { return isDeadRoot; } -// Check if the given `DeadStmt` is a coroutine statement and is a substmt of -// the coroutine statement. `Block` is the CFGBlock containing the `DeadStmt`. -static bool isInCoroutineStmt(const Stmt *DeadStmt, const CFGBlock *Block) { - // The coroutine statement, co_return, co_await, or co_yield. - const Stmt *CoroStmt = nullptr; - // Find the first coroutine statement after the DeadStmt in the block. - bool AfterDeadStmt = false; - for (const CFGElement &Elem : *Block) - if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>()) { - const Stmt *S = CS->getStmt(); - if (S == DeadStmt) - AfterDeadStmt = true; - if (AfterDeadStmt && - // For simplicity, we only check simple coroutine statements. - (llvm::isa<CoreturnStmt>(S) || llvm::isa<CoroutineSuspendExpr>(S))) { - CoroStmt = S; - break; - } - } - if (!CoroStmt) - return false; - struct Checker : DynamicRecursiveASTVisitor { - const Stmt *DeadStmt; - bool CoroutineSubStmt = false; - Checker(const Stmt *S) : DeadStmt(S) { - // Statements captured in the CFG can be implicit. - ShouldVisitImplicitCode = true; - } - - bool VisitStmt(Stmt *S) override { - if (S == DeadStmt) - CoroutineSubStmt = true; - return true; - } - }; - Checker checker(DeadStmt); - checker.TraverseStmt(const_cast<Stmt *>(CoroStmt)); - return checker.CoroutineSubStmt; -} - -static bool isValidDeadStmt(const Stmt *S, const clang::CFGBlock *Block) { +static bool isValidDeadStmt(ParentMap &PM, const Stmt *S, + const clang::CFGBlock *) { if (S->getBeginLoc().isInvalid()) return false; if (const BinaryOperator *BO = dyn_cast<BinaryOperator>(S)) @@ -501,21 +464,27 @@ static bool isValidDeadStmt(const Stmt *S, const clang::CFGBlock *Block) { // Coroutine statements are never considered dead statements, because removing // them may change the function semantic if it is the only coroutine statement // of the coroutine. - return !isInCoroutineStmt(S, Block); + return !PM.getInnerMostAncestor<CoreturnStmt, CoroutineSuspendExpr>(S); } const Stmt *DeadCodeScan::findDeadCode(const clang::CFGBlock *Block) { + auto &PM = AC.getParentMap(); + for (CFGBlock::const_iterator I = Block->begin(), E = Block->end(); I!=E; ++I) if (std::optional<CFGStmt> CS = I->getAs<CFGStmt>()) { const Stmt *S = CS->getStmt(); - if (isValidDeadStmt(S, Block)) + auto *RewrittenParent = + PM.getOuterMostAncestor<CXXDefaultArgExpr, CXXDefaultInitExpr>(S); + if (RewrittenParent) + S = RewrittenParent; + if (isValidDeadStmt(AC.getParentMap(), S, Block)) return S; } CFGTerminator T = Block->getTerminator(); if (T.isStmtBranch()) { const Stmt *S = T.getStmt(); - if (S && isValidDeadStmt(S, Block)) + if (S && isValidDeadStmt(AC.getParentMap(), S, Block)) return S; } @@ -761,7 +730,7 @@ void FindUnreachableCode(AnalysisDeclContext &AC, Preprocessor &PP, if (reachable[block->getBlockID()]) continue; - DeadCodeScan DS(reachable, PP, AC.getASTContext()); + DeadCodeScan DS(reachable, PP, AC); numReachable += DS.scanBackwards(block, CB); if (numReachable == cfg->getNumBlockIDs()) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 4e90c496de342..f03d615bdce5b 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -4242,6 +4242,12 @@ ExprResult Sema::ConvertMemberDefaultInitExpression(FieldDecl *FD, SourceLocation InitLoc) { InitializedEntity Entity = InitializedEntity::InitializeMemberFromDefaultMemberInitializer(FD); + return ConvertMemberDefaultInitExpression(FD, Entity, InitExpr, InitLoc); +} + +ExprResult Sema::ConvertMemberDefaultInitExpression( + FieldDecl *FD, const InitializedEntity &Entity, Expr *InitExpr, + SourceLocation InitLoc) { InitializationKind Kind = FD->getInClassInitStyle() == ICIS_ListInit ? InitializationKind::CreateDirectList(InitExpr->getBeginLoc(), @@ -5342,7 +5348,7 @@ static bool CollectFieldInitializer(Sema &SemaRef, BaseAndFieldInfo &Info, if (Field->hasInClassInitializer() && !Info.isImplicitCopyOrMove()) { ExprResult DIE = - SemaRef.BuildCXXDefaultInitExpr(Info.Ctor->getLocation(), Field); + SemaRef.BuildCXXCtorDefaultInitExpr(Info.Ctor->getLocation(), Field); if (DIE.isInvalid()) return true; @@ -14129,7 +14135,7 @@ bool SpecialMemberExceptionSpecInfo::visitField(FieldDecl *FD) { // FIXME: We should have a single context note pointing at Loc, and // this location should be MD->getLocation() instead, since that's // the location where we actually use the default init expression. - E = S.BuildCXXDefaultInitExpr(Loc, FD).get(); + E = S.BuildCXXCtorDefaultInitExpr(Loc, FD).get(); if (E) ExceptSpec.CalledExpr(E); } else if (auto *RD = S.Context.getBaseElementType(FD->getType()) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 2b524a956ecc4..31b308ad4b52b 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -5876,39 +5876,51 @@ static FieldDecl *FindFieldDeclInstantiationPattern(const ASTContext &Ctx, return cast<FieldDecl>(*Rng.begin()); } -ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { - assert(Field->hasInClassInitializer()); - - CXXThisScopeRAII This(*this, Field->getParent(), Qualifiers()); - +ExprResult Sema::BuildCXXDefaultInitInternal(SourceLocation Loc, + FieldDecl *Field, + const InitializedEntity &Entity, + bool NestedDefaultChecking, + bool NeedRebuild) { auto *ParentRD = cast<CXXRecordDecl>(Field->getParent()); - std::optional<ExpressionEvaluationContextRecord::InitializationContext> - InitializationContext = - OutermostDeclarationWithDelayedImmediateInvocations(); - if (!InitializationContext.has_value()) - InitializationContext.emplace(Loc, Field, CurContext); - - Expr *Init = nullptr; - - bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer(); - bool NeedRebuild = needsRebuildOfDefaultArgOrInit(); - EnterExpressionEvaluationContext EvalContext( - *this, ExpressionEvaluationContext::PotentiallyEvaluated, Field); - - if (!Field->getInClassInitializer()) { + if (!Field->getInClassInitializer() && + isTemplateInstantiation(ParentRD->getTemplateSpecializationKind())) { // Maybe we haven't instantiated the in-class initializer. Go check the // pattern FieldDecl to see if it has one. - if (isTemplateInstantiation(ParentRD->getTemplateSpecializationKind())) { - FieldDecl *Pattern = - FindFieldDeclInstantiationPattern(getASTContext(), Field); - assert(Pattern && "We must have set the Pattern!"); - if (!Pattern->hasInClassInitializer() || - InstantiateInClassInitializer(Loc, Field, Pattern, - getTemplateInstantiationArgs(Field))) { - return ExprError(); - } - } + FieldDecl *Pattern = + FindFieldDeclInstantiationPattern(getASTContext(), Field); + assert(Pattern && "We must have set the Pattern!"); + if (!Pattern->hasInClassInitializer() || + InstantiateInClassInitializer(Loc, Field, Pattern, + getTemplateInstantiationArgs(Field))) + return ExprError(); + } + + Expr *InClassInit = Field->getInClassInitializer(); + if (!InClassInit) { + // 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(); } // CWG2631 @@ -5927,27 +5939,27 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { // expression is an ExprWithCleanups. Then make sure the normal lifetime // extension code recurses into the default initializer and does lifetime // extension when warranted. - bool ContainsAnyTemporaries = - isa_and_present<ExprWithCleanups>(Field->getInClassInitializer()); - if (Field->getInClassInitializer() && - !Field->getInClassInitializer()->containsErrors() && + bool ContainsAnyTemporaries = isa<ExprWithCleanups>(InClassInit); + Expr *Init = InClassInit; + if (!InClassInit->containsErrors() && (V.HasImmediateCalls || (NeedRebuild && ContainsAnyTemporaries))) { ExprEvalContexts.back().DelayedDefaultInitializationContext = {Loc, Field, CurContext}; ExprEvalContexts.back().IsCurrentlyCheckingDefaultArgumentOrInitializer = NestedDefaultChecking; // Pass down lifetime extending flag, and collect temporaries in - // CreateMaterializeTemporaryExpr when we rewrite the call argument. + // CreateMaterializeTemporaryExpr when we rewrite the initializer. currentEvaluationContext().InLifetimeExtendingContext = parentEvaluationContext().InLifetimeExtendingContext; + EnsureImmediateInvocationInDefaultArgs Immediate(*this); ExprResult Res; runWithSufficientStackSpace(Loc, [&] { - Res = Immediate.TransformInitializer(Field->getInClassInitializer(), + Res = Immediate.TransformInitializer(InClassInit, /*CXXDirectInit=*/false); }); if (!Res.isInvalid()) - Res = ConvertMemberDefaultInitExpression(Field, Res.get(), Loc); + Res = ConvertMemberDefaultInitExpression(Field, Entity, Res.get(), Loc); if (Res.isInvalid()) { Field->setInvalidDecl(); return ExprError(); @@ -5955,52 +5967,92 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { Init = Res.get(); } - if (Field->getInClassInitializer()) { - Expr *E = Init ? Init : Field->getInClassInitializer(); - if (!NestedDefaultChecking) - runWithSufficientStackSpace(Loc, [&] { - MarkDeclarationsReferencedInExpr(E, /*SkipLocalVariables=*/false); - }); - if (isInLifetimeExtendingContext()) - DiscardCleanupsInEvaluationContext(); - // 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(); + if (!NestedDefaultChecking) + runWithSufficientStackSpace(Loc, [&] { + MarkDeclarationsReferencedInExpr(Init, /*SkipLocalVariables=*/false); + }); + return Init; +} - return CXXDefaultInitExpr::Create(Context, InitializationContext->Loc, - Field, InitializationContext->Context, - Init); - } +ExprResult Sema::BuildCXXCtorDefaultInitExpr(SourceLocation Loc, + FieldDecl *Field) { + assert(Field->hasInClassInitializer()); - // 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()) + bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer(); + bool NeedRebuild = needsRebuildOfDefaultArgOrInit(); + + // C++11 [class.base.init]p7: + // The initialization of each base and member constitutes a + // full-expression. + // So this initializer gets an evaluation context of its own, and is finished + // as a full-expression below. + EnterExpressionEvaluationContext EvalContext( + *this, ExpressionEvaluationContext::PotentiallyEvaluated, Field); + CXXThisScopeRAII This(*this, Field->getParent(), Qualifiers()); + + auto InitContext = OutermostDeclarationWithDelayedImmediateInvocations(); + if (!InitContext) + InitContext.emplace(Loc, Field, CurContext); + + ExprResult Init = BuildCXXDefaultInitInternal( + Loc, Field, + InitializedEntity::InitializeMemberFromDefaultMemberInitializer(Field), + NestedDefaultChecking, NeedRebuild); + if (Init.isInvalid()) + return ExprError(); + + if (isInLifetimeExtendingContext()) + DiscardCleanupsInEvaluationContext(); + Init = ActOnFinishFullExpr(Init.get(), /*DiscardedValue=*/false); + if (Init.isInvalid()) { Field->setInvalidDecl(); - return ExprError(); + return ExprError(); + } + + return CXXDefaultInitExpr::Create( + Context, InitContext->Loc, Field, InitContext->Context, + Init.get() == Field->getInClassInitializer() ? nullptr : Init.get()); +} + +ExprResult +Sema::BuildCXXAggregateDefaultInitExpr(SourceLocation Loc, FieldDecl *Field, + const InitializedEntity &MemberEntity) { + assert(Field->hasInClassInitializer()); + + bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer(); + + // Unlike a mem-initializer, this initializer is a subexpression of the + // full-expression containing the aggregate initialization. It is evaluated + // exactly as that full-expression is, so inherit the enclosing context kind + // rather than forcing a potentially evaluated one. + EnterExpressionEvaluationContext EvalContext( + *this, currentEvaluationContext().Context, Field); + CXXThisScopeRAII This(*this, Field->getParent(), Qualifiers()); + + auto InitContext = OutermostDeclarationWithDelayedImmediateInvocations(); + if (!InitContext) + InitContext.emplace(Loc, Field, CurContext); + + // CWG1815: always rebuild, never share the AST built when the field was + // declared. Only a copy rebuilt here has its MaterializeTemporaryExprs + // collected in this context, which is what lets the aggregate initialization + // lifetime-extend them; sharing one AST would also make several uses of the + // same field fight over its extension. A mem-initializer has no such need, + // as its temporaries die at the end of the initializer itself. + ExprResult Init = BuildCXXDefaultInitInternal( + Loc, Field, MemberEntity, NestedDefaultChecking, /*NeedRebuild=*/true); + if (Init.isInvalid()) + return ExprError(); + + // Deliberately not finished as a full-expression: leaving the temporaries it + // created on ExprCleanupObjects lets PopExpressionEvaluationContext merge + // them into the enclosing context, which eventually wraps them all in a + // single ExprWithCleanups. They are then destroyed at the end of the + // containing full-expression, in reverse construction order. + + return CXXDefaultInitExpr::Create( + Context, InitContext->Loc, Field, InitContext->Context, + Init.get() == Field->getInClassInitializer() ? nullptr : Init.get()); } VariadicCallType Sema::getVariadicCallType(FunctionDecl *FDecl, diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 48ce51863c2c0..2b756b803e205 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -813,28 +813,15 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field, if (VerifyOnly) return; - ExprResult DIE; - { - // Enter a default initializer rebuild context, then we can support - // lifetime extension of temporary created by aggregate initialization - // using a default member initializer. - // CWG1815 (https://wg21.link/CWG1815). - EnterExpressionEvaluationContext RebuildDefaultInit( - SemaRef, Sema::ExpressionEvaluationContext::PotentiallyEvaluated); - SemaRef.currentEvaluationContext().RebuildDefaultArgOrDefaultInit = - true; - SemaRef.currentEvaluationContext().DelayedDefaultInitializationContext = - SemaRef.parentEvaluationContext() - .DelayedDefaultInitializationContext; - SemaRef.currentEvaluationContext().InLifetimeExtendingContext = - SemaRef.parentEvaluationContext().InLifetimeExtendingContext; - DIE = SemaRef.BuildCXXDefaultInitExpr(Loc, Field); - } + // A default member initializer used in aggregate initialization is part + // of the full-expression containing the aggregate initialization. Do not + // create or finish a separate expression evaluation context here. + ExprResult DIE = + SemaRef.BuildCXXAggregateDefaultInitExpr(Loc, Field, MemberEntity); if (DIE.isInvalid()) { hadError = true; return; } - SemaRef.checkInitializerLifetime(MemberEntity, DIE.get()); if (Init < NumInits) ILE->setInit(Init, DIE.get()); else { @@ -6152,11 +6139,10 @@ static void TryOrBuildParenListInitialization( // C++ [dcl.init]p16.6.2.2 // The remaining elements are initialized with their default // member initializers, if any - ExprResult DIE = S.BuildCXXDefaultInitExpr( - Kind.getParenOrBraceRange().getEnd(), FD); + ExprResult DIE = S.BuildCXXAggregateDefaultInitExpr( + Kind.getParenOrBraceRange().getEnd(), FD, SubEntity); if (DIE.isInvalid()) return; - S.checkInitializerLifetime(SubEntity, DIE.get()); InitExprs.push_back(DIE.get()); } } else { diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 024f726b188b7..83c125cebde5d 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -3521,7 +3521,7 @@ class TreeTransform { /// routine to provide different behavior. ExprResult RebuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { - return getSema().BuildCXXDefaultInitExpr(Loc, Field); + return getSema().BuildCXXCtorDefaultInitExpr(Loc, Field); } /// Build a new C++ zero-initialization expression. diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 530fae9ee2dee..e2f30968e02ff 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1958,32 +1958,47 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, ExplodedNodeSet Tmp; - const Expr *ArgE; - if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S)) + bool HasRebuiltInit = false; + const Expr *ArgE = nullptr; + if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S)) { ArgE = DefE->getExpr(); - else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S)) + HasRebuiltInit = DefE->hasRewrittenInit(); + } else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S)) { ArgE = DefE->getExpr(); - else + HasRebuiltInit = DefE->hasRewrittenInit(); + } else llvm_unreachable("unknown constant wrapper kind"); - bool IsTemporary = false; - if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(ArgE)) { - ArgE = MTE->getSubExpr(); - IsTemporary = true; - } + if (HasRebuiltInit) { + for (const auto N : PreVisit) { + const StackFrame *SF = N->getStackFrame(); + ProgramStateRef State = N->getState(); + State = State->BindExpr(cast<Expr>(S), SF, State->getSVal(ArgE, SF)); + Tmp.insert(Engine.makePostStmtNode(S, State, N)); + } + } else { + // If it's not rewritten, the contents of these expressions are not + // actually part of the current function, so we fall back to constant + // evaluation. + bool IsTemporary = false; + if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(ArgE)) { + ArgE = MTE->getSubExpr(); + IsTemporary = true; + } - std::optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE); - if (!ConstantVal) - ConstantVal = UnknownVal(); - - const StackFrame *SF = Pred->getStackFrame(); - for (const auto I : PreVisit) { - ProgramStateRef State = I->getState(); - State = State->BindExpr(cast<Expr>(S), SF, *ConstantVal); - if (IsTemporary) - State = createTemporaryRegionIfNeeded(State, SF, cast<Expr>(S), - cast<Expr>(S)); - Tmp.insert(Engine.makePostStmtNode(S, State, I)); + std::optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE); + if (!ConstantVal) + ConstantVal = UnknownVal(); + + for (const auto I : PreVisit) { + const StackFrame *SF = I->getStackFrame(); + ProgramStateRef State = I->getState(); + State = State->BindExpr(cast<Expr>(S), SF, *ConstantVal); + if (IsTemporary) + State = createTemporaryRegionIfNeeded(State, SF, cast<Expr>(S), + cast<Expr>(S)); + Tmp.insert(Engine.makePostStmtNode(S, State, I)); + } } getCheckerManager().runCheckersForPostStmt(Dst, Tmp, S, *this); diff --git a/clang/test/AST/ByteCode/records.cpp b/clang/test/AST/ByteCode/records.cpp index 36b5cb62fe95f..f70c7f641120a 100644 --- a/clang/test/AST/ByteCode/records.cpp +++ b/clang/test/AST/ByteCode/records.cpp @@ -1302,11 +1302,30 @@ namespace { }; constexpr int a() { int x = 1; - int f = B{x}.x; + { + B b{x}; + } + return x; + } + static_assert(a() == 0); + + constexpr int discarded() { + int x = 1; B{x}; // both-warning {{expression result unused}} + return x; + } + static_assert(discarded() == 0); - return 1; + /// A const-qualified composite result is writable while under construction. + constexpr int decrement(int &x) { + return --x; } + struct DMIConstComposite { + int a; + int b = decrement(a); + }; + constexpr DMIConstComposite c{1}; + static_assert(c.a == 0); } #endif diff --git a/clang/test/AST/ast-dump-default-init.cpp b/clang/test/AST/ast-dump-default-init.cpp index 2c2d670486627..9f972bb3cc921 100644 --- a/clang/test/AST/ast-dump-default-init.cpp +++ b/clang/test/AST/ast-dump-default-init.cpp @@ -13,13 +13,12 @@ void test() { B b{}; } // CHECK: -CXXDefaultInitExpr 0x{{[^ ]*}} <{{.*}}> 'const A' lvalue has rewritten init -// CHECK-NEXT: `-ExprWithCleanups 0x{{[^ ]*}} <{{.*}}> 'const A' lvalue -// CHECK-NEXT: `-MaterializeTemporaryExpr 0x{{[^ ]*}} <{{.*}}> 'const A' lvalue extended by Var 0x{{[^ ]*}} 'b' 'B' -// CHECK-NEXT: `-ImplicitCastExpr 0x{{[^ ]*}} <{{.*}}> 'const A' <NoOp> -// CHECK-NEXT: `-CXXFunctionalCastExpr 0x{{[^ ]*}} <{{.*}}> 'A' functional cast to A <NoOp> -// CHECK-NEXT: `-InitListExpr 0x{{[^ ]*}} <{{.*}}> 'A' -// CHECK-NEXT: `-InitListExpr 0x{{[^ ]*}} <{{.*}}> 'int[1]' -// CHECK-NEXT: `-IntegerLiteral 0x{{[^ ]*}} <{{.*}}> 'int' 0 +// CHECK-NEXT: `-MaterializeTemporaryExpr 0x{{[^ ]*}} <{{.*}}> 'const A' lvalue extended by Var 0x{{[^ ]*}} 'b' 'B' +// CHECK-NEXT: `-ImplicitCastExpr 0x{{[^ ]*}} <{{.*}}> 'const A' <NoOp> +// CHECK-NEXT: `-CXXFunctionalCastExpr 0x{{[^ ]*}} <{{.*}}> 'A' functional cast to A <NoOp> +// CHECK-NEXT: `-InitListExpr 0x{{[^ ]*}} <{{.*}}> 'A' +// CHECK-NEXT: `-InitListExpr 0x{{[^ ]*}} <{{.*}}> 'int[1]' +// CHECK-NEXT: `-IntegerLiteral 0x{{[^ ]*}} <{{.*}}> 'int' 0 // JSON: "kind": "CXXDefaultInitExpr", // JSON: "type": { @@ -30,73 +29,65 @@ void test() { // JSON-NEXT: "inner": [ // JSON-NEXT: { // JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "ExprWithCleanups", +// JSON-NEXT: "kind": "MaterializeTemporaryExpr", // JSON: "type": { // JSON-NEXT: "qualType": "const A" // JSON-NEXT: }, // JSON-NEXT: "valueCategory": "lvalue", +// JSON-NEXT: "extendingDecl": { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "VarDecl", +// JSON-NEXT: "name": "b", +// JSON-NEXT: "type": { +// JSON-NEXT: "qualType": "B" +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "storageDuration": "automatic", +// JSON-NEXT: "boundToLValueRef": true, // JSON-NEXT: "inner": [ // JSON-NEXT: { // JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "MaterializeTemporaryExpr", +// JSON-NEXT: "kind": "ImplicitCastExpr", // JSON: "type": { // JSON-NEXT: "qualType": "const A" // JSON-NEXT: }, -// JSON-NEXT: "valueCategory": "lvalue", -// JSON-NEXT: "extendingDecl": { -// JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "VarDecl", -// JSON-NEXT: "name": "b", -// JSON-NEXT: "type": { -// JSON-NEXT: "qualType": "B" -// JSON-NEXT: } -// JSON-NEXT: }, -// JSON-NEXT: "storageDuration": "automatic", -// JSON-NEXT: "boundToLValueRef": true, +// JSON-NEXT: "valueCategory": "prvalue", +// JSON-NEXT: "castKind": "NoOp", // JSON-NEXT: "inner": [ // JSON-NEXT: { // JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "ImplicitCastExpr", +// JSON-NEXT: "kind": "CXXFunctionalCastExpr", // JSON: "type": { -// JSON-NEXT: "qualType": "const A" +// JSON-NEXT: "qualType": "A" // JSON-NEXT: }, // JSON-NEXT: "valueCategory": "prvalue", // JSON-NEXT: "castKind": "NoOp", // JSON-NEXT: "inner": [ // JSON-NEXT: { // JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "CXXFunctionalCastExpr", +// JSON-NEXT: "kind": "InitListExpr", // JSON: "type": { // JSON-NEXT: "qualType": "A" // JSON-NEXT: }, // JSON-NEXT: "valueCategory": "prvalue", -// JSON-NEXT: "castKind": "NoOp", // JSON-NEXT: "inner": [ // JSON-NEXT: { // JSON-NEXT: "id": "0x{{.*}}", // JSON-NEXT: "kind": "InitListExpr", // JSON: "type": { -// JSON-NEXT: "qualType": "A" +// JSON-NEXT: "qualType": "int[1]" // JSON-NEXT: }, // JSON-NEXT: "valueCategory": "prvalue", // JSON-NEXT: "inner": [ // JSON-NEXT: { // JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "InitListExpr", +// JSON-NEXT: "kind": "IntegerLiteral", // JSON: "type": { -// JSON-NEXT: "qualType": "int[1]" +// JSON-NEXT: "qualType": "int" // JSON-NEXT: }, // JSON-NEXT: "valueCategory": "prvalue", -// JSON-NEXT: "inner": [ -// JSON-NEXT: { -// JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "IntegerLiteral", -// JSON: "type": { -// JSON-NEXT: "qualType": "int" -// JSON-NEXT: }, -// JSON-NEXT: "valueCategory": "prvalue", -// JSON-NEXT: "value": "0" -// JSON-NEXT: } -// JSON-NEXT: ] +// JSON-NEXT: "value": "0" // JSON-NEXT: } // JSON-NEXT: ] +// JSON-NEXT: } +// JSON-NEXT: ] diff --git a/clang/test/AST/ast-dump-recovery.cpp b/clang/test/AST/ast-dump-recovery.cpp index 3c1811b2c8d02..790b7bb2025fd 100644 --- a/clang/test/AST/ast-dump-recovery.cpp +++ b/clang/test/AST/ast-dump-recovery.cpp @@ -294,7 +294,7 @@ union U { // CHECK-NEXT: `-DeclStmt {{.*}} // CHECK-NEXT: `-VarDecl {{.*}} g 'U' listinit // CHECK-NEXT: `-InitListExpr {{.*}} 'U' contains-errors field Field {{.*}} 'f' 'int' -// CHECK-NEXT: `-CXXDefaultInitExpr {{.*}} 'int' contains-errors has rewritten init +// CHECK-NEXT: `-CXXDefaultInitExpr {{.*}} 'int' contains-errors // CHECK-NEXT: `-RecoveryExpr {{.*}} 'int' contains-errors // DISABLED-NOT: -RecoveryExpr {{.*}} contains-errors void foo() { diff --git a/clang/test/Analysis/lifetime-extended-regions.cpp b/clang/test/Analysis/lifetime-extended-regions.cpp index 4458ad294af7c..02a1210d9af92 100644 --- a/clang/test/Analysis/lifetime-extended-regions.cpp +++ b/clang/test/Analysis/lifetime-extended-regions.cpp @@ -121,11 +121,10 @@ void aggregateWithReferences() { clang_analyzer_dump(viaReference.rx); // expected-warning-re {{&lifetime_extended_object{int, viaReference, S{{[0-9]+}}} }} clang_analyzer_dump(viaReference.ry); // expected-warning-re {{&lifetime_extended_object{Composite, viaReference, S{{[0-9]+}}} }} - // FIXME: clang currently support extending lifetime of object bound to reference members of aggregates, - // that are created from default member initializer. But CFG and ExprEngine need to be updated to address this change. - // The following expect warning: {{&lifetime_extended_object{Composite, defaultInitExtended, S{{[0-9]+}}} }} + // The lifetime of object bound to reference members of aggregates, + // that are created from default member initializer was extended. RefAggregate defaultInitExtended{i}; - clang_analyzer_dump(defaultInitExtended.ry); // expected-warning {{Unknown }} + clang_analyzer_dump(defaultInitExtended.ry); // expected-warning-re {{&lifetime_extended_object{Composite, defaultInitExtended, S{{[0-9]+}}} }} } void lambda() { diff --git a/clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp b/clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp new file mode 100644 index 0000000000000..40758d33de4b0 --- /dev/null +++ b/clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp @@ -0,0 +1,42 @@ +// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -emit-llvm -o - %s | FileCheck %s + +struct A { + int &x; + ~A() { x = 0; } +}; + +struct AA { + int &x; + ~AA() { x = -1; } +}; + +struct B { + int &x; + const A &a = A{x}; +}; + +struct BB { + int &x; + const AA &a = AA{x}; +}; + +// CHECK-LABEL: define{{.*}} i32 @_Z3onev() +int one() { + int x = 1; + B{x}; + // CHECK: call void @_ZN1AD{{[012]}}Ev + // CHECK-NEXT: load i32, ptr + return x; +} + +// The default initializers are part of the same full-expression, so their +// temporaries are destroyed in reverse construction order. +// CHECK-LABEL: define{{.*}} i32 @_Z3twov() +int two() { + int x = 1; + B{x}, BB{x}; + // CHECK: call void @_ZN2AAD{{[012]}}Ev + // CHECK-NEXT: call void @_ZN1AD{{[012]}}Ev + // CHECK-NEXT: load i32, ptr + return x; +} diff --git a/clang/test/SemaCXX/aggregate-default-member-initializers.cpp b/clang/test/SemaCXX/aggregate-default-member-initializers.cpp new file mode 100644 index 0000000000000..fb3db54cf1d91 --- /dev/null +++ b/clang/test/SemaCXX/aggregate-default-member-initializers.cpp @@ -0,0 +1,103 @@ +// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -verify %s +// RUN: %clang_cc1 -std=c++23 -Wno-unused-value -verify %s +// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -verify %s \ +// RUN: -fexperimental-new-constant-interpreter + +namespace lifetime { + +struct A { + int &x; + constexpr ~A() { x = 0; } +}; + +struct AA { + int &x; + constexpr ~AA() { x = -1; } +}; + +struct B { + int &x; + const A &a = A{x}; +}; + +struct BB { + int &x; + const AA &a = AA{x}; +}; + +constexpr int one() { + int x = 1; + B{x}; + return x; +} + +constexpr int two() { + int x = 1; + B{x}, BB{x}; + return x; +} + +constexpr int paren() { + int x = 1; + (B(x)); + return x; +} + +static_assert(one() == 0); +static_assert(two() == 0); +static_assert(paren() == 0); + +} // namespace lifetime + +namespace unevaluated { + +template <typename T> int noInstantiate() { + static_assert(false); + return 0; +} + +struct S { + int x = noInstantiate<int>(); +}; + +int size = sizeof(S{}); + +} // namespace unevaluated + +namespace immediate { + +struct Inner { + int a; + static consteval int decrement(int &x) { + return --x; + } + // FIXME: The aggregate result object does not exist yet when the immediate + // invocation is checked, so reading 'a' fails. This is long-standing and is + // independent of which full-expression the initializer belongs to. + int b = decrement(a); // expected-error {{call to consteval function 'immediate::Inner::decrement' is not a constant expression}} \ + // expected-note {{implicit use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function}} \ + // expected-note {{declared here}} +}; + +struct Outer { + const Inner &inner = Inner{1}; // expected-note {{in the default initializer of 'b'}} +}; + +constexpr int value = Outer{}.inner.a; +static_assert(value == 0); + +consteval unsigned currentLine(unsigned line = __builtin_LINE()) { + return line; +} + +struct SourceAndRuntime { + unsigned line = currentLine(); + int runtime; +}; + +void sourceAndRuntime(int n) { + // The runtime initializer does not make currentLine() non-constant. + SourceAndRuntime value{.runtime = n}; +} + +} // namespace immediate diff --git a/clang/test/SemaCXX/cxx2c-placeholder-vars.cpp b/clang/test/SemaCXX/cxx2c-placeholder-vars.cpp index 8e428c0ef0427..37824c16f4f05 100644 --- a/clang/test/SemaCXX/cxx2c-placeholder-vars.cpp +++ b/clang/test/SemaCXX/cxx2c-placeholder-vars.cpp @@ -274,16 +274,16 @@ void f() { // CHECK: ClassTemplateSpecializationDecl {{.*}} struct A definition // CHECK: CXXConstructorDecl {{.*}} implicit used constexpr A 'void () noexcept' // CHECK-NEXT: CXXCtorInitializer Field {{.*}} '_' 'int' -// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' has rewritten init +// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' // CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1 // CHECK-NEXT: CXXCtorInitializer Field {{.*}} '_' 'int' -// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' has rewritten init +// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' // CHECK-NEXT: IntegerLiteral {{.*}} 'int' 2 // CHECK-NEXT: CXXCtorInitializer Field {{.*}} 'a' 'int' -// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' has rewritten init +// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' // CHECK-NEXT: IntegerLiteral {{.*}} 'int' 3 // CHECK-NEXT: CXXCtorInitializer Field {{.*}} '_' 'int' -// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' has rewritten init +// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' // CHECK-NEXT: IntegerLiteral {{.*}} 'int' 4 // CHECK-NEXT: CompoundStmt {{.*}} diff --git a/clang/test/SemaCXX/warn-unreachable.cpp b/clang/test/SemaCXX/warn-unreachable.cpp index e6f5bc5ef8e12..79505a2427426 100644 --- a/clang/test/SemaCXX/warn-unreachable.cpp +++ b/clang/test/SemaCXX/warn-unreachable.cpp @@ -414,3 +414,107 @@ void tautological_compare(bool x, int y) { calledFun(); } + +namespace test_rebuilt_default_arg { +struct A { + explicit A(int = __builtin_LINE()); +}; + +int h(int a) { + return 3; + A(); // expected-warning {{will never be executed}} +} + +struct Temp { + Temp(); + ~Temp(); +}; + +struct B { + explicit B(const Temp &t = Temp()); +}; +int f(int a) { + return 3; + B(); // expected-warning {{will never be executed}} +} +} // namespace test_rebuilt_default_arg +namespace test_rebuilt_default_init { + +struct A { + A(); + ~A(); +}; + +struct B { + const A &t = A(); +}; +int f(int a) { + return 3; + A{}; // expected-warning {{will never be executed}} +} +} // namespace test_rebuilt_default_init + +// This issue reported by the comments in https://github.com/llvm/llvm-project/pull/117437. +// All block-level expressions should have already been IgnoreParens()ed. +namespace gh117437_ignore_parens_in_default_arg { + class Location { + public: + static Location Current(int = __builtin_LINE()); + }; + class DOMMatrix; + class BasicMember { + public: + BasicMember(DOMMatrix *); + }; + template <typename> using Member = BasicMember; + class ExceptionState { + public: + ExceptionState &ReturnThis(); + ExceptionState(Location); + }; + class NonThrowableExceptionState : public ExceptionState { + public: + NonThrowableExceptionState(Location location = Location::Current()) + : ExceptionState(location) {} + }; + class DOMMatrix { + public: + static DOMMatrix * + Create(int *, ExceptionState & = (NonThrowableExceptionState().ReturnThis())); + }; + class CSSMatrixComponent { + int CSSMatrixComponent_matrix; + CSSMatrixComponent() + : matrix_(DOMMatrix::Create(&CSSMatrixComponent_matrix)) {} + Member<DOMMatrix> matrix_; + }; +} // namespace gh117437_ignore_parens_in_default_arg + +class Location { + public: + static Location CurrentWithoutFunctionName( + const char* file_name = __builtin_FILE(), + int line_number = __builtin_LINE()); + }; + + class NotReachedNoreturnError { + public: + explicit NotReachedNoreturnError( + const Location& location = + Location::CurrentWithoutFunctionName()); + + [[noreturn]] [[clang::nomerge]] [[clang::noinline]] [[clang::not_tail_called]] + ~NotReachedNoreturnError(); + }; + + #define NOTREACHED() NotReachedNoreturnError() + + int f() { + NOTREACHED(); + return 4; // expected-warning {{will never be executed}} + } + + int g() { + return 4; + NOTREACHED(); // Ok, no diagnostic. + } diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp index f34568a072c32..76cab4618c5e6 100644 --- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp @@ -2950,6 +2950,24 @@ TEST_P(UncheckedOptionalAccessTest, ConstructorOtherStructField) { )cc"); } +TEST_P(UncheckedOptionalAccessTest, + AggregateDefaultInitializerReferencesPriorField) { + ExpectDiagnosticsFor(R"cc( + #include "unchecked_optional_access_test.h" + struct NonTrivDtor { + NonTrivDtor(int x); + ~NonTrivDtor() {} + }; + struct Other { + $ns::$optional<int> x = $ns::nullopt; + NonTrivDtor y = x.has_value() ? NonTrivDtor(*x) : NonTrivDtor(-1); + }; + struct target { + target() { Other{}; } + }; + )cc"); +} + TEST_P(UncheckedOptionalAccessTest, AssertTrueGtestMacro) { ExpectDiagnosticsFor(R"cc( #include "unchecked_optional_access_test.h" >From 8c9405378f2e75a60948af441e5113d719dd6a71 Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> Date: Thu, 27 Aug 2026 13:57:57 -0700 Subject: [PATCH 2/4] Merge duplicated code Signed-off-by: yronglin <[email protected]> --- clang/lib/AST/ByteCode/Compiler.cpp | 50 ++++++++++++++--------------- clang/lib/AST/ByteCode/Compiler.h | 6 ++++ 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 6b02a57a0deda..cf4945a3f2ff6 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -3493,17 +3493,8 @@ bool Compiler<Emitter>::VisitExprWithCleanups(const ExprWithCleanups *E) { LocalScope<Emitter> ES(this, ScopeKind::FullExpression); const Expr *SubExpr = E->getSubExpr(); - if (DiscardResult && !SubExpr->isGLValue() && - !canClassify(SubExpr->getType()) && containsDefaultInitExpr(SubExpr)) { - UnsignedOrNone LocalIndex = - allocateLocal(SubExpr, QualType(), ScopeKind::FullExpression); - if (!LocalIndex) - return false; - InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex)); - if (!this->emitGetPtrLocal(*LocalIndex, E)) - return false; - return this->visitInitializerPop(SubExpr) && ES.destroyLocals(E); - } + if (DiscardResult && this->discardNeedsResultObject(SubExpr)) + return this->discardIntoResultObject(SubExpr) && ES.destroyLocals(E); return this->delegate(SubExpr) && ES.destroyLocals(E); } @@ -4925,22 +4916,29 @@ bool Compiler<Emitter>::VisitStmtExpr(const StmtExpr *E) { return BS.destroyLocals(); } +template <class Emitter> +bool Compiler<Emitter>::discardNeedsResultObject(const Expr *E) const { + return !E->isGLValue() && !canClassify(E->getType()) && + containsDefaultInitExpr(E); +} + +template <class Emitter> +bool Compiler<Emitter>::discardIntoResultObject(const Expr *E) { + UnsignedOrNone LocalIndex = + allocateLocal(E, QualType(), ScopeKind::FullExpression); + if (!LocalIndex) + return false; + InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex)); + if (!this->emitGetPtrLocal(*LocalIndex, E)) + return false; + return this->visitInitializerPop(E); +} + template <class Emitter> bool Compiler<Emitter>::discard(const Expr *E) { - // A discarded composite prvalue still needs a result object when a default - // member initializer refers to previously initialized subobjects. Let an - // ExprWithCleanups establish its full-expression scope before allocating - // that object. - if (!isa<ExprWithCleanups>(E) && !E->isGLValue() && - !canClassify(E->getType()) && containsDefaultInitExpr(E)) { - UnsignedOrNone LocalIndex = - allocateLocal(E, QualType(), ScopeKind::FullExpression); - if (!LocalIndex) - return false; - InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex)); - if (!this->emitGetPtrLocal(*LocalIndex, E)) - return false; - return this->visitInitializerPop(E); - } + // Let an ExprWithCleanups establish its full-expression scope first; it + // allocates the result object itself. + if (!isa<ExprWithCleanups>(E) && this->discardNeedsResultObject(E)) + return this->discardIntoResultObject(E); OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/true, /*NewInitializing=*/false, /*ToLValue=*/false); diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index f34809cd0f14c..6e22f57749e43 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -321,6 +321,12 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, bool visitAsLValue(const Expr *E); /// Evaluates an expression for side effects and discards the result. bool discard(const Expr *E); + /// Whether discarding \p E still requires a result object: a composite + /// prvalue whose default member initializer may refer to previously + /// initialized subobjects, so that `this` has something to denote. + bool discardNeedsResultObject(const Expr *E) const; + /// Allocate that result object and initialize \p E into it. + bool discardIntoResultObject(const Expr *E); /// Just pass evaluation on to \p E. This leaves all the parsing flags /// intact. bool delegate(const Expr *E); >From 4df9f9fa29270275eb6c3258ca24cbedc18e6a68 Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> Date: Thu, 27 Aug 2026 20:34:05 -0700 Subject: [PATCH 3/4] [clang] Don't need rebuild ctor DIE Signed-off-by: yronglin <[email protected]> --- clang/lib/Sema/SemaExpr.cpp | 20 +++++++++++++++++-- .../aggregate-default-member-initializers.cpp | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 31b308ad4b52b..34a0bff7c5289 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -5979,7 +5979,6 @@ ExprResult Sema::BuildCXXCtorDefaultInitExpr(SourceLocation Loc, assert(Field->hasInClassInitializer()); bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer(); - bool NeedRebuild = needsRebuildOfDefaultArgOrInit(); // C++11 [class.base.init]p7: // The initialization of each base and member constitutes a @@ -5994,10 +5993,18 @@ ExprResult Sema::BuildCXXCtorDefaultInitExpr(SourceLocation Loc, if (!InitContext) InitContext.emplace(Loc, Field, CurContext); + // [class.temporary]/p7: + // If such a temporary object would otherwise be destroyed at the end of the + // for-range-initializer full-expression, the object persists for the lifetime + // of the reference initialized by the for-range-initializer. + // + // A default member initializer used by a constructor is a separate + // full-expression, we don't need extend temporaries lifetime in this + // situation, the NeedRebuild will always false. ExprResult Init = BuildCXXDefaultInitInternal( Loc, Field, InitializedEntity::InitializeMemberFromDefaultMemberInitializer(Field), - NestedDefaultChecking, NeedRebuild); + NestedDefaultChecking, /*NeedRebuild=*/false); if (Init.isInvalid()) return ExprError(); @@ -6033,6 +6040,15 @@ Sema::BuildCXXAggregateDefaultInitExpr(SourceLocation Loc, FieldDecl *Field, if (!InitContext) InitContext.emplace(Loc, Field, CurContext); + // [class.temporary]/p7: + // If such a temporary object would otherwise be destroyed at the end of the + // for-range-initializer full-expression, the object persists for the lifetime + // of the reference initialized by the for-range-initializer. + // + // A default member initializer used by an aggregate initialization belongs to + // the full-expression containing the aggregate initialization. we need extend + // temporaries lifetime in this situation, the NeedRebuild will always true. + // CWG1815: always rebuild, never share the AST built when the field was // declared. Only a copy rebuilt here has its MaterializeTemporaryExprs // collected in this context, which is what lets the aggregate initialization diff --git a/clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp b/clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp index 40758d33de4b0..b0a8d63450aff 100644 --- a/clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp +++ b/clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -Wno-unused-value -emit-llvm -o - %s | FileCheck %s struct A { int &x; >From ee3bb55345c0b7b348d9651bd73722d49d3237f2 Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> Date: Mon, 31 Aug 2026 01:43:03 -0700 Subject: [PATCH 4/4] Drop static analyzer changes Signed-off-by: yronglin <[email protected]> --- clang/include/clang/AST/ParentMap.h | 31 ------ clang/lib/AST/ByteCode/Compiler.cpp | 62 +---------- clang/lib/AST/ByteCode/Compiler.h | 6 - clang/lib/AST/ParentMap.cpp | 17 --- clang/lib/Analysis/CFG.cpp | 55 ++------- clang/lib/Analysis/ReachableCode.cpp | 67 ++++++++--- clang/lib/Sema/TreeTransform.h | 23 +++- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 57 ++++------ clang/test/AST/ByteCode/records.cpp | 23 +--- .../Analysis/lifetime-extended-regions.cpp | 12 +- .../aggregate-default-member-initializers.cpp | 31 ++++-- clang/test/SemaCXX/warn-unreachable.cpp | 104 ------------------ clang/unittests/AST/ASTExprTest.cpp | 49 +++++++++ .../UncheckedOptionalAccessModelTest.cpp | 18 --- 14 files changed, 179 insertions(+), 376 deletions(-) diff --git a/clang/include/clang/AST/ParentMap.h b/clang/include/clang/AST/ParentMap.h index 5853a0be0a483..86e2f048a3445 100644 --- a/clang/include/clang/AST/ParentMap.h +++ b/clang/include/clang/AST/ParentMap.h @@ -13,8 +13,6 @@ #ifndef LLVM_CLANG_AST_PARENTMAP_H #define LLVM_CLANG_AST_PARENTMAP_H -#include "llvm/Support/Casting.h" - namespace clang { class Stmt; class Expr; @@ -41,25 +39,6 @@ class ParentMap { Stmt *getParentIgnoreParenImpCasts(Stmt *) const; Stmt *getOuterParenParent(Stmt *) const; - template <typename... Ts> Stmt *getOuterMostAncestor(Stmt *S) const { - Stmt *Res = nullptr; - while (S) { - if (llvm::isa<Ts...>(S)) - Res = S; - S = getParent(S); - } - return Res; - } - - template <typename... Ts> Stmt *getInnerMostAncestor(Stmt *S) const { - while (S) { - if (llvm::isa<Ts...>(S)) - return S; - S = getParent(S); - } - return nullptr; - } - const Stmt *getParent(const Stmt* S) const { return getParent(const_cast<Stmt*>(S)); } @@ -72,16 +51,6 @@ class ParentMap { return getParentIgnoreParenCasts(const_cast<Stmt*>(S)); } - template <typename... Ts> - const Stmt *getOuterMostAncestor(const Stmt *S) const { - return getOuterMostAncestor<Ts...>(const_cast<Stmt *>(S)); - } - - template <typename... Ts> - const Stmt *getInnerMostAncestor(const Stmt *S) const { - return getInnerMostAncestor<Ts...>(const_cast<Stmt *>(S)); - } - bool hasParent(const Stmt *S) const { return getParent(S) != nullptr; } bool isConsumedExpr(Expr *E) const; diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index cf4945a3f2ff6..c182639ea07f8 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -50,22 +50,6 @@ static bool isSideEffectFree(const Expr *E) { return false; } -static bool containsDefaultInitExpr(const Expr *E) { - class Finder final : public ConstDynamicRecursiveASTVisitor { - public: - Finder() { ShouldVisitImplicitCode = true; } - - bool VisitCXXDefaultInitExpr(const CXXDefaultInitExpr *) override { - Found = true; - return true; - } - - bool Found = false; - } F; - F.TraverseStmt(E); - return F.Found; -} - /// Scope chain managing the variable lifetimes. template <class Emitter> class VariableScope { public: @@ -281,9 +265,7 @@ template <class Emitter> class InitStackScope final { public: InitStackScope(Compiler<Emitter> *Ctx, bool Active) : Ctx(Ctx), OldValue(Ctx->InitStackActive), Active(Active) { - // An explicit initializer nested in a default member initializer still - // needs the surrounding default initializer's `this` reconstruction. - Ctx->InitStackActive = OldValue || Active; + Ctx->InitStackActive = Active; if (Active) Ctx->InitStack.push_back(InitLink::DIE()); } @@ -3493,9 +3475,6 @@ bool Compiler<Emitter>::VisitExprWithCleanups(const ExprWithCleanups *E) { LocalScope<Emitter> ES(this, ScopeKind::FullExpression); const Expr *SubExpr = E->getSubExpr(); - if (DiscardResult && this->discardNeedsResultObject(SubExpr)) - return this->discardIntoResultObject(SubExpr) && ES.destroyLocals(E); - return this->delegate(SubExpr) && ES.destroyLocals(E); } @@ -3562,12 +3541,8 @@ bool Compiler<Emitter>::VisitMaterializeTemporaryExpr( // Non-primitive values. if (!this->emitGetPtrGlobal(*GlobalIndex, E)) return false; - if (!this->emitStartInit(E)) - return false; if (!this->visitInitializer(Inner)) return false; - if (!this->emitEndInit(E)) - return false; if (IsStatic) { assert(TempDecl); return this->emitInitGlobalTempComp(TempDecl, E); @@ -3610,11 +3585,7 @@ bool Compiler<Emitter>::VisitMaterializeTemporaryExpr( if (!this->emitGetPtrLocal(*LocalIndex, E)) return false; - if (!this->emitStartInit(E)) - return false; - if (!this->visitInitializer(Inner)) - return false; - return this->emitEndInit(E); + return this->visitInitializer(Inner); } return false; } @@ -4916,30 +4887,7 @@ bool Compiler<Emitter>::VisitStmtExpr(const StmtExpr *E) { return BS.destroyLocals(); } -template <class Emitter> -bool Compiler<Emitter>::discardNeedsResultObject(const Expr *E) const { - return !E->isGLValue() && !canClassify(E->getType()) && - containsDefaultInitExpr(E); -} - -template <class Emitter> -bool Compiler<Emitter>::discardIntoResultObject(const Expr *E) { - UnsignedOrNone LocalIndex = - allocateLocal(E, QualType(), ScopeKind::FullExpression); - if (!LocalIndex) - return false; - InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex)); - if (!this->emitGetPtrLocal(*LocalIndex, E)) - return false; - return this->visitInitializerPop(E); -} - template <class Emitter> bool Compiler<Emitter>::discard(const Expr *E) { - // Let an ExprWithCleanups establish its full-expression scope first; it - // allocates the result object itself. - if (!isa<ExprWithCleanups>(E) && this->discardNeedsResultObject(E)) - return this->discardIntoResultObject(E); - OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/true, /*NewInitializing=*/false, /*ToLValue=*/false); return this->Visit(E); @@ -5503,14 +5451,8 @@ bool Compiler<Emitter>::visitExpr(const Expr *E, bool DestroyToplevelScope) { if (!this->emitGetPtrLocal(*LocalOffset, E)) return false; - // A const-qualified result object is writable while it is being - // initialized, just like an object evaluated through visitVarDecl(). - if (!this->emitStartInit(E)) - return false; if (!visitInitializer(E)) return false; - if (!this->emitEndInit(E)) - return false; // We are destroying the locals AFTER the Ret op. // The Ret op needs to copy the (alive) values, but the // destructors may still turn the entire expression invalid. diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index 6e22f57749e43..f34809cd0f14c 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -321,12 +321,6 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, bool visitAsLValue(const Expr *E); /// Evaluates an expression for side effects and discards the result. bool discard(const Expr *E); - /// Whether discarding \p E still requires a result object: a composite - /// prvalue whose default member initializer may refer to previously - /// initialized subobjects, so that `this` has something to denote. - bool discardNeedsResultObject(const Expr *E) const; - /// Allocate that result object and initialize \p E into it. - bool discardIntoResultObject(const Expr *E); /// Just pass evaluation on to \p E. This leaves all the parsing flags /// intact. bool delegate(const Expr *E); diff --git a/clang/lib/AST/ParentMap.cpp b/clang/lib/AST/ParentMap.cpp index 580613b2618fb..e62e71bf5a514 100644 --- a/clang/lib/AST/ParentMap.cpp +++ b/clang/lib/AST/ParentMap.cpp @@ -13,7 +13,6 @@ #include "clang/AST/ParentMap.h" #include "clang/AST/Decl.h" #include "clang/AST/Expr.h" -#include "clang/AST/ExprCXX.h" #include "clang/AST/StmtObjC.h" #include "llvm/ADT/DenseMap.h" @@ -104,22 +103,6 @@ static void BuildParentMap(MapTy& M, Stmt* S, BuildParentMap(M, SubStmt, OVMode); } break; - case Stmt::CXXDefaultArgExprClass: - if (auto *Arg = dyn_cast<CXXDefaultArgExpr>(S)) { - if (Arg->hasRewrittenInit()) { - M[Arg->getExpr()] = S; - BuildParentMap(M, Arg->getExpr(), OVMode); - } - } - break; - case Stmt::CXXDefaultInitExprClass: - if (auto *Init = dyn_cast<CXXDefaultInitExpr>(S)) { - if (Init->hasRewrittenInit()) { - M[Init->getExpr()] = S; - BuildParentMap(M, Init->getExpr(), OVMode); - } - } - break; default: for (Stmt *SubStmt : S->children()) { if (SubStmt) { diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index f92e6f3dcbf47..5263114ebca28 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -581,10 +581,6 @@ class CFGBuilder { private: // Visitors to walk an AST and construct the CFG. - CFGBlock *VisitCXXDefaultArgExpr(CXXDefaultArgExpr *Default, - AddStmtChoice asc); - CFGBlock *VisitCXXDefaultInitExpr(CXXDefaultInitExpr *Default, - AddStmtChoice asc); CFGBlock *VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc); CFGBlock *VisitAddrLabelExpr(AddrLabelExpr *A, AddStmtChoice asc); CFGBlock *VisitAttributedStmt(AttributedStmt *A, AddStmtChoice asc); @@ -2409,10 +2405,16 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc, asc, ExternallyDestructed); case Stmt::CXXDefaultArgExprClass: - return VisitCXXDefaultArgExpr(cast<CXXDefaultArgExpr>(S), asc); - case Stmt::CXXDefaultInitExprClass: - return VisitCXXDefaultInitExpr(cast<CXXDefaultInitExpr>(S), asc); + // FIXME: The expression inside a CXXDefaultArgExpr is owned by the + // called function's declaration, not by the caller. If we simply add + // this expression to the CFG, we could end up with the same Expr + // appearing multiple times (PR13385). + // + // It's likewise possible for multiple CXXDefaultInitExprs for the same + // expression to be used in the same function (through aggregate + // initialization). + return VisitStmt(S, asc); case Stmt::CXXBindTemporaryExprClass: return VisitCXXBindTemporaryExpr(cast<CXXBindTemporaryExpr>(S), asc); @@ -2595,45 +2597,6 @@ CFGBlock *CFGBuilder::VisitCallExprChildren(CallExpr *C) { return VisitChildren(C); } -CFGBlock *CFGBuilder::VisitCXXDefaultArgExpr(CXXDefaultArgExpr *Arg, - AddStmtChoice asc) { - if (Arg->hasRewrittenInit()) { - if (asc.alwaysAdd(*this, Arg)) { - autoCreateBlock(); - appendStmt(Block, Arg); - } - return VisitStmt(Arg->getExpr()->IgnoreParens(), asc); - } - - // We can't add the default argument if it's not rewritten because the - // expression inside a CXXDefaultArgExpr is owned by the called function's - // declaration, not by the caller. We could end up with the same expression - // appearing multiple times. - return VisitStmt(Arg, asc); -} - -CFGBlock *CFGBuilder::VisitCXXDefaultInitExpr(CXXDefaultInitExpr *Init, - AddStmtChoice asc) { - if (Init->hasRewrittenInit()) { - if (asc.alwaysAdd(*this, Init)) { - autoCreateBlock(); - appendStmt(Block, Init); - } - - // Unlike CXXDefaultArgExpr::getExpr, which strips off the top-level - // FullExpr and ConstantExpr, CXXDefaultInitExpr::getExpr does not do this, - // so the top level cannot be a ParenExpr. Use Visit rather than VisitStmt - // so that control flow inside the initializer (a conditional operator, for - // instance) is decomposed into blocks instead of being laid out linearly. - return Visit(Init->getExpr(), asc); - } - - // We can't add the default initializer if it's not rewritten because multiple - // CXXDefaultInitExprs can refer to the same subexpression in the same - // function (through aggregate initialization). - return VisitStmt(Init, asc); -} - CFGBlock *CFGBuilder::VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc) { if (asc.alwaysAdd(*this, ILE)) { autoCreateBlock(); diff --git a/clang/lib/Analysis/ReachableCode.cpp b/clang/lib/Analysis/ReachableCode.cpp index 7d17a0cb4cfd4..4a9ab5d9f0f73 100644 --- a/clang/lib/Analysis/ReachableCode.cpp +++ b/clang/lib/Analysis/ReachableCode.cpp @@ -25,7 +25,6 @@ #include "clang/Basic/SourceManager.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/BitVector.h" -#include <cstddef> #include <optional> using namespace clang; @@ -397,7 +396,6 @@ namespace { SmallVector<const CFGBlock *, 10> WorkList; Preprocessor &PP; ASTContext &C; - AnalysisDeclContext &AC; typedef SmallVector<std::pair<const CFGBlock *, const Stmt *>, 12> DeferredLocsTy; @@ -405,10 +403,10 @@ namespace { DeferredLocsTy DeferredLocs; public: - DeadCodeScan(llvm::BitVector &reachable, Preprocessor &PP, - AnalysisDeclContext &AC) - : Visited(reachable.size()), Reachable(reachable), PP(PP), - C(AC.getASTContext()), AC(AC) {} + DeadCodeScan(llvm::BitVector &reachable, Preprocessor &PP, ASTContext &C) + : Visited(reachable.size()), + Reachable(reachable), + PP(PP), C(C) {} void enqueue(const CFGBlock *block); unsigned scanBackwards(const CFGBlock *Start, @@ -455,8 +453,47 @@ bool DeadCodeScan::isDeadCodeRoot(const clang::CFGBlock *Block) { return isDeadRoot; } -static bool isValidDeadStmt(ParentMap &PM, const Stmt *S, - const clang::CFGBlock *) { +// Check if the given `DeadStmt` is a coroutine statement and is a substmt of +// the coroutine statement. `Block` is the CFGBlock containing the `DeadStmt`. +static bool isInCoroutineStmt(const Stmt *DeadStmt, const CFGBlock *Block) { + // The coroutine statement, co_return, co_await, or co_yield. + const Stmt *CoroStmt = nullptr; + // Find the first coroutine statement after the DeadStmt in the block. + bool AfterDeadStmt = false; + for (const CFGElement &Elem : *Block) + if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>()) { + const Stmt *S = CS->getStmt(); + if (S == DeadStmt) + AfterDeadStmt = true; + if (AfterDeadStmt && + // For simplicity, we only check simple coroutine statements. + (llvm::isa<CoreturnStmt>(S) || llvm::isa<CoroutineSuspendExpr>(S))) { + CoroStmt = S; + break; + } + } + if (!CoroStmt) + return false; + struct Checker : DynamicRecursiveASTVisitor { + const Stmt *DeadStmt; + bool CoroutineSubStmt = false; + Checker(const Stmt *S) : DeadStmt(S) { + // Statements captured in the CFG can be implicit. + ShouldVisitImplicitCode = true; + } + + bool VisitStmt(Stmt *S) override { + if (S == DeadStmt) + CoroutineSubStmt = true; + return true; + } + }; + Checker checker(DeadStmt); + checker.TraverseStmt(const_cast<Stmt *>(CoroStmt)); + return checker.CoroutineSubStmt; +} + +static bool isValidDeadStmt(const Stmt *S, const clang::CFGBlock *Block) { if (S->getBeginLoc().isInvalid()) return false; if (const BinaryOperator *BO = dyn_cast<BinaryOperator>(S)) @@ -464,27 +501,21 @@ static bool isValidDeadStmt(ParentMap &PM, const Stmt *S, // Coroutine statements are never considered dead statements, because removing // them may change the function semantic if it is the only coroutine statement // of the coroutine. - return !PM.getInnerMostAncestor<CoreturnStmt, CoroutineSuspendExpr>(S); + return !isInCoroutineStmt(S, Block); } const Stmt *DeadCodeScan::findDeadCode(const clang::CFGBlock *Block) { - auto &PM = AC.getParentMap(); - for (CFGBlock::const_iterator I = Block->begin(), E = Block->end(); I!=E; ++I) if (std::optional<CFGStmt> CS = I->getAs<CFGStmt>()) { const Stmt *S = CS->getStmt(); - auto *RewrittenParent = - PM.getOuterMostAncestor<CXXDefaultArgExpr, CXXDefaultInitExpr>(S); - if (RewrittenParent) - S = RewrittenParent; - if (isValidDeadStmt(AC.getParentMap(), S, Block)) + if (isValidDeadStmt(S, Block)) return S; } CFGTerminator T = Block->getTerminator(); if (T.isStmtBranch()) { const Stmt *S = T.getStmt(); - if (S && isValidDeadStmt(AC.getParentMap(), S, Block)) + if (S && isValidDeadStmt(S, Block)) return S; } @@ -730,7 +761,7 @@ void FindUnreachableCode(AnalysisDeclContext &AC, Preprocessor &PP, if (reachable[block->getBlockID()]) continue; - DeadCodeScan DS(reachable, PP, AC); + DeadCodeScan DS(reachable, PP, AC.getASTContext()); numReachable += DS.scanBackwards(block, CB); if (numReachable == cfg->getNumBlockIDs()) diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 83c125cebde5d..9c8d05676f470 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -3519,9 +3519,10 @@ class TreeTransform { /// By default, builds a new default field initialization expression, which /// does not require any semantic analysis. Subclasses may override this /// routine to provide different behavior. - ExprResult RebuildCXXDefaultInitExpr(SourceLocation Loc, - FieldDecl *Field) { - return getSema().BuildCXXCtorDefaultInitExpr(Loc, Field); + ExprResult RebuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field, + Expr *RewrittenInit) { + return CXXDefaultInitExpr::Create(getSema().Context, Loc, Field, + getSema().CurContext, RewrittenInit); } /// Build a new C++ zero-initialization expression. @@ -15119,11 +15120,23 @@ TreeTransform<Derived>::TransformCXXDefaultInitExpr(CXXDefaultInitExpr *E) { if (!Field) return ExprError(); + ExprResult InitRes; + if (E->hasRewrittenInit()) { + // The initializer can refer to `this` and to other members, so it has to + // be transformed in the scope of the field's class. + Sema::CXXThisScopeRAII ThisScope(SemaRef, Field->getParent(), Qualifiers()); + InitRes = getDerived().TransformExpr(E->getRewrittenExpr()); + if (InitRes.isInvalid()) + return ExprError(); + } + if (!getDerived().AlwaysRebuild() && Field == E->getField() && - E->getUsedContext() == SemaRef.CurContext) + E->getUsedContext() == SemaRef.CurContext && + InitRes.get() == E->getRewrittenExpr()) return E; - return getDerived().RebuildCXXDefaultInitExpr(E->getExprLoc(), Field); + return getDerived().RebuildCXXDefaultInitExpr(E->getExprLoc(), Field, + InitRes.get()); } template<typename Derived> diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index e2f30968e02ff..530fae9ee2dee 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1958,47 +1958,32 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, ExplodedNodeSet Tmp; - bool HasRebuiltInit = false; - const Expr *ArgE = nullptr; - if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S)) { + const Expr *ArgE; + if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S)) ArgE = DefE->getExpr(); - HasRebuiltInit = DefE->hasRewrittenInit(); - } else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S)) { + else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S)) ArgE = DefE->getExpr(); - HasRebuiltInit = DefE->hasRewrittenInit(); - } else + else llvm_unreachable("unknown constant wrapper kind"); - if (HasRebuiltInit) { - for (const auto N : PreVisit) { - const StackFrame *SF = N->getStackFrame(); - ProgramStateRef State = N->getState(); - State = State->BindExpr(cast<Expr>(S), SF, State->getSVal(ArgE, SF)); - Tmp.insert(Engine.makePostStmtNode(S, State, N)); - } - } else { - // If it's not rewritten, the contents of these expressions are not - // actually part of the current function, so we fall back to constant - // evaluation. - bool IsTemporary = false; - if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(ArgE)) { - ArgE = MTE->getSubExpr(); - IsTemporary = true; - } + bool IsTemporary = false; + if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(ArgE)) { + ArgE = MTE->getSubExpr(); + IsTemporary = true; + } - std::optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE); - if (!ConstantVal) - ConstantVal = UnknownVal(); - - for (const auto I : PreVisit) { - const StackFrame *SF = I->getStackFrame(); - ProgramStateRef State = I->getState(); - State = State->BindExpr(cast<Expr>(S), SF, *ConstantVal); - if (IsTemporary) - State = createTemporaryRegionIfNeeded(State, SF, cast<Expr>(S), - cast<Expr>(S)); - Tmp.insert(Engine.makePostStmtNode(S, State, I)); - } + std::optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE); + if (!ConstantVal) + ConstantVal = UnknownVal(); + + const StackFrame *SF = Pred->getStackFrame(); + for (const auto I : PreVisit) { + ProgramStateRef State = I->getState(); + State = State->BindExpr(cast<Expr>(S), SF, *ConstantVal); + if (IsTemporary) + State = createTemporaryRegionIfNeeded(State, SF, cast<Expr>(S), + cast<Expr>(S)); + Tmp.insert(Engine.makePostStmtNode(S, State, I)); } getCheckerManager().runCheckersForPostStmt(Dst, Tmp, S, *this); diff --git a/clang/test/AST/ByteCode/records.cpp b/clang/test/AST/ByteCode/records.cpp index f70c7f641120a..36b5cb62fe95f 100644 --- a/clang/test/AST/ByteCode/records.cpp +++ b/clang/test/AST/ByteCode/records.cpp @@ -1302,30 +1302,11 @@ namespace { }; constexpr int a() { int x = 1; - { - B b{x}; - } - return x; - } - static_assert(a() == 0); - - constexpr int discarded() { - int x = 1; + int f = B{x}.x; B{x}; // both-warning {{expression result unused}} - return x; - } - static_assert(discarded() == 0); - /// A const-qualified composite result is writable while under construction. - constexpr int decrement(int &x) { - return --x; + return 1; } - struct DMIConstComposite { - int a; - int b = decrement(a); - }; - constexpr DMIConstComposite c{1}; - static_assert(c.a == 0); } #endif diff --git a/clang/test/Analysis/lifetime-extended-regions.cpp b/clang/test/Analysis/lifetime-extended-regions.cpp index 02a1210d9af92..4e5e72eeb6df7 100644 --- a/clang/test/Analysis/lifetime-extended-regions.cpp +++ b/clang/test/Analysis/lifetime-extended-regions.cpp @@ -121,10 +121,16 @@ void aggregateWithReferences() { clang_analyzer_dump(viaReference.rx); // expected-warning-re {{&lifetime_extended_object{int, viaReference, S{{[0-9]+}}} }} clang_analyzer_dump(viaReference.ry); // expected-warning-re {{&lifetime_extended_object{Composite, viaReference, S{{[0-9]+}}} }} - // The lifetime of object bound to reference members of aggregates, - // that are created from default member initializer was extended. + // A temporary created by a default member initializer of an aggregate is now part of the full-expression containing the aggregate initialization, + // so its lifetime is extended along with the aggregate (CWG1815). + // + // FIXME: The analyzer does not model that extension yet and still reports a plain temp_object here; it should report + // &lifetime_extended_object{Composite, defaultInitExtended, S...}. Teaching it requires CFG and ExprEngine to handle + // the rebuilt default member initializer. + // + // Once https://github.com/llvm/llvm-project/pull/146281 landed, this issue will be fixed. RefAggregate defaultInitExtended{i}; - clang_analyzer_dump(defaultInitExtended.ry); // expected-warning-re {{&lifetime_extended_object{Composite, defaultInitExtended, S{{[0-9]+}}} }} + clang_analyzer_dump(defaultInitExtended.ry); // expected-warning-re {{&temp_object{Composite, S{{[0-9]+}}} }} } void lambda() { diff --git a/clang/test/SemaCXX/aggregate-default-member-initializers.cpp b/clang/test/SemaCXX/aggregate-default-member-initializers.cpp index fb3db54cf1d91..9a004ea79d84a 100644 --- a/clang/test/SemaCXX/aggregate-default-member-initializers.cpp +++ b/clang/test/SemaCXX/aggregate-default-member-initializers.cpp @@ -1,6 +1,6 @@ -// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -verify %s -// RUN: %clang_cc1 -std=c++23 -Wno-unused-value -verify %s -// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -verify %s \ +// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -verify=expected %s +// RUN: %clang_cc1 -std=c++23 -Wno-unused-value -verify=expected %s +// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -verify=expected,bytecode %s \ // RUN: -fexperimental-new-constant-interpreter namespace lifetime { @@ -17,7 +17,10 @@ struct AA { struct B { int &x; - const A &a = A{x}; + // FIXME: The new constant interpreter does not give a discarded composite + // prvalue a result object, so the default member initializer cannot read + // 'x'. This predates this change; the legacy interpreter gets it right. + const A &a = A{x}; // bytecode-note 3{{implicit use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function}} }; struct BB { @@ -43,9 +46,12 @@ constexpr int paren() { return x; } -static_assert(one() == 0); -static_assert(two() == 0); -static_assert(paren() == 0); +static_assert(one() == 0); // bytecode-error {{static assertion expression is not an integral constant expression}} \ + // bytecode-note {{in call to 'one()'}} +static_assert(two() == 0); // bytecode-error {{static assertion expression is not an integral constant expression}} \ + // bytecode-note {{in call to 'two()'}} +static_assert(paren() == 0); // bytecode-error {{static assertion expression is not an integral constant expression}} \ + // bytecode-note {{in call to 'paren()'}} } // namespace lifetime @@ -69,22 +75,25 @@ namespace immediate { struct Inner { int a; static consteval int decrement(int &x) { - return --x; + return --x; // bytecode-note {{modification of object of const-qualified type 'const int' is not allowed in a constant expression}} } // FIXME: The aggregate result object does not exist yet when the immediate // invocation is checked, so reading 'a' fails. This is long-standing and is // independent of which full-expression the initializer belongs to. int b = decrement(a); // expected-error {{call to consteval function 'immediate::Inner::decrement' is not a constant expression}} \ // expected-note {{implicit use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function}} \ - // expected-note {{declared here}} + // expected-note {{declared here}} \ + // bytecode-note {{in call to 'decrement(Inner{1}.a)'}} }; struct Outer { const Inner &inner = Inner{1}; // expected-note {{in the default initializer of 'b'}} }; -constexpr int value = Outer{}.inner.a; -static_assert(value == 0); +constexpr int value = Outer{}.inner.a; // bytecode-error {{constexpr variable 'value' must be initialized by a constant expression}} \ + // bytecode-note {{declared here}} +static_assert(value == 0); // bytecode-error {{static assertion expression is not an integral constant expression}} \ + // bytecode-note {{initializer of 'value' is not a constant expression}} consteval unsigned currentLine(unsigned line = __builtin_LINE()) { return line; diff --git a/clang/test/SemaCXX/warn-unreachable.cpp b/clang/test/SemaCXX/warn-unreachable.cpp index 79505a2427426..e6f5bc5ef8e12 100644 --- a/clang/test/SemaCXX/warn-unreachable.cpp +++ b/clang/test/SemaCXX/warn-unreachable.cpp @@ -414,107 +414,3 @@ void tautological_compare(bool x, int y) { calledFun(); } - -namespace test_rebuilt_default_arg { -struct A { - explicit A(int = __builtin_LINE()); -}; - -int h(int a) { - return 3; - A(); // expected-warning {{will never be executed}} -} - -struct Temp { - Temp(); - ~Temp(); -}; - -struct B { - explicit B(const Temp &t = Temp()); -}; -int f(int a) { - return 3; - B(); // expected-warning {{will never be executed}} -} -} // namespace test_rebuilt_default_arg -namespace test_rebuilt_default_init { - -struct A { - A(); - ~A(); -}; - -struct B { - const A &t = A(); -}; -int f(int a) { - return 3; - A{}; // expected-warning {{will never be executed}} -} -} // namespace test_rebuilt_default_init - -// This issue reported by the comments in https://github.com/llvm/llvm-project/pull/117437. -// All block-level expressions should have already been IgnoreParens()ed. -namespace gh117437_ignore_parens_in_default_arg { - class Location { - public: - static Location Current(int = __builtin_LINE()); - }; - class DOMMatrix; - class BasicMember { - public: - BasicMember(DOMMatrix *); - }; - template <typename> using Member = BasicMember; - class ExceptionState { - public: - ExceptionState &ReturnThis(); - ExceptionState(Location); - }; - class NonThrowableExceptionState : public ExceptionState { - public: - NonThrowableExceptionState(Location location = Location::Current()) - : ExceptionState(location) {} - }; - class DOMMatrix { - public: - static DOMMatrix * - Create(int *, ExceptionState & = (NonThrowableExceptionState().ReturnThis())); - }; - class CSSMatrixComponent { - int CSSMatrixComponent_matrix; - CSSMatrixComponent() - : matrix_(DOMMatrix::Create(&CSSMatrixComponent_matrix)) {} - Member<DOMMatrix> matrix_; - }; -} // namespace gh117437_ignore_parens_in_default_arg - -class Location { - public: - static Location CurrentWithoutFunctionName( - const char* file_name = __builtin_FILE(), - int line_number = __builtin_LINE()); - }; - - class NotReachedNoreturnError { - public: - explicit NotReachedNoreturnError( - const Location& location = - Location::CurrentWithoutFunctionName()); - - [[noreturn]] [[clang::nomerge]] [[clang::noinline]] [[clang::not_tail_called]] - ~NotReachedNoreturnError(); - }; - - #define NOTREACHED() NotReachedNoreturnError() - - int f() { - NOTREACHED(); - return 4; // expected-warning {{will never be executed}} - } - - int g() { - return 4; - NOTREACHED(); // Ok, no diagnostic. - } diff --git a/clang/unittests/AST/ASTExprTest.cpp b/clang/unittests/AST/ASTExprTest.cpp index 305e659e573d4..5c9e7928bb804 100644 --- a/clang/unittests/AST/ASTExprTest.cpp +++ b/clang/unittests/AST/ASTExprTest.cpp @@ -12,6 +12,7 @@ #include "ASTPrint.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/DynamicRecursiveASTVisitor.h" #include "clang/AST/Expr.h" #include "clang/AST/IgnoreExpr.h" #include "clang/AST/OpenACCClause.h" @@ -397,3 +398,51 @@ TEST(ASTExpr, IsKnownToHaveBooleanValue) { ExpectKnown("from_bitint1", false, true); ExpectKnown("from_bitint2", false, false); } + +TEST(ASTExpr, CXXDefaultInitExprHasRewrittenInit) { + auto AST = buildASTFromCodeWithArgs(R"cpp( + struct WithDtor { + int &r; + ~WithDtor(); + }; + + struct Agg { + int &r; + const WithDtor &d = WithDtor{r}; + }; + + struct Ctor { + int x = 1; + Ctor(); + }; + Ctor::Ctor() {} + + void aggregate(int &i) { Agg{i}; } + )cpp", + {"-std=c++20", "-Wno-unused-value"}); + + struct Visitor : DynamicRecursiveASTVisitor { + llvm::StringMap<const CXXDefaultInitExpr *> ByField; + Visitor() { ShouldVisitImplicitCode = true; } + bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *E) override { + ByField.try_emplace(E->getField()->getName(), E); + return true; + } + } V; + V.TraverseDecl(AST->getASTContext().getTranslationUnitDecl()); + + // Used by aggregate initialization: rebuilt, so it owns its initializer. + const CXXDefaultInitExpr *AggDIE = V.ByField.lookup("d"); + ASSERT_NE(AggDIE, nullptr); + EXPECT_TRUE(AggDIE->hasRewrittenInit()); + EXPECT_NE(AggDIE->getRewrittenExpr(), nullptr); + EXPECT_EQ(AggDIE->getExpr(), AggDIE->getRewrittenExpr()); + EXPECT_NE(AggDIE->getExpr(), AggDIE->getField()->getInClassInitializer()); + + // Used by a constructor with nothing to rebuild: shares the field's one, so + // getExpr() falls back to it. + const CXXDefaultInitExpr *CtorDIE = V.ByField.lookup("x"); + ASSERT_NE(CtorDIE, nullptr); + EXPECT_FALSE(CtorDIE->hasRewrittenInit()); + EXPECT_EQ(CtorDIE->getExpr(), CtorDIE->getField()->getInClassInitializer()); +} diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp index 76cab4618c5e6..f34568a072c32 100644 --- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp @@ -2950,24 +2950,6 @@ TEST_P(UncheckedOptionalAccessTest, ConstructorOtherStructField) { )cc"); } -TEST_P(UncheckedOptionalAccessTest, - AggregateDefaultInitializerReferencesPriorField) { - ExpectDiagnosticsFor(R"cc( - #include "unchecked_optional_access_test.h" - struct NonTrivDtor { - NonTrivDtor(int x); - ~NonTrivDtor() {} - }; - struct Other { - $ns::$optional<int> x = $ns::nullopt; - NonTrivDtor y = x.has_value() ? NonTrivDtor(*x) : NonTrivDtor(-1); - }; - struct target { - target() { Other{}; } - }; - )cc"); -} - TEST_P(UncheckedOptionalAccessTest, AssertTrueGtestMacro) { ExpectDiagnosticsFor(R"cc( #include "unchecked_optional_access_test.h" _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
