[clang] [Webkit Checkers][SaferCpp] Detect implicit `id`-to-specific-type casts in MemoryUnsafeCastChecker (PR #213113)
Rashmi Mudduluru via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 30 12:45:53 PDT 2026
https://github.com/t-rasmud updated https://github.com/llvm/llvm-project/pull/213113
>From 46df8bb595fdb64aefa2d550ea841c037a2bbf03 Mon Sep 17 00:00:00 2001
From: Rashmi Mudduluru <r_mudduluru at apple.com>
Date: Thu, 30 Jul 2026 12:24:56 -0700
Subject: [PATCH 1/3] [Webkit Checkers][SaferCpp] Detect implicit
`id`-to-specific-type casts in MemoryUnsafeCastChecker
Flag arguments of type `id` implicitly converted to a specific
Objective-C pointer type at a call, message send, or constructor
call (e.g. passing `id` where an `NSString *` parameter is
expected). These conversions compile without a visible cast but
throw at runtime if the object is not actually of that type.
rdar://148708396
---
.../WebKit/MemoryUnsafeCastChecker.cpp | 45 ++++++++++++++++++
.../Checkers/WebKit/memory-unsafe-cast.mm | 47 +++++++++++++++++++
2 files changed, 92 insertions(+)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
index f5e8247d17179..0adb3c5480e9d 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
@@ -77,6 +77,25 @@ static void emitDiagnosticsUnrelated(const BoundNodes &Nodes, BugReporter &BR,
BR.emitReport(std::move(Report));
}
+static void emitDiagnosticsIdArg(const BoundNodes &Nodes, BugReporter &BR,
+ AnalysisDeclContext *ADC,
+ const MemoryUnsafeCastChecker *Checker,
+ const BugType &BT) {
+ const auto *CE = Nodes.getNodeAs<CastExpr>(WarnRecordDecl);
+ const NamedDecl *Derived = Nodes.getNodeAs<NamedDecl>(DerivedNode);
+ assert(CE && Derived);
+
+ std::string Diagnostics;
+ llvm::raw_string_ostream OS(Diagnostics);
+ OS << "Unsafe implicit cast from 'id' to specific type '"
+ << Derived->getNameAsString() << "'";
+ PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(),
+ BR.getSourceManager());
+ auto Report = std::make_unique<BasicBugReport>(BT, OS.str(), BSLoc);
+ Report->addRange(CE->getSourceRange());
+ BR.emitReport(std::move(Report));
+}
+
namespace clang {
namespace ast_matchers {
AST_MATCHER_P(StringLiteral, mentionsBoundType, std::string, BindingID) {
@@ -89,6 +108,9 @@ AST_MATCHER_P(StringLiteral, mentionsBoundType, std::string, BindingID) {
});
}
+// Matches the plain `id` type.
+AST_MATCHER(QualType, isObjCIdType) { return Node->isObjCIdType(); }
+
// Matches a cast whose previously-bound BaseID node is a class template
// specialization and whose previously-bound DerivedID node is one of that
// specialization's type template arguments, i.e. the CRTP pattern
@@ -239,6 +261,29 @@ void MemoryUnsafeCastChecker::checkASTCodeBody(const Decl *D,
*D->getBody(), AM.getASTContext());
for (BoundNodes Match : MatchesUnrelatedTypes)
emitDiagnosticsUnrelated(Match, BR, ADC, this, BT);
+
+ // Match an `id`-typed argument implicitly converted to a specific
+ // Objective-C type at a call, message send, or constructor call, e.g.
+ // passing an `id` where an `NSString *` parameter is expected. Such
+ // conversions compile without a visible cast but throw at runtime if the
+ // object is not actually of that type.
+ auto CastArgFromIdToSpecificType =
+ implicitCastExpr(
+ hasCastKind(CK_BitCast),
+ hasSourceExpression(ignoringParenImpCasts(
+ hasType(qualType(isObjCIdType())))),
+ hasType(qualType(hasCanonicalType(objcObjectPointerType(
+ pointee(hasDeclaration(objcInterfaceDecl().bind(DerivedNode))))))))
+ .bind(WarnRecordDecl);
+ auto MatchCallArgFromId =
+ anyOf(callExpr(hasAnyArgument(CastArgFromIdToSpecificType)),
+ cxxConstructExpr(hasAnyArgument(CastArgFromIdToSpecificType)),
+ objcMessageExpr(hasAnyArgument(CastArgFromIdToSpecificType)));
+ auto MatchesCallArgFromId = match(
+ stmt(forEachDescendant(stmt(MatchCallArgFromId))), *D->getBody(),
+ AM.getASTContext());
+ for (BoundNodes Match : MatchesCallArgFromId)
+ emitDiagnosticsIdArg(Match, BR, ADC, this, BT);
}
void ento::registerMemoryUnsafeCastChecker(CheckerManager &Mgr) {
diff --git a/clang/test/Analysis/Checkers/WebKit/memory-unsafe-cast.mm b/clang/test/Analysis/Checkers/WebKit/memory-unsafe-cast.mm
index 55b02b2657230..a2f1c78955206 100644
--- a/clang/test/Analysis/Checkers/WebKit/memory-unsafe-cast.mm
+++ b/clang/test/Analysis/Checkers/WebKit/memory-unsafe-cast.mm
@@ -97,3 +97,50 @@ void fn_cast_01(Base* base) {
auto* d10 = reinterpret_cast<Derived*>((void*)base);
// expected-warning at -1{{Unsafe cast from base type 'Base' to derived type 'Derived'}}
}
+
+void takesNSString(NSString *str);
+
+ at interface IdParamReceiver
+- (void)takeString:(NSString *)str;
+ at end
+
+struct StringWrapper {
+ StringWrapper(NSString *str);
+};
+
+struct String {
+ String(NSString *str);
+};
+
+void test_id_passed_to_specific_type_param(id anId, NSString *str, IdParamReceiver *receiver) {
+ takesNSString(anId);
+ // expected-warning at -1{{Unsafe implicit cast from 'id' to specific type 'NSString'}}
+ [receiver takeString:anId];
+ // expected-warning at -1{{Unsafe implicit cast from 'id' to specific type 'NSString'}}
+ StringWrapper wrapper1(anId);
+ // expected-warning at -1{{Unsafe implicit cast from 'id' to specific type 'NSString'}}
+ StringWrapper wrapper2 { anId };
+ // expected-warning at -1{{Unsafe implicit cast from 'id' to specific type 'NSString'}}
+
+ takesNSString(str); // no warning
+ [receiver takeString:str]; // no warning
+ StringWrapper wrapper3(str); // no warning
+
+ NSString *fixed = checked_objc_cast<NSString>(anId); // no warning
+ takesNSString(fixed); // no warning
+ RetainPtr<NSString> fixedDynamic = dynamic_objc_cast<NSString>(anId); // no warning
+
+ id array = [NSArray arrayWithObjects:0 count:0];
+ String s { array };
+ // expected-warning at -1{{Unsafe implicit cast from 'id' to specific type 'NSString'}}
+}
+
+void takesNonnullNSString(NSString * _Nonnull str);
+void takesNullableNSString(NSString * _Nullable str);
+
+void test_id_passed_with_nullability(id anId) {
+ takesNonnullNSString(anId);
+ // expected-warning at -1{{Unsafe implicit cast from 'id' to specific type 'NSString'}}
+ takesNullableNSString(anId);
+ // expected-warning at -1{{Unsafe implicit cast from 'id' to specific type 'NSString'}}
+}
>From 5a8af1f05468ca06a81488d775f2642ad12526e4 Mon Sep 17 00:00:00 2001
From: Rashmi Mudduluru <r_mudduluru at apple.com>
Date: Thu, 30 Jul 2026 12:36:53 -0700
Subject: [PATCH 2/3] Fix formatting
---
.../Checkers/WebKit/MemoryUnsafeCastChecker.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
index 0adb3c5480e9d..0846df6990e62 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
@@ -279,9 +279,9 @@ void MemoryUnsafeCastChecker::checkASTCodeBody(const Decl *D,
anyOf(callExpr(hasAnyArgument(CastArgFromIdToSpecificType)),
cxxConstructExpr(hasAnyArgument(CastArgFromIdToSpecificType)),
objcMessageExpr(hasAnyArgument(CastArgFromIdToSpecificType)));
- auto MatchesCallArgFromId = match(
- stmt(forEachDescendant(stmt(MatchCallArgFromId))), *D->getBody(),
- AM.getASTContext());
+ auto MatchesCallArgFromId =
+ match(stmt(forEachDescendant(stmt(MatchCallArgFromId))), *D->getBody(),
+ AM.getASTContext());
for (BoundNodes Match : MatchesCallArgFromId)
emitDiagnosticsIdArg(Match, BR, ADC, this, BT);
}
>From 67b48e9fc825af65ff67bd52165ec7a3a8e04dd7 Mon Sep 17 00:00:00 2001
From: Rashmi Mudduluru <r_mudduluru at apple.com>
Date: Thu, 30 Jul 2026 12:45:36 -0700
Subject: [PATCH 3/3] Fix formatting
---
.../Checkers/WebKit/MemoryUnsafeCastChecker.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
index 0846df6990e62..d90adad87acb6 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
@@ -270,10 +270,10 @@ void MemoryUnsafeCastChecker::checkASTCodeBody(const Decl *D,
auto CastArgFromIdToSpecificType =
implicitCastExpr(
hasCastKind(CK_BitCast),
- hasSourceExpression(ignoringParenImpCasts(
- hasType(qualType(isObjCIdType())))),
- hasType(qualType(hasCanonicalType(objcObjectPointerType(
- pointee(hasDeclaration(objcInterfaceDecl().bind(DerivedNode))))))))
+ hasSourceExpression(
+ ignoringParenImpCasts(hasType(qualType(isObjCIdType())))),
+ hasType(qualType(hasCanonicalType(objcObjectPointerType(pointee(
+ hasDeclaration(objcInterfaceDecl().bind(DerivedNode))))))))
.bind(WarnRecordDecl);
auto MatchCallArgFromId =
anyOf(callExpr(hasAnyArgument(CastArgFromIdToSpecificType)),
More information about the cfe-commits
mailing list