[clang] [RFC][clang][AST] Copy default-member-init wherever it is used and add a tag to its initialization init-list node (PR #221299)
Yihan Wang via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 4 10:48:23 PDT 2026
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 <yronglin777 at gmail.com>
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 <yronglin777 at gmail.com>
---
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 {
More information about the cfe-commits
mailing list