https://github.com/dmaclach updated https://github.com/llvm/llvm-project/pull/216158
>From 58772e7819096f7e4723922186810acb60b22081 Mon Sep 17 00:00:00 2001 From: Dave MacLachlan <[email protected]> Date: Thu, 13 Aug 2026 12:28:09 -0700 Subject: [PATCH 1/3] [include-cleaner]Support toll-free bridged casts in include-cleaner. Handle `CK_CPointerToObjCPointerCast` in `WalkAST` to report implicit references to the destination Objective-C interface and its protocols. This ensures that toll-free bridging casts from C pointers to Objective-C pointers are correctly tracked. Also adds unit tests for various bridged cast types. --- .../include-cleaner/lib/WalkAST.cpp | 8 +- .../include-cleaner/unittests/WalkASTTest.cpp | 88 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp index 7d15f96405903..3587e2334de93 100644 --- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp +++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp @@ -15,6 +15,7 @@ #include "clang/AST/DeclTemplate.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/OperationKinds.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/TemplateBase.h" #include "clang/AST/TemplateName.h" @@ -482,13 +483,18 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> { bool VisitCastExpr(CastExpr *E) { // Handle implicit or explicit casts between Objective-C object pointers // aimed towards protocol-qualification (e.g., `ClassName *` to - // `id<Proto>`). + // `id<Proto>`), as well as toll-free bridged C-pointer-to-ObjC casts. QualType SourceType = E->getSubExpr()->getType(); QualType DestType = E->getType(); const auto *SrcPtr = SourceType->getAs<ObjCObjectPointerType>(); const auto *DestPtr = DestType->getAs<ObjCObjectPointerType>(); + if (E->getCastKind() == CK_CPointerToObjCPointerCast) { + if (DestPtr && DestPtr->getInterfaceDecl()) + report(E->getExprLoc(), DestPtr->getInterfaceDecl(), RefType::Implicit); + } + // If we're casting from a known class pointer to protocol conformance. if (SrcPtr && DestPtr && SrcPtr->getInterfaceDecl()) { const ObjCInterfaceDecl *Class = SrcPtr->getInterfaceDecl(); diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp index cf9a5a365edb6..418aafe6ec76e 100644 --- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp @@ -1164,5 +1164,93 @@ TEST(WalkAST, ObjCIvarRefExprFree) { {"-x", "objective-c"}); } +TEST(WalkAST, ObjCBridgedCastExprToObjC) { + testWalk(R"objc( + typedef const struct __CFString *CFStringRef; + @interface $explicit^NSString + @end + )objc", + R"objc( + void test(CFStringRef cf) { + NSString *s = (__bridge ^NSString *)cf; + } + )objc", + {"-x", "objective-c", "-fobjc-arc"}); +} + +TEST(WalkAST, ObjCBridgedCastExprToCF) { + testWalk(R"objc( + typedef const struct __CFString * $explicit^CFStringRef; + @interface NSString + @end + )objc", + R"objc( + void test(NSString *s) { + CFStringRef cf = (__bridge ^CFStringRef)s; + } + )objc", + {"-x", "objective-c", "-fobjc-arc"}); +} + +TEST(WalkAST, ObjCBridgedCastExprBridgeTransfer) { + testWalk(R"objc( + typedef const struct __CFString *CFStringRef; + @interface $explicit^NSString + @end + )objc", + R"objc( + void test(CFStringRef cf) { + NSString *s = (__bridge_transfer ^NSString *)cf; + } + )objc", + {"-x", "objective-c", "-fobjc-arc"}); +} + +TEST(WalkAST, ObjCBridgedCastExprBridgeRetained) { + testWalk(R"objc( + typedef const struct __CFString * $explicit^CFStringRef; + @interface NSString + @end + )objc", + R"objc( + void test(NSString *s) { + CFStringRef cf = (__bridge_retained ^CFStringRef)s; + } + )objc", + {"-x", "objective-c", "-fobjc-arc"}); +} + +TEST(WalkAST, ObjCTollFreeBridgeCStyleCast) { + testWalk(R"objc( + typedef const struct __attribute__((objc_bridge(NSString))) __CFString * CFStringRef; + @interface $explicit^NSString + @end + )objc", + R"objc( + void test(CFStringRef cf) { + NSString *s = (^NSString *)cf; + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCBridgedCastExprToProtocol) { + // Note this test case is handled by TraverseObjCProtocolLoc instead of + // VisitCastExpr. + // It is here for completeness. + testWalk(R"objc( + typedef const struct __CFString *CFStringRef; + @protocol $explicit^MyProtocol + - (void)doSomething; + @end + )objc", + R"objc( + void test(CFStringRef cf) { + id<MyProtocol> p = (__bridge id<^MyProtocol>)cf; + } + )objc", + {"-x", "objective-c", "-fobjc-arc"}); +} + } // namespace } // namespace clang::include_cleaner >From a2291482cb08b696e629f74f179e9d3063a5caa5 Mon Sep 17 00:00:00 2001 From: Dave MacLachlan <[email protected]> Date: Mon, 24 Aug 2026 10:47:14 -0700 Subject: [PATCH 2/3] Added test that I missed copying in. Add comment to be clear what change implements. --- .../include-cleaner/lib/WalkAST.cpp | 1 + .../include-cleaner/unittests/WalkASTTest.cpp | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp index 3587e2334de93..10a5c5fda7100 100644 --- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp +++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp @@ -490,6 +490,7 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> { const auto *SrcPtr = SourceType->getAs<ObjCObjectPointerType>(); const auto *DestPtr = DestType->getAs<ObjCObjectPointerType>(); + // Handles non-arc CPointer to ObjCPointer casts. if (E->getCastKind() == CK_CPointerToObjCPointerCast) { if (DestPtr && DestPtr->getInterfaceDecl()) report(E->getExprLoc(), DestPtr->getInterfaceDecl(), RefType::Implicit); diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp index 418aafe6ec76e..243664a1e7c05 100644 --- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp @@ -1222,7 +1222,8 @@ TEST(WalkAST, ObjCBridgedCastExprBridgeRetained) { TEST(WalkAST, ObjCTollFreeBridgeCStyleCast) { testWalk(R"objc( - typedef const struct __attribute__((objc_bridge(NSString))) __CFString * CFStringRef; + typedef const struct __attribute__((objc_bridge(NSString))) + __CFString * CFStringRef; @interface $explicit^NSString @end )objc", @@ -1252,5 +1253,18 @@ TEST(WalkAST, ObjCBridgedCastExprToProtocol) { {"-x", "objective-c", "-fobjc-arc"}); } +TEST(WalkAST, ObjCImplicitPointerCast) { + testWalk(R"objc( + @interface $implicit^NSString + @end + )objc", + R"objc( + NSString *foo(void *p) { + return ^p; + } + )objc", + {"-x", "objective-c"}); +} + } // namespace } // namespace clang::include_cleaner >From cbaff9c0293209860bb5d1532928c729de9e4e20 Mon Sep 17 00:00:00 2001 From: Dave MacLachlan <[email protected]> Date: Mon, 24 Aug 2026 12:08:49 -0700 Subject: [PATCH 3/3] - Add missing test cases. - Add missing cast from id - Restructure cast function to reduce indentation with some fast returns. --- .../include-cleaner/lib/WalkAST.cpp | 90 +++++++++---------- .../include-cleaner/unittests/WalkASTTest.cpp | 21 ++++- 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp index 10a5c5fda7100..56c85761132cf 100644 --- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp +++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp @@ -483,59 +483,57 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> { bool VisitCastExpr(CastExpr *E) { // Handle implicit or explicit casts between Objective-C object pointers // aimed towards protocol-qualification (e.g., `ClassName *` to - // `id<Proto>`), as well as toll-free bridged C-pointer-to-ObjC casts. - QualType SourceType = E->getSubExpr()->getType(); - QualType DestType = E->getType(); + // `id<Proto>`), as well as C-pointer-to-ObjC and id-to-ObjC pointer casts. + const auto *DestPtr = E->getType()->getAs<ObjCObjectPointerType>(); - const auto *SrcPtr = SourceType->getAs<ObjCObjectPointerType>(); - const auto *DestPtr = DestType->getAs<ObjCObjectPointerType>(); + if (!DestPtr) + return true; - // Handles non-arc CPointer to ObjCPointer casts. - if (E->getCastKind() == CK_CPointerToObjCPointerCast) { - if (DestPtr && DestPtr->getInterfaceDecl()) - report(E->getExprLoc(), DestPtr->getInterfaceDecl(), RefType::Implicit); - } + const auto *SrcPtr = + E->getSubExpr()->getType()->getAs<ObjCObjectPointerType>(); + + // Handles non-arc CPointer to ObjCPointer and id to ObjCPointer casts. + if (isa<ImplicitCastExpr>(E) && + (E->getCastKind() == CK_CPointerToObjCPointerCast || + (SrcPtr && SrcPtr->isObjCIdType()))) + report(E->getExprLoc(), DestPtr->getInterfaceDecl(), RefType::Implicit); + + if (!SrcPtr) + return true; // 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; + const ObjCInterfaceDecl *Class = SrcPtr->getInterfaceDecl(); + if (!Class) + return true; - // 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); - } - } + 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 (auto *PI : Current->protocols()) { + if (Ctx.ProtocolCompatibleWithProtocol(Proto, PI)) { + ConformsDirectly = true; + break; } - Current = Current->getSuperClass(); } + // 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 (auto *Cat : Current->visible_categories()) + for (auto *PI : Cat->protocols()) + if (Ctx.ProtocolCompatibleWithProtocol(Proto, PI)) + report(E->getExprLoc(), Cat, getCategoryRefType(Cat)); + Current = Current->getSuperClass(); } } return true; diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp index 243664a1e7c05..6f2b0c1bb525f 100644 --- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp @@ -1253,14 +1253,29 @@ TEST(WalkAST, ObjCBridgedCastExprToProtocol) { {"-x", "objective-c", "-fobjc-arc"}); } -TEST(WalkAST, ObjCImplicitPointerCast) { +TEST(WalkAST, ObjCImplicitVoidPointerCast) { testWalk(R"objc( @interface $implicit^NSString @end + void cast(NSString *p); )objc", R"objc( - NSString *foo(void *p) { - return ^p; + void foo(void *p) { + cast(^p); + } + )objc", + {"-x", "objective-c"}); +} + +TEST(WalkAST, ObjCImplicitIdPointerCast) { + testWalk(R"objc( + @interface $implicit^NSString + @end + void cast(NSString *p); + )objc", + R"objc( + void foo(id p) { + cast(^p); } )objc", {"-x", "objective-c"}); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
