https://github.com/dmaclach updated https://github.com/llvm/llvm-project/pull/211119
>From f2520b1581449fb1e0db2aa24dbcb450e523e6f5 Mon Sep 17 00:00:00 2001 From: Dave MacLachlan <[email protected]> Date: Tue, 21 Jul 2026 14:56:35 -0700 Subject: [PATCH] Add Objective-C support to include-cleaner's AST walker. Extend WalkAST to recognize and report references in Objective-C constructs, including interfaces, protocols, message expressions, properties, categories, compatible aliases, and instance variables. Also update the test helper to support custom compiler arguments and add corresponding unit tests. --- .../include-cleaner/lib/WalkAST.cpp | 166 +++++++++- .../include-cleaner/unittests/WalkASTTest.cpp | 296 +++++++++++++++++- 2 files changed, 457 insertions(+), 5 deletions(-) diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp index d444ddd90839d..e3e610b8c33d8 100644 --- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp +++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp @@ -15,7 +15,6 @@ #include "clang/AST/DeclTemplate.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" -#include "clang/AST/NestedNameSpecifier.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/TemplateBase.h" #include "clang/AST/TemplateName.h" @@ -25,7 +24,6 @@ #include "clang/Basic/OperatorKinds.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/Specifiers.h" -#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" @@ -395,6 +393,170 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> { report(E->getExprLoc(), E->getOperatorDelete(), RefType::Ambiguous); return true; } + + // Objective-C support + + bool VisitObjCInterfaceTypeLoc(ObjCInterfaceTypeLoc TL) { + reportType(TL.getNameLoc(), TL.getIFaceDecl()); + return true; + } + + // Protocols are odd in that they are covered by Traverse instead of Visit. + bool TraverseObjCProtocolLoc(ObjCProtocolLoc ProtocolLoc) { + if (auto *Proto = ProtocolLoc.getProtocol()) { + report(ProtocolLoc.getLocation(), Proto); + } + return true; + } + + bool VisitObjCImplementationDecl(ObjCImplementationDecl *D) { + if (auto *Interface = D->getClassInterface()) { + report(D->getLocation(), Interface); + } + return true; + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *E) { + // Identify the selector and the method declaration + if (auto *Method = E->getMethodDecl()) { + // Report the method as a used symbol + report(E->getSelectorStartLoc(), Method); + } + + // If it's a class message, report the interface/class as used + if (E->getReceiverKind() == ObjCMessageExpr::Class) { + if (auto *Interface = E->getReceiverInterface()) { + report(E->getReceiverRange().getBegin(), Interface); + } + } + return true; + } + + bool VisitObjCPropertyDecl(clang::ObjCPropertyDecl *PD) { + reportType(PD->getLocation(), PD); + return true; + } + + bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *E) { + // Unconditionally report property declarations and their backing accessor + // methods. Dot-notation or pseudo-object references (`foo.bar`) require + // the underlying property definition or getters/setters to compile. + // Bypassing transient compiler state flags (isMessagingGetter/ + // isMessagingSetter) guarantees that the declaring + // header keeps the properties recorded as used. + if (E->isExplicitProperty()) { + if (auto *Prop = E->getExplicitProperty()) { + report(E->getLocation(), Prop); + if (auto *Getter = Prop->getGetterMethodDecl()) + report(E->getLocation(), Getter); + if (auto *Setter = Prop->getSetterMethodDecl()) + report(E->getLocation(), Setter); + } + } else { + if (auto *Getter = E->getImplicitPropertyGetter()) + report(E->getLocation(), Getter); + if (auto *Setter = E->getImplicitPropertySetter()) + report(E->getLocation(), Setter); + } + return true; + } + + bool VisitObjCProtocolExpr(ObjCProtocolExpr *E) { + if (auto *Proto = E->getProtocol()) { + report(E->getProtocolIdLoc(), Proto); + } + return true; + } + + bool VisitCastExpr(CastExpr *E) { + // Handle implicit or explicit casts between Objective-C object pointers + // aimed towards protocol-qualification (e.g., `ClassName *` to + // `id<Proto>`). + QualType SourceType = E->getSubExpr()->getType(); + QualType DestType = E->getType(); + + const auto *SrcPtr = SourceType->getAs<ObjCObjectPointerType>(); + const auto *DestPtr = DestType->getAs<ObjCObjectPointerType>(); + + // If we're casting from a known class pointer to protocol conformance. + if (SrcPtr && DestPtr && SrcPtr->getInterfaceDecl()) { + const ObjCInterfaceDecl *Class = SrcPtr->getInterfaceDecl(); + ASTContext &Ctx = Class->getASTContext(); + + // For every protocol required by the destination type. + for (const ObjCProtocolDecl *Proto : DestPtr->quals()) { + const ObjCInterfaceDecl *Current = Class; + // Search the inheritance hierarchy for the provider of conformance. + while (Current) { + bool ConformsDirectly = false; + for (const auto *PI : Current->protocols()) { + if (Ctx.ProtocolCompatibleWithProtocol( + const_cast<ObjCProtocolDecl *>(Proto), + const_cast<ObjCProtocolDecl *>(PI))) { + ConformsDirectly = true; + break; + } + } + // If the class itself provides the conformance directly, we don't + // need to keep searching Categories. + if (ConformsDirectly) + break; + + // If the class doesn't declare direct conformance but conformance is + // injected via a visible Category attached to this class, note that + // the category header is required by recording an Implicit reference + // to it. + for (const auto *Cat : Current->visible_categories()) { + for (auto *PI : Cat->protocols()) { + if (Ctx.ProtocolCompatibleWithProtocol( + const_cast<ObjCProtocolDecl *>(Proto), + const_cast<ObjCProtocolDecl *>(PI))) { + report(E->getExprLoc(), const_cast<ObjCCategoryDecl *>(Cat), + RefType::Implicit); + } + } + } + Current = Current->getSuperClass(); + } + } + } + return true; + } + + bool VisitObjCCategoryDecl(ObjCCategoryDecl *D) { + // A category declaration depends on its base interface. + if (auto *Interface = D->getClassInterface()) { + report(D->getLocation(), Interface); + } + return true; + } + + bool VisitObjCCategoryImplDecl(ObjCCategoryImplDecl *D) { + // Implementation requires the base interface. + if (auto *Interface = D->getClassInterface()) { + report(D->getLocation(), Interface); + } + // Implementation requires the category declaration. + if (auto *Category = D->getCategoryDecl()) { + report(D->getCategoryNameLoc(), Category); + } + return true; + } + + bool VisitObjCCompatibleAliasDecl(ObjCCompatibleAliasDecl *D) { + // An alias declaration requires the underlying class. + if (auto *Aliased = D->getClassInterface()) { + report(D->getLocation(), Aliased); + } + return true; + } + + bool VisitObjCIvarRefExpr(ObjCIvarRefExpr *E) { + if (auto *Ivar = E->getDecl()) { + report(E->getLocation(), Ivar); + } + return true; + } }; } // namespace diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp index 3487f24f2af8f..6bf0bde9cfa10 100644 --- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp @@ -41,8 +41,9 @@ using testing::ElementsAre; // Referencing: int x = ^foo(); // There must be exactly one referencing location marked. // Returns target decls. -std::vector<Decl::Kind> testWalk(llvm::StringRef TargetCode, - llvm::StringRef ReferencingCode) { +std::vector<Decl::Kind> +testWalk(llvm::StringRef TargetCode, llvm::StringRef ReferencingCode, + std::vector<std::string> ExtraArgs = {"-std=c++20"}) { llvm::Annotations Target(TargetCode); llvm::Annotations Referencing(ReferencingCode); @@ -50,7 +51,8 @@ std::vector<Decl::Kind> testWalk(llvm::StringRef TargetCode, Inputs.ExtraFiles["target.h"] = Target.code().str(); Inputs.ExtraArgs.push_back("-include"); Inputs.ExtraArgs.push_back("target.h"); - Inputs.ExtraArgs.push_back("-std=c++20"); + for (const auto &Arg : ExtraArgs) + Inputs.ExtraArgs.push_back(Arg); TestAST AST(Inputs); const auto &SM = AST.sourceManager(); @@ -576,5 +578,293 @@ TEST(WalkAST, CleanupAttr) { "void foo() { __attribute__((__cleanup__(^freep))) char* x = 0; }"); } +TEST(WalkAST, ObjCInterfaceTypeLoc) { + testWalk(R"objc( + @interface $explicit^MyClass + @end + )objc", + R"objc( + void test() { + ^MyClass *obj; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCImplementationDeclDependsOnInterface) { + testWalk(R"objc( + @interface $explicit^MyClass + @end + )objc", + R"objc( + @implementation ^MyClass + @end + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCMessageExprSelectorLoc) { + testWalk(R"objc( + @interface MyClass + $explicit^- (void)doSomething; + @end + )objc", + R"objc( + void test(MyClass *obj) { + [obj ^doSomething]; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCMessageExprClassReceiver) { + testWalk(R"objc( + @interface $explicit^MyClass + + (void)classMethod; + @end + )objc", + R"objc( + void test() { + [^MyClass classMethod]; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCPropertyRefExprExplicit) { + testWalk(R"objc( + @interface MyClass + @property(nonatomic) int $explicit^foo; + @end + )objc", + R"objc( + void test(MyClass *obj) { + int x = obj.^foo; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCPropertyRefExprImplicitGetter) { + testWalk(R"objc( + @interface MyClass + $explicit^- (int)foo; + @end + )objc", + R"objc( + void test(MyClass *obj) { + int x = obj.^foo; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCPropertyRefExprImplicitSetter) { + testWalk(R"objc( + @interface MyClass + $explicit^- (void)setFoo:(int)val; + @end + )objc", + R"objc( + void test(MyClass *obj) { + obj.^foo = 42; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCPropertyRefExprExplicitSetter) { + testWalk(R"objc( + @interface MyClass + @property(nonatomic) int $explicit^foo; + @end + )objc", + R"objc( + void test(MyClass *obj) { + obj.^foo = 42; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCPropertyRefExprProtocol) { + testWalk(R"objc( + @protocol MyProtocol + @property(nonatomic) int $explicit^foo; + @end + )objc", + R"objc( + void test(id<MyProtocol> obj) { + int x = obj.^foo; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCProtocolInType) { + testWalk(R"objc( + @protocol $explicit^MyProtocol + @end + )objc", + R"objc( + void test() { + id<^MyProtocol> obj; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCProtocolInClassInterface) { + testWalk(R"objc( + @protocol $explicit^MyProtocol + @end + )objc", + R"objc( + @interface MyClass <^MyProtocol> + @end + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCProtocolInProtocolInheritance) { + testWalk(R"objc( + @protocol $explicit^ParentProtocol + @end + )objc", + R"objc( + @protocol MyProtocol <^ParentProtocol> + @end + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCProtocolExpr) { + testWalk(R"objc( + @protocol $explicit^MyProtocol + @end + )objc", + R"objc( + void test() { + Protocol* p = @protocol(^MyProtocol); + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCCategoryDeclDependsOnInterface) { + testWalk(R"objc( + @interface $explicit^MyClass + @end + )objc", + R"objc( + @interface ^MyClass (Category) + @end + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCCategoryImplDependsOnInterface) { + testWalk(R"objc( + @interface $explicit^MyClass + @end + )objc", + R"objc( + @interface MyClass (Category) + @end + @implementation ^MyClass (Category) + @end + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCCategoryImplDependsOnCategoryDecl) { + testWalk(R"objc( + @interface MyClass + @end + @interface $explicit^MyClass (Category) + @end + )objc", + R"objc( + @implementation MyClass (^Category) + @end + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCImplicitCastToProtocolConformingCategory) { + testWalk(R"objc( + @protocol MyProtocol + @end + @interface MyClass + @end + @interface $implicit^MyClass (MyCategory) <MyProtocol> + @end + )objc", + R"objc( + void test(MyClass *obj) { + id<MyProtocol> p = ^obj; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCCompatibleAliasDecl) { + testWalk(R"objc( + @interface $explicit^MyClass + @end + )objc", + R"objc( + ^@compatibility_alias AliasName MyClass; + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCCompatibleAliasUsage) { + testWalk(R"objc( + @interface $explicit^MyClass + @end + @compatibility_alias AliasName MyClass; + )objc", + R"objc( + void test() { + ^AliasName *obj; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCIvarRefExprExplicit) { + testWalk(R"objc( + @interface MyClass { + @public + int $explicit^foo; + } + @end + )objc", + R"objc( + void test(MyClass *obj) { + int x = obj->^foo; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCIvarRefExprFree) { + testWalk(R"objc( + @interface MyClass { + int $explicit^foo; + } + @end + )objc", + R"objc( + @implementation MyClass + - (void)test { + int x = ^foo; + } + @end + )objc", + {"-x", "objective-c"}); +} + } // namespace } // namespace clang::include_cleaner _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
