https://github.com/dingxiangfei2009 updated https://github.com/llvm/llvm-project/pull/230070
>From 6fa6f085f64868acfe72e3e90e7749dd0e43d4fa Mon Sep 17 00:00:00 2001 From: Xiangfei Ding <[email protected]> Date: Thu, 8 Oct 2026 10:44:43 +0000 Subject: [PATCH] [include-cleaner] Trace through incomplete forward declaration Use of initializer should constitute the use of the base type. It appears that we have not been recording such use, potentially indirectly through incomplete forward declaration. Signed-off-by: Xiangfei Ding <[email protected]> --- .../include-cleaner/lib/WalkAST.cpp | 33 +++++++++++++-- .../unittests/AnalysisTest.cpp | 18 ++++++++ .../include-cleaner/unittests/WalkASTTest.cpp | 41 +++++++++++++++++++ 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp index 5449411371e60..617d544df200f 100644 --- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp +++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp @@ -101,10 +101,14 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> { NamedDecl *getMemberProvider(QualType Base) { if (Base->isPointerType()) return getMemberProvider(Base->getPointeeType()); - if (const auto *TT = dyn_cast<TypedefType>(Base)) - return TT->getDecl(); - if (const auto *UT = dyn_cast<UsingType>(Base)) - return UT->getDecl(); + // An alias of an incomplete record does not provide the record definition. + if (const auto *RT = Base->getAs<RecordType>(); + RT && RT->getDecl()->isCompleteDefinition()) { + if (const auto *TT = dyn_cast<TypedefType>(Base)) + return TT->getDecl(); + if (const auto *UT = dyn_cast<UsingType>(Base)) + return UT->getDecl(); + } // A heuristic: to resolve a template type to **only** its template name. // We're only using this method for the base type of MemberExpr, in general // the template provides the member, and the critical case `unique_ptr<Foo>` @@ -248,6 +252,27 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> { return true; } + bool VisitInitListExpr(InitListExpr *E) { + const InitListExpr *Sem = E->isSemanticForm() ? E : E->getSemanticForm(); + QualType Type = Sem ? Sem->getType() : E->getType(); + report(E->getBeginLoc(), getMemberProvider(Type), RefType::Implicit); + return true; + } + + bool VisitCXXParenListInitExpr(CXXParenListInitExpr *E) { + report(E->getBeginLoc(), getMemberProvider(E->getType()), + RefType::Implicit); + return true; + } + + bool VisitDesignatedInitExpr(DesignatedInitExpr *E) { + for (const DesignatedInitExpr::Designator &D : E->designators()) { + if (D.isFieldDesignator()) + report(D.getFieldLoc(), D.getFieldDecl(), RefType::Implicit); + } + return true; + } + bool VisitOverloadExpr(OverloadExpr *E) { // Since we can't prove which overloads are used, report all of them. for (NamedDecl *D : E->decls()) diff --git a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp index 5df8792c93b2e..22cb9c5abdb24 100644 --- a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp @@ -445,6 +445,24 @@ TEST_F(AnalyzeTest, ImplicitOperatorNewDeleteNotUnused) { EXPECT_THAT(Results.Unused, testing::IsEmpty()); } +TEST_F(AnalyzeTest, ForwardDeclaredTypedefAggregateInit) { + Inputs.Code = R"cpp( +#include "fwd.h" +#include "def.h" + +static const Bar b = { 1 }; +)cpp"; + Inputs.ExtraFiles["fwd.h"] = guard("typedef struct Foo Bar;"); + Inputs.ExtraFiles["def.h"] = guard("struct Foo { int a; };"); + TestAST AST(Inputs); + auto Decls = AST.context().getTranslationUnitDecl()->decls(); + auto Results = + analyze(std::vector<Decl *>{Decls.begin(), Decls.end()}, + PP.MacroReferences, PP.Includes, &PI, AST.preprocessor()); + EXPECT_THAT(Results.Unused, testing::IsEmpty()); + EXPECT_THAT(Results.Missing, testing::IsEmpty()); +} + TEST(FixIncludes, Basic) { llvm::StringRef Code = R"cpp(#include "d.h" #include "a.h" diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp index c9519c2e91b68..519368b23ddac 100644 --- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp @@ -472,6 +472,22 @@ TEST(WalkAST, MemberExprs) { namespace ns { template<typename> struct Foo { int a; }; } using ns::$implicit^Foo;)cpp", "void k(Foo<int> b) { b.^a; }"); + // If the alias points to an incomplete forward declaration, look through to + // the underlying record declaration. + testWalk(R"cpp( + typedef struct $implicit^Foo Bar; + struct Foo { int a; };)cpp", + "void test(Bar b) { b.^a; }"); + testWalk(R"cpp( + typedef struct $implicit^Foo Bar; + typedef Bar Baz; + struct Foo { int a; };)cpp", + "void test(Baz b) { b.^a; }"); + testWalk(R"cpp( + namespace ns { struct $implicit^Foo; } + using Bar = ns::Foo; + namespace ns { struct Foo { int a; }; })cpp", + "void test(Bar b) { b.^a; }"); // Test the dependent-type case (CXXDependentScopeMemberExpr) testWalk("template<typename T> struct $implicit^Base { void method(); };", "template<typename T> void k(Base<T> t) { t.^method(); }"); @@ -487,6 +503,31 @@ TEST(WalkAST, ConstructExprs) { testWalk("struct $implicit^S { S(int); };", "S ^t(42);"); testWalk("struct $implicit^S { S(int); };", "S t = ^42;"); testWalk("namespace ns { struct S{}; } using ns::$implicit^S;", "S ^t;"); + testWalk("typedef struct $implicit^S T; struct S {};", "T ^t;"); +} + +TEST(WalkAST, InitListExprs) { + testWalk("struct $implicit^S { int a; };", "S s = ^{1};"); + testWalk("struct $implicit^S { int a; };", "S s^(1);"); + testWalk("struct S { int $implicit^a; };", "S s = {.^a = 1};"); + testWalk("struct S { int a; }; using $implicit^T = S;", "T s = ^{1};"); + testWalk(R"cpp( + typedef struct $implicit^Foo Bar; + struct Foo { int a; };)cpp", + "Bar b = ^{1};"); + testWalk(R"cpp( + typedef struct $implicit^Foo Bar; + using Baz = Bar; + struct Foo { int a; };)cpp", + "Baz b = ^{1};"); + testWalk(R"cpp( + typedef struct $implicit^Foo Bar; + struct Foo { int a; };)cpp", + "Bar b = ^{.a = 1};"); + testWalk(R"cpp( + typedef struct Foo Bar; + struct Foo { int $implicit^a; };)cpp", + "Bar b = {.^a = 1};"); } TEST(WalkAST, Operator) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
