https://github.com/yronglin created https://github.com/llvm/llvm-project/pull/221299
None >From f8adc7acf60a72ff1238f76d96474d6c355cb4d6 Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> Date: Thu, 27 Aug 2026 06:48:32 -0700 Subject: [PATCH] [clang][AST] Copy default-member-init wherever it is used and add a tag to its initialization init-list node Signed-off-by: yronglin <[email protected]> --- clang/include/clang/AST/Expr.h | 24 ++ clang/include/clang/AST/ExprCXX.h | 8 + clang/include/clang/AST/JSONNodeDumper.h | 1 + clang/include/clang/AST/Stmt.h | 5 + clang/include/clang/AST/TextNodeDumper.h | 1 + clang/include/clang/Analysis/CFG.h | 103 +++++++- .../FlowSensitive/DataflowEnvironment.h | 66 ++--- clang/lib/AST/ByteCode/Compiler.cpp | 81 +++--- clang/lib/AST/ByteCode/Compiler.h | 9 + clang/lib/AST/Expr.cpp | 9 + clang/lib/AST/JSONNodeDumper.cpp | 10 + clang/lib/AST/TextNodeDumper.cpp | 12 + clang/lib/Analysis/CFG.cpp | 45 +++- .../lib/Analysis/FlowSensitive/AdornedCFG.cpp | 5 + .../FlowSensitive/DataflowEnvironment.cpp | 135 +--------- clang/lib/Analysis/FlowSensitive/Transfer.cpp | 8 +- .../TypeErasedDataflowAnalysis.cpp | 10 + clang/lib/Analysis/PathDiagnostic.cpp | 4 + clang/lib/Sema/SemaExpr.cpp | 53 +++- clang/lib/Sema/SemaInit.cpp | 7 + clang/lib/Serialization/ASTReaderStmt.cpp | 3 + clang/lib/Serialization/ASTWriterStmt.cpp | 2 + clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 5 + .../lib/StaticAnalyzer/Core/SymbolManager.cpp | 3 + .../AST/ast-dump-uses-default-member-init.cpp | 52 ++++ .../FlowSensitive/DataflowEnvironmentTest.cpp | 247 ------------------ .../Analysis/FlowSensitive/TransferTest.cpp | 122 +++++++++ 27 files changed, 559 insertions(+), 471 deletions(-) create mode 100644 clang/test/AST/ast-dump-uses-default-member-init.cpp diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index 535086a6c2aa3..2699a08d6ef18 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -479,6 +479,24 @@ class Expr : public ValueStmt { /// an aspect of the value-kind type system. bool refersToBitField() const { return getObjectKind() == OK_BitField; } + /// Whether this is a list-initialization (an \c InitListExpr or a + /// \c CXXParenListInitExpr) that initializes an object using a default + /// member initializer, that is, one of whose initializers is a + /// \c CXXDefaultInitExpr. + /// + /// Within such an initializer, \c this denotes the object that this + /// list-initialization initializes, not the instance pointer of the + /// enclosing member function: + /// + /// \code + /// struct S { int x; int y = this->x; }; + /// int foo() { return S{10}.y; } // `this` denotes the `S{10}` object + /// \endcode + /// + /// Sema sets this when it fills the list in, so that consumers do not each + /// have to re-derive which list-initializations establish such an object. + bool initializesObjectWithDefaultMemberInit() const; + /// If this expression refers to a bit-field, retrieve the /// declaration of that bit-field. /// @@ -5380,6 +5398,7 @@ class InitListExpr : public Expr { explicit InitListExpr(EmptyShell Empty) : Expr(InitListExprClass, Empty), AltForm(nullptr, true) { InitListExprBits.IsExplicit = false; + InitListExprBits.InitializesObjectWithDefaultMemberInit = false; } unsigned getNumInits() const { return InitExprs.size(); } @@ -5494,6 +5513,11 @@ class InitListExpr : public Expr { // locations). Implicit InitListExpr's are created by the semantic analyzer. bool isExplicit() const { return InitListExprBits.IsExplicit; } + /// See Expr::initializesObjectWithDefaultMemberInit(). + void setInitializesObjectWithDefaultMemberInit(bool V = true) { + InitListExprBits.InitializesObjectWithDefaultMemberInit = V; + } + /// Is this an initializer for an array of characters, initialized by a string /// literal or an @encode? bool isStringLiteralInit() const; diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index bb790b1100e7f..9e6956a9d703d 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -5193,9 +5193,12 @@ class CXXParenListInitExpr final friend class TrailingObjects; friend class ASTStmtReader; friend class ASTStmtWriter; + friend class Expr; unsigned NumExprs; unsigned NumUserSpecifiedExprs; + /// See Expr::initializesObjectWithDefaultMemberInit(). + bool InitializesObjectWithDefaultMemberInit = false; SourceLocation InitLoc, LParenLoc, RParenLoc; llvm::PointerUnion<Expr *, FieldDecl *> ArrayFillerOrUnionFieldInit; @@ -5229,6 +5232,11 @@ class CXXParenListInitExpr final void updateDependence() { setDependence(computeDependence(this)); } + /// See Expr::initializesObjectWithDefaultMemberInit(). + void setInitializesObjectWithDefaultMemberInit(bool V = true) { + InitializesObjectWithDefaultMemberInit = V; + } + MutableArrayRef<Expr *> getInitExprs() { return getTrailingObjects(NumExprs); } diff --git a/clang/include/clang/AST/JSONNodeDumper.h b/clang/include/clang/AST/JSONNodeDumper.h index 679ce4e4815ae..0205362195111 100644 --- a/clang/include/clang/AST/JSONNodeDumper.h +++ b/clang/include/clang/AST/JSONNodeDumper.h @@ -309,6 +309,7 @@ class JSONNodeDumper void VisitCXXTypeidExpr(const CXXTypeidExpr *CTE); void VisitConstantExpr(const ConstantExpr *CE); void VisitInitListExpr(const InitListExpr *ILE); + void VisitCXXParenListInitExpr(const CXXParenListInitExpr *PLIE); void VisitGenericSelectionExpr(const GenericSelectionExpr *GSE); void VisitCXXUnresolvedConstructExpr(const CXXUnresolvedConstructExpr *UCE); void VisitCXXConstructExpr(const CXXConstructExpr *CE); diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 5d27ded64082d..93fb9838c9538 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -670,6 +670,7 @@ class alignas(void *) Stmt { class InitListExprBitfields { friend class ASTStmtReader; + friend class Expr; friend class InitListExpr; LLVM_PREFERRED_TYPE(ExprBitfields) @@ -682,6 +683,10 @@ class alignas(void *) Stmt { // Whether this list is explicitly written in the source (with braces). LLVM_PREFERRED_TYPE(bool) unsigned IsExplicit : 1; + /// Whether this list initializes an object using a default member + /// initializer. See InitListExpr::initializesObjectWithDefaultMemberInit(). + LLVM_PREFERRED_TYPE(bool) + unsigned InitializesObjectWithDefaultMemberInit : 1; }; class ParenListExprBitfields { diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h index 7219a0d3f8e50..cb5e2c694fca1 100644 --- a/clang/include/clang/AST/TextNodeDumper.h +++ b/clang/include/clang/AST/TextNodeDumper.h @@ -287,6 +287,7 @@ class TextNodeDumper void VisitFloatingLiteral(const FloatingLiteral *Node); void VisitStringLiteral(const StringLiteral *Str); void VisitInitListExpr(const InitListExpr *ILE); + void VisitCXXParenListInitExpr(const CXXParenListInitExpr *PLIE); void VisitGenericSelectionExpr(const GenericSelectionExpr *E); void VisitUnaryOperator(const UnaryOperator *Node); void VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *Node); diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index 749ed3fc76452..0cf25bdaf8de3 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -51,6 +51,16 @@ class FieldDecl; class LangOptions; class VarDecl; +/// The pointers a `CFGElement` stores are AST nodes and `CFGElement`s of AST +/// nodes, all of which are allocated by `ASTContext`'s allocator with at least +/// 8-byte alignment. Three low bits are therefore available for the kind; the +/// alignment itself is checked by `llvm::PointerIntPair`. +struct CFGElementPtrTraits { + static void *getAsVoidPointer(const void *P) { return const_cast<void *>(P); } + static const void *getFromVoidPointer(void *P) { return P; } + static constexpr int NumLowBitsAvailable = 3; +}; + /// Represents a top-level expression in a basic block. class CFGElement { public: @@ -81,16 +91,22 @@ class CFGElement { DTOR_BEGIN = AutomaticObjectDtor, DTOR_END = TemporaryDtor, CleanupFunction, + // list-initialized object marker kind + ListInitObjectBegin, + ListInitObjectEnd, + LIST_INIT_OBJECT_BEGIN = ListInitObjectBegin, + LIST_INIT_OBJECT_END = ListInitObjectEnd, }; protected: - // The int bits are used to mark the kind. - llvm::PointerIntPair<const void *, 2> Data1; - llvm::PointerIntPair<const void *, 2> Data2; + // The int bits are used to mark the kind: three bits in each of the two + // pointers, so up to 64 kinds. + llvm::PointerIntPair<const void *, 3, unsigned, CFGElementPtrTraits> Data1; + llvm::PointerIntPair<const void *, 3, unsigned, CFGElementPtrTraits> Data2; CFGElement(Kind kind, const void *Ptr1, const void *Ptr2 = nullptr) - : Data1(Ptr1, ((unsigned)kind) & 0x3), - Data2(Ptr2, (((unsigned)kind) >> 2) & 0x3) { + : Data1(Ptr1, ((unsigned)kind) & 0x7), + Data2(Ptr2, (((unsigned)kind) >> 3) & 0x7) { assert(getKind() == kind); } @@ -121,7 +137,7 @@ class CFGElement { Kind getKind() const { unsigned x = Data2.getInt(); - x <<= 2; + x <<= 3; x |= Data1.getInt(); return (Kind) x; } @@ -409,6 +425,68 @@ class CFGScopeEnd : public CFGScopeMarker { } }; +/// Delimits the region of a list-initialization that establishes the object +/// which `this` denotes inside the default member initializers it uses. +/// +/// A `ListInitObjectBegin` precedes every element of the list-initialization, +/// including the default member initializers themselves, and the matching +/// `ListInitObjectEnd` follows them. This re-expresses, in the linearized CFG, +/// the nesting that the AST encodes by containment: consumers that walk the +/// CFG can carry the object forward from the marker instead of searching for +/// the enclosing list-initialization. +/// +/// Only emitted when `BuildOptions::AddCXXDefaultInitExprInAggregates` is set, +/// since otherwise the default member initializers are not in the CFG at all. +class CFGListInitObject : public CFGElement { +public: + /// The `InitListExpr` or `CXXParenListInitExpr` that establishes the object. + LLVM_ATTRIBUTE_RETURNS_NONNULL const Expr *getListInitExpr() const { + return static_cast<const Expr *>(Data1.getPointer()); + } + +protected: + CFGListInitObject() = default; + CFGListInitObject(Kind K, const Expr *E) : CFGElement(K, E) { + assert(isKind(*this)); + } + +private: + friend class CFGElement; + + static bool isKind(const CFGElement &E) { + return E.getKind() >= LIST_INIT_OBJECT_BEGIN && + E.getKind() <= LIST_INIT_OBJECT_END; + } +}; + +class CFGListInitObjectBegin : public CFGListInitObject { +public: + CFGListInitObjectBegin() {} + explicit CFGListInitObjectBegin(const Expr *E) + : CFGListInitObject(ListInitObjectBegin, E) {} + +private: + friend class CFGElement; + + static bool isKind(const CFGElement &E) { + return E.getKind() == ListInitObjectBegin; + } +}; + +class CFGListInitObjectEnd : public CFGListInitObject { +public: + CFGListInitObjectEnd() {} + explicit CFGListInitObjectEnd(const Expr *E) + : CFGListInitObject(ListInitObjectEnd, E) {} + +private: + friend class CFGElement; + + static bool isKind(const CFGElement &E) { + return E.getKind() == ListInitObjectEnd; + } +}; + /// Represents C++ object destructor implicitly generated by compiler on various /// occasions. class CFGImplicitDtor : public CFGElement { @@ -1206,6 +1284,14 @@ class CFGBlock { Elements.push_back(CFGScopeEnd(VD, S), C); } + void appendListInitObjectBegin(const Expr *E, BumpVectorContext &C) { + Elements.push_back(CFGListInitObjectBegin(E), C); + } + + void appendListInitObjectEnd(const Expr *E, BumpVectorContext &C) { + Elements.push_back(CFGListInitObjectEnd(E), C); + } + void appendBaseDtor(const CXXBaseSpecifier *BS, BumpVectorContext &C) { Elements.push_back(CFGBaseDtor(BS), C); } @@ -1301,6 +1387,11 @@ class CFG { bool AddCXXNewAllocator = false; bool AddCXXDefaultInitExprInCtors = false; bool AddCXXDefaultInitExprInAggregates = false; + /// Bracket each list-initialization that establishes an object for the + /// default member initializers it uses with CFGListInitObjectBegin/End. + /// Requires AddCXXDefaultInitExprInAggregates, since otherwise those + /// initializers are not in the CFG. + bool AddListInitObjectMarkers = false; bool AddRichCXXConstructors = false; bool MarkElidedCXXConstructors = false; bool AddVirtualBaseBranches = false; diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h index 47ac06137bb5f..10f63cefe019f 100644 --- a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h +++ b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h @@ -30,6 +30,7 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/MapVector.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" #include <cassert> @@ -366,26 +367,37 @@ class Environment { return ThisPointeeLoc; } - /// Returns the storage location assigned to the `this` pointee in the - /// environment given a specific `CXXThisExpr`. Returns null if the `this` - /// pointee has no assigned storage location in the environment. - /// Note that `this` can be used in a non-member context, e.g.: + /// Returns the object established by the innermost list-initialization + /// currently being evaluated, or null if there is none. + /// + /// Within a default member initializer, `this` denotes the object being + /// initialized. When that initializer is used by a list-initialization + /// rather than by a constructor, the object is not the `this` pointee of the + /// enclosing member function: /// /// \code - /// struct S { - /// int x; - /// int y = this->x; - /// }; - /// int foo() { - /// return S{10}.y; // will have a `this` for initializing `S::y`. - /// } + /// struct S { int x; int y = this->x; }; + /// int foo() { return S{10}.y; } // `this` denotes the `S{10}` object /// \endcode - RecordStorageLocation * - getThisPointeeStorageLocation(const CXXThisExpr &ThisExpr) const { - auto It = ThisExprOverrides->find(&ThisExpr); - if (It == ThisExprOverrides->end()) - return ThisPointeeLoc; - return It->second; + /// + /// The CFG brackets such a list-initialization with + /// `CFGListInitObjectBegin`/`CFGListInitObjectEnd`, which push and pop this + /// stack, so the object is available while its elements are evaluated. + RecordStorageLocation *getListInitObjectLocation() const { + return ListInitObjects.empty() ? nullptr : ListInitObjects.back(); + } + + /// Pushes the object established by a list-initialization. Called for + /// `CFGListInitObjectBegin`. + void pushListInitObject(RecordStorageLocation &Loc) { + ListInitObjects.push_back(&Loc); + } + + /// Pops the object established by a list-initialization. Called for + /// `CFGListInitObjectEnd`. + void popListInitObject() { + assert(!ListInitObjects.empty()); + ListInitObjects.pop_back(); } /// Sets the storage location assigned to the `this` pointee in the @@ -717,8 +729,6 @@ class Environment { private: using PrValueToResultObject = llvm::DenseMap<const Expr *, RecordStorageLocation *>; - using ThisExprOverridesMap = - llvm::DenseMap<const CXXThisExpr *, RecordStorageLocation *>; // The copy-constructor is for use in fork() only. Environment(const Environment &) = default; @@ -782,15 +792,6 @@ class Environment { RecordStorageLocation *ThisPointeeLoc, RecordStorageLocation *LocForRecordReturnVal); - static ThisExprOverridesMap - buildThisExprOverridesMap(const FunctionDecl *FuncDecl, - RecordStorageLocation *ThisPointeeLoc, - const PrValueToResultObject &ResultObjectMap); - - static ThisExprOverridesMap - buildThisExprOverridesMap(Stmt *S, RecordStorageLocation *ThisPointeeLoc, - const PrValueToResultObject &ResultObjectMap); - // `DACtx` is not null and not owned by this object. DataflowAnalysisContext *DACtx; @@ -833,15 +834,14 @@ class Environment { // constructed. RecordStorageLocation *LocForRecordReturnVal = nullptr; + // The objects established by the list-initializations currently being + // evaluated, innermost last. See getListInitObjectLocation(). + llvm::SmallVector<RecordStorageLocation *, 1> ListInitObjects; + // The storage location of the `this` pointee. Should only be null if the // analysis target is not a method. RecordStorageLocation *ThisPointeeLoc = nullptr; - // Maps from `CXXThisExpr`s to their storage locations, if it should be - // different from `ThisPointeeLoc` (for example, CXXThisExpr that are - // under a CXXDefaultInitExpr under an InitListExpr). - std::shared_ptr<ThisExprOverridesMap> ThisExprOverrides; - // Maps from declarations and glvalue expression to storage locations that are // assigned to them. Unlike the maps in `DataflowAnalysisContext`, these // include only storage locations that are in scope for a particular basic diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index c182639ea07f8..b2bb4db26f547 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -282,6 +282,23 @@ template <class Emitter> class InitStackScope final { bool Active; }; +/// Scope for the object established by a list-initialization that uses a +/// default member initializer. Mirrors +/// CodeGenFunction::FieldConstructionScope. +template <class Emitter> class ListInitObjectScope final { +public: + ListInitObjectScope(Compiler<Emitter> *Ctx, UnsignedOrNone Ptr) + : Ctx(Ctx), OldValue(Ctx->ListInitObjectPtr) { + Ctx->ListInitObjectPtr = Ptr; + } + + ~ListInitObjectScope() { Ctx->ListInitObjectPtr = OldValue; } + +private: + Compiler<Emitter> *Ctx; + UnsignedOrNone OldValue; +}; + /// Scope used to handle temporaries in toplevel variable declarations. template <class Emitter> class DeclScope final : public LocalScope<Emitter> { public: @@ -2298,6 +2315,23 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const Expr *> Inits, const Expr *ArrayFiller, const Expr *E) { InitLinkScope<Emitter> ILS(this, InitLink::InitList()); + // If this list initializes an object using a default member initializer, + // `this` within that initializer denotes the object being initialized here. + // Record a pointer to it so that CXXThisExpr can simply load it; the pointer + // is on the stack because visitInitializer() puts it there. + // Note: only enter the scope when this list establishes an object. A nested + // list that does not must leave the enclosing one in place, since a default + // member initializer may contain one. + std::optional<ListInitObjectScope<Emitter>> LIOS; + if (E->initializesObjectWithDefaultMemberInit() && Initializing) { + unsigned Offset = this->allocateLocalPrimitive(E, PT_Ptr, /*IsConst=*/true); + if (!this->emitDupPtr(E)) + return false; + if (!this->emitSetLocal(PT_Ptr, Offset, E)) + return false; + LIOS.emplace(this, Offset); + } + QualType QT = E->getType(); if (const auto *AT = QT->getAs<AtomicType>()) QT = AT->getValueType(); @@ -6445,57 +6479,30 @@ bool Compiler<Emitter>::VisitCXXThisExpr(const CXXThisExpr *E) { if (!InitStackActive || InitStack.empty()) return this->emitThis(E); - // If our init stack is, for example: - // 0 Stack: 3 (decl) - // 1 Stack: 6 (init list) - // 2 Stack: 1 (field) - // 3 Stack: 6 (init list) - // 4 Stack: 1 (field) - // - // We want to find the LAST element in it that's an init list, - // which is marked with the K_InitList marker. The index right - // before that points to an init list. We need to find the - // elements before the K_InitList element that point to a base - // (e.g. a decl or This), optionally followed by field, elem, etc. - // In the example above, we want to emit elements [0..2]. - unsigned StartIndex = 0; - unsigned EndIndex = 0; - // Find the init list. - for (StartIndex = InitStack.size() - 1; StartIndex > 0; --StartIndex) { - if (InitStack[StartIndex].Kind == InitLink::K_DIE) { - EndIndex = StartIndex; - --StartIndex; - break; - } - } + // We are inside a default member initializer (InitStackActive), so 'this' + // denotes the object under construction. If a list-initialization + // established one, it recorded a pointer to it; otherwise the path in + // InitStack leads to it. + if (ListInitObjectPtr) + return this->emitGetLocal(PT_Ptr, *ListInitObjectPtr, E); - // Walk backwards to find the base. + // Walk backwards to the base the path starts from, then replay it. + unsigned StartIndex = InitStack.size() - 1; for (; StartIndex > 0; --StartIndex) { - if (InitStack[StartIndex].Kind == InitLink::K_InitList) - continue; - if (InitStack[StartIndex].Kind != InitLink::K_Field && InitStack[StartIndex].Kind != InitLink::K_Elem && InitStack[StartIndex].Kind != InitLink::K_Base && + InitStack[StartIndex].Kind != InitLink::K_InitList && InitStack[StartIndex].Kind != InitLink::K_DIE) break; } - if (StartIndex == 0 && EndIndex == 0) - EndIndex = InitStack.size() - 1; - assert(InitStack[StartIndex].Kind == InitLink::K_Decl || InitStack[StartIndex].Kind == InitLink::K_This || InitStack[StartIndex].Kind == InitLink::K_Temp || InitStack[StartIndex].Kind == InitLink::K_RVO); - // NOTE: This could be StartIndex < EndIndex, but we're also abusing the - // InitStack mechanism in visitWithSubstitutions to have the This pointer - // _just_ be a local variable. - assert(StartIndex <= EndIndex); - - // Emit the instructions. - for (unsigned I = StartIndex; I != (EndIndex + 1); ++I) { + for (unsigned I = StartIndex, N = InitStack.size(); I != N; ++I) { if (InitStack[I].Kind == InitLink::K_InitList || InitStack[I].Kind == InitLink::K_DIE) continue; diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index f34809cd0f14c..0dca94f7e3bb3 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -42,6 +42,7 @@ template <class Emitter> class LabelScope; template <class Emitter> class SwitchScope; template <class Emitter> class StmtExprScope; template <class Emitter> class LocOverrideScope; +template <class Emitter> class ListInitObjectScope; template <class Emitter> class Compiler; struct InitLink { @@ -362,6 +363,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, friend class DeclScope<Emitter>; friend class InitLinkScope<Emitter>; friend class InitStackScope<Emitter>; + friend class ListInitObjectScope<Emitter>; friend class OptionScope<Emitter>; friend class ArrayIndexScope<Emitter>; friend class SourceLocScope<Emitter>; @@ -506,6 +508,13 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, llvm::SmallVector<InitLink> InitStack; bool InitStackActive = false; + /// Offset of a local holding a pointer to the object established by the + /// innermost enclosing list-initialization that uses a default member + /// initializer, if any. Within such an initializer, `this` denotes that + /// object rather than the instance pointer of the current frame. This + /// mirrors CodeGenFunction::CXXDefaultInitExprThis. + UnsignedOrNone ListInitObjectPtr = std::nullopt; + /// Type of the expression returned by the function. OptPrimType ReturnType; diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp index 6ce0a29aa3bd7..a1720596ea315 100644 --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -2443,6 +2443,7 @@ InitListExpr::InitListExpr(const ASTContext &C, SourceLocation lbraceloc, sawArrayRangeDesignator(false); InitExprs.insert(C, InitExprs.end(), initExprs.begin(), initExprs.end()); InitListExprBits.IsExplicit = isExplicit; + InitListExprBits.InitializesObjectWithDefaultMemberInit = false; setDependence(computeDependence(this)); } @@ -2595,6 +2596,14 @@ static bool IsDecompositionDeclRefExpr(const Expr *E) { return isa_and_nonnull<DecompositionDecl>(Ref->getDecl()); } +bool Expr::initializesObjectWithDefaultMemberInit() const { + if (const auto *ILE = dyn_cast<InitListExpr>(this)) + return ILE->InitListExprBits.InitializesObjectWithDefaultMemberInit; + if (const auto *PLIE = dyn_cast<CXXParenListInitExpr>(this)) + return PLIE->InitializesObjectWithDefaultMemberInit; + return false; +} + bool Expr::isReadIfDiscardedInCPlusPlus11() const { // In C++11, discarded-value expressions of a certain form are special, // according to [expr]p10: diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp index 4cea437db4150..fe34232509c9e 100644 --- a/clang/lib/AST/JSONNodeDumper.cpp +++ b/clang/lib/AST/JSONNodeDumper.cpp @@ -1567,6 +1567,16 @@ void JSONNodeDumper::VisitConstantExpr(const ConstantExpr *CE) { void JSONNodeDumper::VisitInitListExpr(const InitListExpr *ILE) { if (const FieldDecl *FD = ILE->getInitializedFieldInUnion()) JOS.attribute("field", createBareDeclRef(FD)); + attributeOnlyIfTrue("usesDefaultMemberInit", + ILE->initializesObjectWithDefaultMemberInit()); +} + +void JSONNodeDumper::VisitCXXParenListInitExpr( + const CXXParenListInitExpr *PLIE) { + if (const FieldDecl *FD = PLIE->getInitializedFieldInUnion()) + JOS.attribute("field", createBareDeclRef(FD)); + attributeOnlyIfTrue("usesDefaultMemberInit", + PLIE->initializesObjectWithDefaultMemberInit()); } void JSONNodeDumper::VisitGenericSelectionExpr( diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp index f10753fe675d8..0115c2d6441c8 100644 --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -1745,6 +1745,18 @@ void TextNodeDumper::VisitInitListExpr(const InitListExpr *ILE) { dumpBareDeclRef(Field); } OS << ' ' << (ILE->isExplicit() ? "explicit" : "implicit"); + if (ILE->initializesObjectWithDefaultMemberInit()) + OS << " uses default member init"; +} + +void TextNodeDumper::VisitCXXParenListInitExpr( + const CXXParenListInitExpr *PLIE) { + if (auto *Field = PLIE->getInitializedFieldInUnion()) { + OS << " field "; + dumpBareDeclRef(Field); + } + if (PLIE->initializesObjectWithDefaultMemberInit()) + OS << " uses default member init"; } void TextNodeDumper::VisitGenericSelectionExpr(const GenericSelectionExpr *E) { diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index 5263114ebca28..1f8b715ce7495 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -960,6 +960,16 @@ class CFGBuilder { B->appendScopeEnd(VD, S, cfg->getBumpVectorContext()); } + /// Whether `E` establishes an object that `this` denotes inside the default + /// member initializers it uses, and those initializers are part of the CFG. + bool marksListInitObject(const Expr *E) { + assert((!BuildOpts.AddListInitObjectMarkers || + BuildOpts.AddCXXDefaultInitExprInAggregates) && + "markers are useless without the initializers they bracket"); + return BuildOpts.AddListInitObjectMarkers && + E->initializesObjectWithDefaultMemberInit(); + } + /// Find a relational comparison with an expression evaluating to a /// boolean and a constant other than 0 and 1. /// e.g. if ((x < y) == 10) @@ -2612,9 +2622,28 @@ CFGBlock *CFGBuilder::VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc) { B = R; if (BuildOpts.AddCXXDefaultInitExprInAggregates) { if (auto *DIE = dyn_cast<CXXDefaultInitExpr>(Child)) - if (Stmt *Child = DIE->getExpr()) - if (CFGBlock *R = Visit(Child)) + if (Stmt *Body = DIE->getExpr()) { + // Bracket the default member initializer -- and only it -- with the + // object this list-initialization establishes: `this` denotes that + // object there, whereas in the explicitly written elements it still + // denotes the object of the enclosing member function. + // + // The CFG is built backwards, so the end marker goes in first and + // the begin marker last; in execution order they bracket the body. + bool MarksObject = marksListInitObject(ILE); + if (MarksObject) { + autoCreateBlock(); + Block->appendListInitObjectEnd(ILE, cfg->getBumpVectorContext()); + B = Block; + } + if (CFGBlock *R = Visit(Body)) B = R; + if (MarksObject) { + autoCreateBlock(); + Block->appendListInitObjectBegin(ILE, cfg->getBumpVectorContext()); + B = Block; + } + } } } return B; @@ -5524,6 +5553,8 @@ CFGImplicitDtor::getDestructorDecl(ASTContext &astContext) const { case CFGElement::ScopeEnd: case CFGElement::FullExprCleanup: case CFGElement::CleanupFunction: + case CFGElement::ListInitObjectBegin: + case CFGElement::ListInitObjectEnd: llvm_unreachable("getDestructorDecl should only be used with " "ImplicitDtors"); case CFGElement::AutomaticObjectDtor: { @@ -6127,6 +6158,16 @@ static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper, OS << ")"; break; + case CFGElement::Kind::ListInitObjectBegin: + case CFGElement::Kind::ListInitObjectEnd: + OS << (E.getKind() == CFGElement::Kind::ListInitObjectBegin + ? "CFGListInitObjectBegin(" + : "CFGListInitObjectEnd("); + E.castAs<CFGListInitObject>().getListInitExpr()->getType().print( + OS, PrintingPolicy(Helper.getLangOpts())); + OS << ")"; + break; + case CFGElement::Kind::NewAllocator: OS << "CFGNewAllocator("; if (const CXXNewExpr *AllocExpr = E.castAs<CFGNewAllocator>().getAllocatorExpr()) diff --git a/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp b/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp index 6f4c3e6531e9b..fcf572c30dab8 100644 --- a/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp +++ b/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp @@ -161,6 +161,11 @@ llvm::Expected<AdornedCFG> AdornedCFG::build(const Decl &D, Stmt &S, Options.AddTemporaryDtors = true; Options.AddInitializers = true; Options.AddCXXDefaultInitExprInCtors = true; + // Needed so that `this` inside a default member initializer used by a + // list-initialization is part of the CFG; the object it denotes is supplied + // by the surrounding CFGListInitObjectBegin/End markers. + Options.AddCXXDefaultInitExprInAggregates = true; + Options.AddListInitObjectMarkers = true; Options.AddLifetime = true; Options.AddParameterLifetimes = true; diff --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp index ab0b76037837f..7829ec3f0557d 100644 --- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp +++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp @@ -494,97 +494,6 @@ class ResultObjectVisitor : public AnalysisASTVisitor { DataflowAnalysisContext &DACtx; }; -/// A visitor that finds `CXXThisExpr` that can refer to an object other than -/// the `this` of a member function. -class ThisExprOverridesVisitor : public AnalysisASTVisitor { - using BaseVisitor = AnalysisASTVisitor; - -public: - ThisExprOverridesVisitor( - RecordStorageLocation *ThisPointeeLoc, - const llvm::DenseMap<const Expr *, RecordStorageLocation *> - &ResultObjectMap, - llvm::DenseMap<const CXXThisExpr *, RecordStorageLocation *> - &ThisExprOverrides) - : DefaultThisPointeeLoc(ThisPointeeLoc), ResultObjectMap(ResultObjectMap), - ThisExprOverrides(ThisExprOverrides) { - ThisLocations.push(DefaultThisPointeeLoc); - } - - void traverseConstructorInits(const CXXConstructorDecl *Ctor) { - for (const CXXCtorInitializer *Init : Ctor->inits()) { - TraverseStmt(Init->getInit()); - } - } - - bool TraverseInitListExpr(InitListExpr *ILE) override { - if (!ILE->isSemanticForm() || ILE->isTransparent()) { - BaseVisitor::TraverseInitListExpr(ILE); - return true; - } - bool IsRecordType = ILE->getType()->isRecordType(); - if (IsRecordType) { - auto It = ResultObjectMap.find(ILE); - if (It == ResultObjectMap.end()) { - llvm_unreachable("InitListExpr not found in ResultObjectMap"); - return false; - } - InitListLocations.push(It->second); - } - BaseVisitor::TraverseInitListExpr(ILE); - if (IsRecordType) - InitListLocations.pop(); - return true; - } - - bool TraverseCXXParenListInitExpr(CXXParenListInitExpr *PLIE) override { - auto It = ResultObjectMap.find(PLIE); - if (It == ResultObjectMap.end()) { - llvm_unreachable("CXXParenListInitExpr not found in ResultObjectMap"); - return false; - } - InitListLocations.push(It->second); - BaseVisitor::TraverseCXXParenListInitExpr(PLIE); - InitListLocations.pop(); - return true; - } - - bool TraverseCXXDefaultInitExpr(CXXDefaultInitExpr *CDIE) override { - bool HasInitListLocations = !InitListLocations.empty(); - if (HasInitListLocations) { - auto *Loc = InitListLocations.top(); - ThisLocations.push(Loc); - } - BaseVisitor::TraverseCXXDefaultInitExpr(CDIE); - if (HasInitListLocations) - ThisLocations.pop(); - return true; - } - - bool TraverseCXXThisExpr(CXXThisExpr *This) override { - assert(!ThisLocations.empty()); - auto *Loc = ThisLocations.top(); - if (Loc != DefaultThisPointeeLoc) - ThisExprOverrides[This] = Loc; - return true; - } - - // The default `this` pointee location (null if not in a member function). - RecordStorageLocation *DefaultThisPointeeLoc; - // Locations to use for `this`, with the most recent scope on top. - std::stack<RecordStorageLocation *> ThisLocations; - // A stack of nested InitListExpr and CXXParenListInitExprs storage - // locations that may be used for `this` if we enter a CXXDefaultInitExpr. - std::stack<RecordStorageLocation *> InitListLocations; - // Map to look up a storage location, e.g., when encountering an - // InitListExpr. - const llvm::DenseMap<const Expr *, RecordStorageLocation *> &ResultObjectMap; - // The visitor will update this map with locations to use for `this`, - // if different from `DefaultThisPointeeLoc`. - llvm::DenseMap<const CXXThisExpr *, RecordStorageLocation *> - &ThisExprOverrides; -}; - } // namespace void Environment::initialize() { @@ -597,11 +506,6 @@ void Environment::initialize() { std::make_shared<PrValueToResultObject>(buildResultObjectMap( DACtx, InitialTargetStmt, getThisPointeeStorageLocation(), /*LocForRecordReturnValue=*/nullptr)); - - ThisExprOverrides = - std::make_shared<ThisExprOverridesMap>(buildThisExprOverridesMap( - InitialTargetStmt, getThisPointeeStorageLocation(), - *ResultObjectMap)); return; } @@ -665,11 +569,6 @@ void Environment::initialize() { std::make_shared<PrValueToResultObject>(buildResultObjectMap( DACtx, InitialTargetFunc, getThisPointeeStorageLocation(), LocForRecordReturnVal)); - - ThisExprOverrides = - std::make_shared<ThisExprOverridesMap>(buildThisExprOverridesMap( - InitialTargetFunc, getThisPointeeStorageLocation(), - *ResultObjectMap)); } // FIXME: Add support for resetting globals after function calls to enable the @@ -770,9 +669,6 @@ void Environment::pushCallInternal(const FunctionDecl *FuncDecl, ResultObjectMap = std::make_shared<PrValueToResultObject>( buildResultObjectMap(DACtx, FuncDecl, getThisPointeeStorageLocation(), LocForRecordReturnVal)); - ThisExprOverrides = - std::make_shared<ThisExprOverridesMap>(buildThisExprOverridesMap( - FuncDecl, getThisPointeeStorageLocation(), *ResultObjectMap)); } void Environment::popCall(const CallExpr *Call, const Environment &CalleeEnv) { @@ -840,7 +736,6 @@ LatticeEffect Environment::widen(const Environment &PrevEnv, assert(ReturnLoc == PrevEnv.ReturnLoc); assert(LocForRecordReturnVal == PrevEnv.LocForRecordReturnVal); assert(ThisPointeeLoc == PrevEnv.ThisPointeeLoc); - assert(ThisExprOverrides == PrevEnv.ThisExprOverrides); assert(CallStack == PrevEnv.CallStack); assert(ResultObjectMap == PrevEnv.ResultObjectMap); assert(InitialTargetFunc == PrevEnv.InitialTargetFunc); @@ -878,7 +773,6 @@ Environment Environment::join(const Environment &EnvA, const Environment &EnvB, assert(EnvA.DACtx == EnvB.DACtx); assert(EnvA.LocForRecordReturnVal == EnvB.LocForRecordReturnVal); assert(EnvA.ThisPointeeLoc == EnvB.ThisPointeeLoc); - assert(EnvA.ThisExprOverrides == EnvB.ThisExprOverrides); assert(EnvA.CallStack == EnvB.CallStack); assert(EnvA.ResultObjectMap == EnvB.ResultObjectMap); assert(EnvA.InitialTargetFunc == EnvB.InitialTargetFunc); @@ -890,7 +784,10 @@ Environment Environment::join(const Environment &EnvA, const Environment &EnvB, JoinedEnv.ResultObjectMap = EnvA.ResultObjectMap; JoinedEnv.LocForRecordReturnVal = EnvA.LocForRecordReturnVal; JoinedEnv.ThisPointeeLoc = EnvA.ThisPointeeLoc; - JoinedEnv.ThisExprOverrides = EnvA.ThisExprOverrides; + // The markers that push and pop these follow the syntactic nesting of the + // list-initializations, so all paths reaching a join agree on them. + assert(EnvA.ListInitObjects == EnvB.ListInitObjects); + JoinedEnv.ListInitObjects = EnvA.ListInitObjects; JoinedEnv.InitialTargetFunc = EnvA.InitialTargetFunc; JoinedEnv.InitialTargetStmt = EnvA.InitialTargetStmt; @@ -1342,30 +1239,6 @@ Environment::PrValueToResultObject Environment::buildResultObjectMap( return Map; } -Environment::ThisExprOverridesMap Environment::buildThisExprOverridesMap( - const FunctionDecl *FuncDecl, RecordStorageLocation *ThisPointeeLoc, - const PrValueToResultObject &ResultObjectMap) { - assert(FuncDecl->doesThisDeclarationHaveABody()); - - ThisExprOverridesMap Map = buildThisExprOverridesMap( - FuncDecl->getBody(), ThisPointeeLoc, ResultObjectMap); - - ThisExprOverridesVisitor Visitor(ThisPointeeLoc, ResultObjectMap, Map); - if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(FuncDecl)) { - Visitor.traverseConstructorInits(Ctor); - } - return Map; -} - -Environment::ThisExprOverridesMap Environment::buildThisExprOverridesMap( - Stmt *S, RecordStorageLocation *ThisPointeeLoc, - const PrValueToResultObject &ResultObjectMap) { - ThisExprOverridesMap Map; - ThisExprOverridesVisitor Visitor(ThisPointeeLoc, ResultObjectMap, Map); - Visitor.TraverseStmt(S); - return Map; -} - RecordStorageLocation *getImplicitObjectLocation(const CXXMemberCallExpr &MCE, const Environment &Env) { Expr *ImplicitObject = MCE.getImplicitObjectArgument(); diff --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp index bbb40748c2d59..523afdcdaaa39 100644 --- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp +++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp @@ -484,7 +484,13 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> { } void VisitCXXThisExpr(const CXXThisExpr *S) { - auto *ThisPointeeLoc = Env.getThisPointeeStorageLocation(*S); + // Within a default member initializer used by a list-initialization, + // `this` denotes the object that initialization establishes; the CFG + // brackets it with markers that put it in the environment. Otherwise + // `this` is the pointee of the enclosing member function. + auto *ThisPointeeLoc = Env.getListInitObjectLocation(); + if (ThisPointeeLoc == nullptr) + ThisPointeeLoc = Env.getThisPointeeStorageLocation(); if (ThisPointeeLoc == nullptr) // Unions are not supported yet, and will not have a location for the // `this` expression's pointee. diff --git a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp index 9e0fd8122c890..bc432d025bb66 100644 --- a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp +++ b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp @@ -398,6 +398,16 @@ static void builtinTransfer(unsigned CurBlockID, const CFGElement &Elt, case CFGElement::Initializer: builtinTransferInitializer(Elt.castAs<CFGInitializer>(), State); break; + case CFGElement::ListInitObjectBegin: + // Make the object this list-initialization establishes available while its + // elements -- including the default member initializers it uses -- are + // evaluated. See `Environment::getListInitObjectLocation()`. + State.Env.pushListInitObject(State.Env.getResultObjectLocation( + *Elt.castAs<CFGListInitObjectBegin>().getListInitExpr())); + break; + case CFGElement::ListInitObjectEnd: + State.Env.popListInitObject(); + break; case CFGElement::LifetimeEnds: // Removing declarations when their lifetime ends serves two purposes: // - Eliminate unnecessary clutter from `Environment::DeclToLoc` diff --git a/clang/lib/Analysis/PathDiagnostic.cpp b/clang/lib/Analysis/PathDiagnostic.cpp index a95ef4582be99..c84dc61bf71ee 100644 --- a/clang/lib/Analysis/PathDiagnostic.cpp +++ b/clang/lib/Analysis/PathDiagnostic.cpp @@ -531,6 +531,10 @@ static PathDiagnosticLocation getLocationForCaller(const StackFrame *SF, return PathDiagnosticLocation(Init.getInitializer()->getInit(), SM, CallerSF); } + case CFGElement::ListInitObjectBegin: + case CFGElement::ListInitObjectEnd: + return PathDiagnosticLocation( + Source.castAs<CFGListInitObject>().getListInitExpr(), SM, CallerSF); case CFGElement::AutomaticObjectDtor: { const CFGAutomaticObjDtor &Dtor = Source.castAs<CFGAutomaticObjDtor>(); return PathDiagnosticLocation::createEnd(Dtor.getTriggerStmt(), SM, diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 2b524a956ecc4..a4f37c82b5d97 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -5754,9 +5754,11 @@ struct EnsureImmediateInvocationInDefaultArgs 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. + // Copy the 'this' pointer rather than rebuilding it: rebuilding would + // re-derive its type from the current 'this' type, which would incorrectly + // point it to the outermost class in the case of nested struct + // initialization. Copying keeps the type and location, while still giving + // each use of a default member initializer its own node. ExprResult TransformCXXThisExpr(CXXThisExpr *E) { return E; } // Rewrite to source location to refer to the context in which they are used. @@ -5927,11 +5929,23 @@ 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. + // + // The initializer is rebuilt for *every* use, so that each + // CXXDefaultInitExpr owns its own copy of it. The initializer is written + // once but evaluated once per use, and `this` within it denotes whichever + // object that use initializes; sharing the nodes between uses would make + // the two indistinguishable to anything that keys state on `Expr *`. + // + // Only the rewrites above are semantic; the rest is a copy of an initializer + // that has already been checked where it was written. Re-checking it must + // therefore not diagnose a second time, and if re-checking fails to + // reproduce it we keep the one from the declaration instead of failing here. bool ContainsAnyTemporaries = isa_and_present<ExprWithCleanups>(Field->getInClassInitializer()); + bool SemanticRebuild = + V.HasImmediateCalls || (NeedRebuild && ContainsAnyTemporaries); if (Field->getInClassInitializer() && - !Field->getInClassInitializer()->containsErrors() && - (V.HasImmediateCalls || (NeedRebuild && ContainsAnyTemporaries))) { + !Field->getInClassInitializer()->containsErrors() && SemanticRebuild) { ExprEvalContexts.back().DelayedDefaultInitializationContext = {Loc, Field, CurContext}; ExprEvalContexts.back().IsCurrentlyCheckingDefaultArgumentOrInitializer = @@ -5942,17 +5956,28 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { parentEvaluationContext().InLifetimeExtendingContext; EnsureImmediateInvocationInDefaultArgs Immediate(*this); ExprResult Res; - runWithSufficientStackSpace(Loc, [&] { - Res = Immediate.TransformInitializer(Field->getInClassInitializer(), - /*CXXDirectInit=*/false); - }); - if (!Res.isInvalid()) - Res = ConvertMemberDefaultInitExpression(Field, Res.get(), Loc); + { + std::optional<TentativeAnalysisScope> SilenceRecheck; + if (!SemanticRebuild) + SilenceRecheck.emplace(*this); + runWithSufficientStackSpace(Loc, [&] { + Res = Immediate.TransformInitializer(Field->getInClassInitializer(), + /*CXXDirectInit=*/false); + }); + if (!Res.isInvalid()) + Res = ConvertMemberDefaultInitExpression(Field, Res.get(), Loc); + } if (Res.isInvalid()) { - Field->setInvalidDecl(); - return ExprError(); + if (SemanticRebuild) { + Field->setInvalidDecl(); + return ExprError(); + } + // Keep sharing the initializer from the declaration; it has already been + // checked and diagnosed there. + Init = nullptr; + } else { + Init = Res.get(); } - Init = Res.get(); } if (Field->getInClassInitializer()) { diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 48ce51863c2c0..db52b77d9989a 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -835,6 +835,9 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field, return; } SemaRef.checkInitializerLifetime(MemberEntity, DIE.get()); + // Record that `this` within the default member initializer denotes the + // object this list initializes. + ILE->setInitializesObjectWithDefaultMemberInit(); if (Init < NumInits) ILE->setInit(Init, DIE.get()); else { @@ -5967,6 +5970,7 @@ static void TryOrBuildParenListInitialization( QualType ResultType; Expr *ArrayFiller = nullptr; FieldDecl *InitializedFieldInUnion = nullptr; + bool UsesDefaultMemberInit = false; auto HandleInitializedEntity = [&](const InitializedEntity &SubEntity, const InitializationKind &SubKind, @@ -6158,6 +6162,7 @@ static void TryOrBuildParenListInitialization( return; S.checkInitializerLifetime(SubEntity, DIE.get()); InitExprs.push_back(DIE.get()); + UsesDefaultMemberInit = true; } } else { // C++ [dcl.init]p17.6.2.2 @@ -6211,6 +6216,8 @@ static void TryOrBuildParenListInitialization( CPLIE->setArrayFiller(ArrayFiller); if (InitializedFieldInUnion) CPLIE->setInitializedFieldInUnion(InitializedFieldInUnion); + if (UsesDefaultMemberInit) + CPLIE->setInitializesObjectWithDefaultMemberInit(); *Result = CPLIE; S.Diag(Kind.getLocation(), diag::warn_cxx17_compat_aggregate_init_paren_list) diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 92c555dc427b3..a60de794596ee 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -1281,6 +1281,8 @@ void ASTStmtReader::VisitInitListExpr(InitListExpr *E) { E->updateInit(Record.getContext(), I, Record.readSubExpr()); } E->InitListExprBits.IsExplicit = Record.readBool(); + E->InitListExprBits.InitializesObjectWithDefaultMemberInit = + Record.readBool(); } void ASTStmtReader::VisitDesignatedInitExpr(DesignatedInitExpr *E) { @@ -2402,6 +2404,7 @@ void ASTStmtReader::VisitCXXParenListInitExpr(CXXParenListInitExpr *E) { E->setInitializedFieldInUnion(readDeclAs<FieldDecl>()); } } + E->InitializesObjectWithDefaultMemberInit = Record.readBool(); E->updateDependence(); } diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 782fecdbd0c80..24fa8a2497277 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -1251,6 +1251,7 @@ void ASTStmtWriter::VisitInitListExpr(InitListExpr *E) { Record.AddStmt(E->getInit(I)); } Record.writeBool(E->isExplicit()); + Record.writeBool(E->initializesObjectWithDefaultMemberInit()); Code = serialization::EXPR_INIT_LIST; } @@ -2418,6 +2419,7 @@ void ASTStmtWriter::VisitCXXParenListInitExpr(CXXParenListInitExpr *E) { else Record.AddDeclRef(UnionField); } + Record.writeBool(E->initializesObjectWithDefaultMemberInit()); Code = serialization::EXPR_CXX_PAREN_LIST_INIT; } diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 530fae9ee2dee..3bedc55a4695d 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -976,6 +976,11 @@ void ExprEngine::processCFGElement(const CFGElement E, ExplodedNode *Pred, case CFGElement::LoopExit: ProcessLoopExit(E.castAs<CFGLoopExit>().getLoopStmt(), Pred); return; + case CFGElement::ListInitObjectBegin: + case CFGElement::ListInitObjectEnd: + // These only carry information for consumers that need to know which + // object a default member initializer initializes; nothing to evaluate. + return; case CFGElement::LifetimeEnds: ProcessLifetimeEnd(E.castAs<CFGLifetimeEnds>().getTriggerStmt(), E.castAs<CFGLifetimeEnds>().getVarDecl(), Pred); diff --git a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp index 1b217be452c6b..f7f0b54046bf7 100644 --- a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp @@ -91,6 +91,9 @@ const Stmt *SymbolConjured::getStmt() const { return Elem->castAs<CFGScopeBegin>().getTriggerStmt(); case CFGElement::ScopeEnd: return Elem->castAs<CFGScopeEnd>().getTriggerStmt(); + case CFGElement::ListInitObjectBegin: + case CFGElement::ListInitObjectEnd: + return Elem->castAs<CFGListInitObject>().getListInitExpr(); case CFGElement::NewAllocator: return Elem->castAs<CFGNewAllocator>().getAllocatorExpr(); case CFGElement::LifetimeEnds: diff --git a/clang/test/AST/ast-dump-uses-default-member-init.cpp b/clang/test/AST/ast-dump-uses-default-member-init.cpp new file mode 100644 index 0000000000000..065ee13429b10 --- /dev/null +++ b/clang/test/AST/ast-dump-uses-default-member-init.cpp @@ -0,0 +1,52 @@ +// Test that a list-initialization which uses a default member initializer is +// marked as such. Within such an initializer, `this` denotes the object the +// list-initialization initializes, not the object of the enclosing member +// function. + +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++20 -ast-dump %s \ +// RUN: | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++20 -ast-dump=json %s \ +// RUN: | FileCheck --check-prefix JSON %s + +struct WithDefault { + int x; + int *self = &x; +}; + +struct NoDefault { + int x; + int y; +}; + +void braces() { + WithDefault a{1}; + NoDefault b{1, 2}; +} + +// CHECK-LABEL: FunctionDecl {{.*}} braces +// CHECK: InitListExpr {{.*}} 'WithDefault' explicit uses default member init +// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1 +// CHECK-NEXT: CXXDefaultInitExpr +// The marker is absent, so the line ends after `explicit`. +// CHECK: InitListExpr {{.*}} 'NoDefault' explicit{{$}} +// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1 + +void parens() { + WithDefault a(1); + NoDefault b(1, 2); +} + +// CHECK-LABEL: FunctionDecl {{.*}} parens +// CHECK: CXXParenListInitExpr {{.*}} 'WithDefault' uses default member init +// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1 +// CHECK-NEXT: CXXDefaultInitExpr +// CHECK: CXXParenListInitExpr {{.*}} 'NoDefault'{{$}} +// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1 + +// JSON: "kind": "InitListExpr", +// JSON: "qualType": "WithDefault" +// JSON: "usesDefaultMemberInit": true + +// JSON: "kind": "CXXParenListInitExpr", +// JSON: "qualType": "WithDefault" +// JSON: "usesDefaultMemberInit": true diff --git a/clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp b/clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp index 6bab26d472e66..b27df5c40eb4e 100644 --- a/clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp @@ -525,251 +525,4 @@ TEST_F(EnvironmentTest, Stmt) { } // This is a crash repro. -TEST_F(EnvironmentTest, LambdaCapturingThisInFieldInitializer) { - using namespace ast_matchers; - std::string Code = R"cc( - struct S { - int f{[this]() { return 1; }()}; - }; - )cc"; - - auto Unit = - tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); - auto &Context = Unit->getASTContext(); - - ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); - - auto *LambdaCallOperator = selectFirst<CXXMethodDecl>( - "method", match(cxxMethodDecl(hasName("operator()"), - ofClass(cxxRecordDecl(isLambda()))) - .bind("method"), - Context)); - - Environment Env(DAContext, *LambdaCallOperator); - // Don't crash when initializing. - Env.initialize(); - // And initialize the captured `this` pointee. - ASSERT_NE(nullptr, Env.getThisPointeeStorageLocation()); -} - -TEST_F(EnvironmentTest, ThisExprLocInNonMemberIsInitListLoc) { - using namespace ast_matchers; - std::string Code = R"cc( - struct Other { - int i = 0; - int j = this->i; - }; - void target(int x) { - Other o = {x}; - } - )cc"; - - auto Unit = - tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++17"}); - auto &Context = Unit->getASTContext(); - - ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); - - auto *Func = selectFirst<FunctionDecl>( - "func", match(functionDecl(hasName("target")).bind("func"), Context)); - ASSERT_NE(Func, nullptr); - - auto Results = match( - initListExpr( - hasInit(1, hasDescendant(memberExpr( - hasObjectExpression(cxxThisExpr().bind("this_i")), - member(fieldDecl(hasName("i"))))))) - .bind("init_list"), - Context); - - auto *ThisForI = selectFirst<CXXThisExpr>("this_i", Results); - ASSERT_NE(ThisForI, nullptr); - auto *InitList = selectFirst<InitListExpr>("init_list", Results); - ASSERT_NE(InitList, nullptr); - - Environment Env(DAContext, *Func); - Env.initialize(); - auto *DefaultThis = Env.getThisPointeeStorageLocation(); - EXPECT_EQ(DefaultThis, nullptr); - - RecordStorageLocation &InitListLoc = Env.getResultObjectLocation(*InitList); - EXPECT_EQ(&InitListLoc, Env.getThisPointeeStorageLocation(*ThisForI)); -} - -TEST_F(EnvironmentTest, ThisExprLocInNonMemberIsParenListInitLoc) { - using namespace ast_matchers; - std::string Code = R"cc( - struct Other { - int i = 0; - int j = this->i; - }; - void target(int x) { - Other o(x); - } - )cc"; - - auto Unit = - tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++20"}); - auto &Context = Unit->getASTContext(); - - ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); - - auto *Func = selectFirst<FunctionDecl>( - "func", match(functionDecl(hasName("target")).bind("func"), Context)); - ASSERT_NE(Func, nullptr); - - const ast_matchers::internal::VariadicDynCastAllOfMatcher< - Stmt, CXXParenListInitExpr> - cxxParenListInitExpr; - - auto Results = - match(cxxParenListInitExpr( - hasDescendant(memberExpr( - hasObjectExpression(cxxThisExpr().bind("this_i")), - member(fieldDecl(hasName("i")))))) - .bind("init_list"), - Context); - - auto *ThisForI = selectFirst<CXXThisExpr>("this_i", Results); - ASSERT_NE(ThisForI, nullptr); - auto *InitList = selectFirst<CXXParenListInitExpr>("init_list", Results); - ASSERT_NE(InitList, nullptr); - - Environment Env(DAContext, *Func); - Env.initialize(); - auto *DefaultThis = Env.getThisPointeeStorageLocation(); - EXPECT_EQ(DefaultThis, nullptr); - - RecordStorageLocation &InitListLoc = Env.getResultObjectLocation(*InitList); - EXPECT_EQ(&InitListLoc, Env.getThisPointeeStorageLocation(*ThisForI)); -} - -TEST_F(EnvironmentTest, ThisExprLocInMemberIsInitListLoc) { - using namespace ast_matchers; - std::string Code = R"cc( - struct Other { - int i = 0; - int j = this->i; - }; - struct Foo { - void target() { - Other o = {this->x}; - } - int x = 0; - }; - )cc"; - - auto Unit = - tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++17"}); - auto &Context = Unit->getASTContext(); - - ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); - - auto *Method = selectFirst<CXXMethodDecl>( - "method", - match(cxxMethodDecl(hasName("target")).bind("method"), Context)); - ASSERT_NE(Method, nullptr); - - auto Results = match( - initListExpr( - hasInit(0, hasDescendant(memberExpr( - hasObjectExpression(cxxThisExpr().bind("this_x")), - member(fieldDecl(hasName("x")))))), - hasInit(1, hasDescendant(memberExpr( - hasObjectExpression(cxxThisExpr().bind("this_i")), - member(fieldDecl(hasName("i"))))))) - .bind("init_list"), - Context); - - auto *ThisForX = selectFirst<CXXThisExpr>("this_x", Results); - ASSERT_NE(ThisForX, nullptr); - auto *ThisForI = selectFirst<CXXThisExpr>("this_i", Results); - ASSERT_NE(ThisForI, nullptr); - auto *InitList = selectFirst<InitListExpr>("init_list", Results); - ASSERT_NE(InitList, nullptr); - - Environment Env(DAContext, *Method); - Env.initialize(); - auto *DefaultThis = Env.getThisPointeeStorageLocation(); - EXPECT_NE(DefaultThis, nullptr); - - EXPECT_EQ(DefaultThis, Env.getThisPointeeStorageLocation(*ThisForX)); - RecordStorageLocation &InitListLoc = Env.getResultObjectLocation(*InitList); - EXPECT_EQ(&InitListLoc, Env.getThisPointeeStorageLocation(*ThisForI)); -} - -TEST_F(EnvironmentTest, ThisExprLocInCtorInitializerIsInitListLoc) { - using namespace ast_matchers; - std::string Code = R"cc( - struct B { - int a = 0; - int b = this->a; - }; - struct Other { - int i = 0; - B b = {this->i}; - }; - struct target { - target() : x(-1) {} - int x; - Other o = {this->x}; - }; - )cc"; - - auto Unit = - tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++17"}); - auto &Context = Unit->getASTContext(); - - ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); - - auto *Ctor = selectFirst<CXXConstructorDecl>( - "ctor", - match(cxxConstructorDecl(hasName("target")).bind("ctor"), Context)); - ASSERT_NE(Ctor, nullptr); - Ctor->dump(); - - auto Results = match( - initListExpr( - hasInit(0, hasDescendant(memberExpr( - hasObjectExpression(cxxThisExpr().bind("this_x")), - member(fieldDecl(hasName("x")))))), - hasInit(1, hasDescendant( - initListExpr( - hasInit(0, hasDescendant(memberExpr( - hasObjectExpression( - cxxThisExpr().bind("this_i")), - member(fieldDecl(hasName("i")))))), - hasInit(1, hasDescendant(memberExpr( - hasObjectExpression( - cxxThisExpr().bind("this_a")), - member(fieldDecl(hasName("a"))))))) - .bind("init_list_inner")))) - .bind("init_list_outer"), - Context); - - auto *ThisForX = selectFirst<CXXThisExpr>("this_x", Results); - ASSERT_NE(ThisForX, nullptr); - auto *ThisForI = selectFirst<CXXThisExpr>("this_i", Results); - ASSERT_NE(ThisForI, nullptr); - auto *ThisForA = selectFirst<CXXThisExpr>("this_a", Results); - ASSERT_NE(ThisForA, nullptr); - auto *InitListOuter = selectFirst<InitListExpr>("init_list_outer", Results); - ASSERT_NE(InitListOuter, nullptr); - auto *InitListInner = selectFirst<InitListExpr>("init_list_inner", Results); - ASSERT_NE(InitListInner, nullptr); - - Environment Env(DAContext, *Ctor); - Env.initialize(); - auto *DefaultThis = Env.getThisPointeeStorageLocation(); - EXPECT_NE(DefaultThis, nullptr); - - EXPECT_EQ(DefaultThis, Env.getThisPointeeStorageLocation(*ThisForX)); - RecordStorageLocation &InitListOuterLoc = - Env.getResultObjectLocation(*InitListOuter); - EXPECT_EQ(&InitListOuterLoc, Env.getThisPointeeStorageLocation(*ThisForI)); - RecordStorageLocation &InitListInnerLoc = - Env.getResultObjectLocation(*InitListInner); - EXPECT_EQ(&InitListInnerLoc, Env.getThisPointeeStorageLocation(*ThisForA)); -} - } // namespace diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp index 60d1d038540ff..4bb154734e657 100644 --- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp @@ -2371,6 +2371,128 @@ TEST(TransferTest, DefaultInitializer) { }); } +TEST(TransferTest, DefaultInitializerInAggregateInit) { + // `this` inside the default member initializer for `Baz` denotes the object + // the list-initialization creates, not the enclosing function's object, so + // `Baz` binds to that object's `Bar`. + std::string Code = R"( + struct S { + int Bar; + int &Baz = this->Bar; + }; + void target(int Foo) { + S Var = {Foo}; + int &Qux = Var.Baz; + // [[p]] + } + )"; + runDataflow( + Code, + [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, + ASTContext &ASTCtx) { + ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); + const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); + + // `target` is not a member function, so there is no enclosing object. + EXPECT_THAT(Env.getThisPointeeStorageLocation(), IsNull()); + + const ValueDecl *VarDecl = findValueDecl(ASTCtx, "Var"); + ASSERT_THAT(VarDecl, NotNull()); + const auto *VarLoc = + cast<RecordStorageLocation>(Env.getStorageLocation(*VarDecl)); + const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar"); + ASSERT_THAT(BarDecl, NotNull()); + + const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); + ASSERT_THAT(QuxDecl, NotNull()); + EXPECT_EQ(Env.getStorageLocation(*QuxDecl), + VarLoc->getChild(*cast<FieldDecl>(BarDecl))); + }); +} + +TEST(TransferTest, DefaultInitializerInAggregateInitInMemberFunction) { + // Here there *is* an enclosing object, and `this` inside the default member + // initializer must denote the list-initialized one rather than it. + std::string Code = R"( + struct S { + int Bar; + int &Baz = this->Bar; + }; + struct Enclosing { + int Other; + void target(int Foo) { + S Var = {Foo}; + int &Qux = Var.Baz; + // [[p]] + } + }; + )"; + runDataflow( + Code, + [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, + ASTContext &ASTCtx) { + ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); + const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); + + const auto *ThisLoc = Env.getThisPointeeStorageLocation(); + ASSERT_THAT(ThisLoc, NotNull()); + + const ValueDecl *VarDecl = findValueDecl(ASTCtx, "Var"); + ASSERT_THAT(VarDecl, NotNull()); + const auto *VarLoc = + cast<RecordStorageLocation>(Env.getStorageLocation(*VarDecl)); + + const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); + ASSERT_THAT(QuxDecl, NotNull()); + const StorageLocation *QuxLoc = Env.getStorageLocation(*QuxDecl); + + // `Qux` refers to `Var`'s `Bar`; the enclosing object is a different + // object entirely. + ASSERT_THAT(QuxLoc, NotNull()); + EXPECT_NE(static_cast<const StorageLocation *>(ThisLoc), + static_cast<const StorageLocation *>(VarLoc)); + const auto *BarDecl = cast<FieldDecl>(findValueDecl(ASTCtx, "Bar")); + EXPECT_EQ(QuxLoc, VarLoc->getChild(*BarDecl)); + }); +} + +TEST(TransferTest, DefaultInitializerInAggregateInitInCtorInitializer) { + // The list-initialization is itself a member initializer, so the enclosing + // object is the one under construction; `this` inside the default member + // initializer must still denote the list-initialized object. + std::string Code = R"( + struct NonTrivDtor { NonTrivDtor(int n); ~NonTrivDtor() {} }; + struct S { + int Bar; + NonTrivDtor Tmp = NonTrivDtor(this->Bar); + int &Baz = this->Bar; + }; + struct target { + S o_; + target(int f) : o_(S{f}) { + int &Qux = o_.Baz; + // [[p]] + } + }; + )"; + runDataflow( + Code, + [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, + ASTContext &ASTCtx) { + ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); + const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); + const auto *ThisLoc = Env.getThisPointeeStorageLocation(); + ASSERT_THAT(ThisLoc, NotNull()); + const auto *ODecl = cast<FieldDecl>(findValueDecl(ASTCtx, "o_")); + const auto *OLoc = + cast<RecordStorageLocation>(ThisLoc->getChild(*ODecl)); + const auto *BarDecl = cast<FieldDecl>(findValueDecl(ASTCtx, "Bar")); + const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "Qux"); + ASSERT_THAT(QuxDecl, NotNull()); + EXPECT_EQ(Env.getStorageLocation(*QuxDecl), OLoc->getChild(*BarDecl)); + }); +} + TEST(TransferTest, DefaultInitializerReference) { std::string Code = R"( struct target { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
