[clang] cb1642b - [Webkit Checkers][SaferCpp] Detect implicit `id`-to-specific-type casts in MemoryUnsafeCastChecker (#213113)

via cfe-commits cfe-commits at lists.llvm.org
Fri Jul 31 08:36:22 PDT 2026


Author: Rashmi Mudduluru
Date: 2026-07-31T08:36:16-07:00
New Revision: cb1642b6f9e458bf8ff5d2c41e22a7f7cf6ee419

URL: https://github.com/llvm/llvm-project/commit/cb1642b6f9e458bf8ff5d2c41e22a7f7cf6ee419
DIFF: https://github.com/llvm/llvm-project/commit/cb1642b6f9e458bf8ff5d2c41e22a7f7cf6ee419.diff

LOG: [Webkit Checkers][SaferCpp] Detect implicit `id`-to-specific-type casts in MemoryUnsafeCastChecker (#213113)

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

Added: 
    

Modified: 
    clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
    clang/test/Analysis/Checkers/WebKit/memory-unsafe-cast.mm

Removed: 
    


################################################################################
diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
index f5e8247d17179..acd54ef137314 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/MemoryUnsafeCastChecker.cpp
@@ -54,6 +54,7 @@ static void emitDiagnostics(const BoundNodes &Nodes, BugReporter &BR,
                                BR.getSourceManager());
   auto Report = std::make_unique<BasicBugReport>(BT, OS.str(), BSLoc);
   Report->addRange(CE->getSourceRange());
+  Report->setDeclWithIssue(ADC->getDecl());
   BR.emitReport(std::move(Report));
 }
 
@@ -74,21 +75,36 @@ static void emitDiagnosticsUnrelated(const BoundNodes &Nodes, BugReporter &BR,
                                BR.getSourceManager());
   auto Report = std::make_unique<BasicBugReport>(BT, OS.str(), BSLoc);
   Report->addRange(CE->getSourceRange());
+  Report->setDeclWithIssue(ADC->getDecl());
   BR.emitReport(std::move(Report));
 }
 
-namespace clang {
-namespace ast_matchers {
-AST_MATCHER_P(StringLiteral, mentionsBoundType, std::string, BindingID) {
-  return Builder->removeBindings([this, &Node](const BoundNodesMap &Nodes) {
-    const auto &BN = Nodes.getNode(this->BindingID);
-    if (const auto *ND = BN.get<NamedDecl>()) {
-      return ND->getName() != Node.getString();
-    }
-    return true;
-  });
+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());
+  Report->setDeclWithIssue(ADC->getDecl());
+  BR.emitReport(std::move(Report));
 }
 
+namespace {
+using BoundNodesMap = ::clang::ast_matchers::internal::BoundNodesMap;
+
+// 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
@@ -111,8 +127,7 @@ AST_MATCHER_P2(Expr, isCRTPCast, std::string, BaseID, std::string, DerivedID) {
     return true;
   });
 }
-} // end namespace ast_matchers
-} // end namespace clang
+} // end anonymous namespace
 
 static decltype(auto) hasTypePointingTo(DeclarationMatcher DeclM) {
   return hasType(pointerType(pointee(hasDeclaration(DeclM))));
@@ -239,6 +254,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'}}
+}


        


More information about the cfe-commits mailing list