[clang-tools-extra] [include-cleaner] Support Objective-C toll-free bridged casts (PR #216158)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Aug 24 12:10:19 PDT 2026
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 <dmaclach at gmail.com>
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 <dmaclach at gmail.com>
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 <dmaclach at gmail.com>
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"});
More information about the cfe-commits
mailing list