[clang] [clang][dataflow] Handle when `this` refers to a different location (PR #146900)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 3 08:09:59 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
@llvm/pr-subscribers-clang-analysis
Author: Jan Voung (jvoung)
<details>
<summary>Changes</summary>
When `this` is under a CXXDefaultInitExpr it could refer to the location of an InitListExpr object instead of the `this` of a member function. 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
---
Patch is 22.37 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/146900.diff
5 Files Affected:
- (modified) clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h (+41)
- (modified) clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp (+134)
- (modified) clang/lib/Analysis/FlowSensitive/Transfer.cpp (+1-1)
- (modified) clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp (+222)
- (modified) clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp (+21)
``````````diff
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")),
+ ...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/146900
More information about the cfe-commits
mailing list