https://github.com/jvoung created https://github.com/llvm/llvm-project/pull/146900
When `this` is under a CXXDefaultInitExpr. E.g., struct S { int x; int y = this->x; }; struct R { int foo() { // `this` for `a` refers to an R, but `this` // for `x` refers to an S. return S{this->a}.y; } int a; }; Prepares for https://github.com/llvm/llvm-project/issues/128068 >From 7cadb15cacf77231d63bb98846346f819d644fa7 Mon Sep 17 00:00:00 2001 From: Jan Voung <jvo...@gmail.com> Date: Thu, 3 Jul 2025 13:37:18 +0000 Subject: [PATCH] [clang][dataflow] Handle when `this` refers to a different location When `this` is under a CXXDefaultInitExpr. E.g., struct S { int x; int y = this->x; }; struct R { int foo() { // `this` for `a` refers to an R, but `this` // for `x` refers to an S. return S{this->a}.y; } int a; }; Prepares for https://github.com/llvm/llvm-project/issues/128068 --- .../FlowSensitive/DataflowEnvironment.h | 41 ++++ .../FlowSensitive/DataflowEnvironment.cpp | 134 +++++++++++ clang/lib/Analysis/FlowSensitive/Transfer.cpp | 2 +- .../FlowSensitive/DataflowEnvironmentTest.cpp | 222 ++++++++++++++++++ .../UncheckedOptionalAccessModelTest.cpp | 21 ++ 5 files changed, 419 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h index 097ff2bdfe7ad..e9b85093d8e3e 100644 --- a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h +++ b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h @@ -18,6 +18,7 @@ #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/Type.h" #include "clang/Analysis/FlowSensitive/ASTOps.h" #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" @@ -351,10 +352,34 @@ class Environment { /// Returns the storage location assigned to the `this` pointee in the /// environment or null if the `this` pointee has no assigned storage location /// in the environment. + /// If you want to look up the storage location for a specific `CXXThisExpr`, + /// use the overload that takes a `CXXThisExpr`. RecordStorageLocation *getThisPointeeStorageLocation() const { 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.: + /// + /// \code + /// struct S { + /// int x; + /// int y = this->x; + /// }; + /// int foo() { + /// return S{10}.y; // will have a `this` for initializing `S::y`. + /// } + /// \endcode + RecordStorageLocation * + getThisPointeeStorageLocation(const CXXThisExpr &ThisExpr) const { + auto It = ThisExprOverrides->find(&ThisExpr); + if (It == ThisExprOverrides->end()) + return ThisPointeeLoc; + return It->second; + } + /// Sets the storage location assigned to the `this` pointee in the /// environment. void setThisPointeeStorageLocation(RecordStorageLocation &Loc) { @@ -684,6 +709,8 @@ 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; @@ -747,6 +774,15 @@ 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; @@ -793,6 +829,11 @@ class Environment { // 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/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp index 256ea18284189..9c6d40d3e1525 100644 --- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp +++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp @@ -15,12 +15,14 @@ #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/Stmt.h" #include "clang/AST/Type.h" #include "clang/Analysis/FlowSensitive/ASTOps.h" #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" #include "clang/Analysis/FlowSensitive/DataflowLattice.h" +#include "clang/Analysis/FlowSensitive/StorageLocation.h" #include "clang/Analysis/FlowSensitive/Value.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" @@ -30,6 +32,7 @@ #include "llvm/Support/ErrorHandling.h" #include <cassert> #include <memory> +#include <stack> #include <utility> #define DEBUG_TYPE "dataflow" @@ -486,6 +489,97 @@ 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() { @@ -498,6 +592,11 @@ void Environment::initialize() { std::make_shared<PrValueToResultObject>(buildResultObjectMap( DACtx, InitialTargetStmt, getThisPointeeStorageLocation(), /*LocForRecordReturnValue=*/nullptr)); + + ThisExprOverrides = + std::make_shared<ThisExprOverridesMap>(buildThisExprOverridesMap( + InitialTargetStmt, getThisPointeeStorageLocation(), + *ResultObjectMap)); return; } @@ -559,6 +658,11 @@ 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 @@ -659,6 +763,9 @@ 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) { @@ -726,6 +833,7 @@ 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); @@ -763,6 +871,7 @@ 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); @@ -774,6 +883,7 @@ 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; JoinedEnv.InitialTargetFunc = EnvA.InitialTargetFunc; JoinedEnv.InitialTargetStmt = EnvA.InitialTargetStmt; @@ -1225,6 +1335,30 @@ 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 86a816e2e406c..bafbc61de3e29 100644 --- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp +++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp @@ -419,7 +419,7 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> { } void VisitCXXThisExpr(const CXXThisExpr *S) { - auto *ThisPointeeLoc = Env.getThisPointeeStorageLocation(); + auto *ThisPointeeLoc = Env.getThisPointeeStorageLocation(*S); if (ThisPointeeLoc == nullptr) // Unions are not supported yet, and will not have a location for the // `this` expression's pointee. diff --git a/clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp b/clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp index 737277e167edd..090edef1a610b 100644 --- a/clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp @@ -8,7 +8,9 @@ #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" #include "TestingSupport.h" +#include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/Stmt.h" #include "clang/ASTMatchers/ASTMatchFinder.h" @@ -550,4 +552,224 @@ TEST_F(EnvironmentTest, LambdaCapturingThisInFieldInitializer) { 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/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp index 1dd07834bfd7e..be7c2c43c99c3 100644 --- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp @@ -4130,6 +4130,27 @@ TEST_P(UncheckedOptionalAccessTest, DiagnosticsHaveRanges) { )cc"); } +TEST_P(UncheckedOptionalAccessTest, ConstructorOtherStructField) { + // Repro for a crash: https://github.com/llvm/llvm-project/issues/128068 + ExpectDiagnosticsFor(R"cc( + #include "unchecked_optional_access_test.h" + + struct NonTrivDtor { + NonTrivDtor(int n); + ~NonTrivDtor() {} + }; + struct Other { + $ns::$optional<int> x; + NonTrivDtor b = NonTrivDtor(x.value()); + }; + struct target { + target(int f) : f_(f), o_(Other{f_}) {} + int f_; + Other o_; + }; + )cc"); +} + // FIXME: Add support for: // - constructors (copy, move) // - assignment operators (default, copy, move) _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits