[clang] [alpha.webkit.UnretainedLocalVarsChecker] Treat a raw pointer collection of a fast enumeration as the origin of its element (PR #234682)

Ryosuke Niwa via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 22 17:52:27 PDT 2026


https://github.com/rniwa updated https://github.com/llvm/llvm-project/pull/234682

>From 20e0034dad5ef6c75808a7b304c955a3a62df726 Mon Sep 17 00:00:00 2001
From: Ryosuke Niwa <rniwa at webkit.org>
Date: Thu, 22 Oct 2026 09:27:45 -0700
Subject: [PATCH] [alpha.webkit.UnretainedLocalVarsChecker] Treat a raw pointer
 collection of a fast enumeration as the origin of its element

alpha.webkit.UnretainedLocalVarsChecker still reported the element variable
of an Objective-C fast enumeration when the collection was a raw pointer
local variable or parameter, as in "NSArray *array = obj.array;" followed by
"for (T *x in array)", even though the variable itself isn't reported. This
includes a nested fast enumeration whose collection is the element of the
enclosing one, and a range-based for loop over an Objective-C collection,
which is a fast enumeration as well.

The collection of a fast enumeration is evaluated before the loop body is
entered and keeps its elements alive. A raw pointer local variable or
parameter holding the collection is checked on its own, so treat it as a
guardian of the element as long as the loop body doesn't mutate it.

Also treat passing the address of an Objective-C object pointer guardian as
a mutation of the guardian as done for other raw pointers.

Authored with Claude Code.
---
 .../WebKit/RawPtrRefLocalVarsChecker.cpp      | 13 ++-
 .../Checkers/WebKit/unretained-local-vars.mm  | 96 +++++++++++++++++++
 2 files changed, 108 insertions(+), 1 deletion(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
index 88c70571d1f179..2e493b0a49758a 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
@@ -149,7 +149,7 @@ struct GuardianVisitor : DynamicRecursiveASTVisitor {
     auto *VarRef = dyn_cast<DeclRefExpr>(Arg);
     if (!VarRef || VarRef->getDecl() != Guardian)
       return false;
-    if (GuardianIsRawPtrOrRef && !Guardian->getType()->isPointerType())
+    if (GuardianIsRawPtrOrRef && !Guardian->getType()->isAnyPointerType())
       return false;
     if (IsAddressOf) {
       if (!ArgType->isPointerType())
@@ -536,6 +536,17 @@ class RawPtrRefLocalVarsChecker
       return false;
 
     QualType GuardianType = MaybeGuardian->getType();
+    // The collection of a fast enumeration is evaluated before the loop body is
+    // entered and keeps its elements alive. A raw pointer local variable or
+    // parameter holding the collection is checked on its own, so it only needs
+    // to be left untouched by the loop body.
+    if (auto *FCS = findObjCForCollectionStmtOfElement(V);
+        FCS && !GuardianType.isNull() && GuardianType->isAnyPointerType() &&
+        (MaybeGuardian->isLocalVarDecl() || isa<ParmVarDecl>(MaybeGuardian))) {
+      GuardianVisitor Visitor(MaybeGuardian, /*GuardianIsRawPtrOrRef=*/true);
+      return Visitor.TraverseStmt(const_cast<Stmt *>(FCS->getBody()));
+    }
+
     if (!GuardianType.isNull()) {
       if (auto *Record = GuardianType->getAsCXXRecordDecl()) {
         if (MaybeGuardian->isLocalVarDecl() &&
diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm
index acec4b36947e23..c2555a14ea2a49 100644
--- a/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm
+++ b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm
@@ -574,9 +574,15 @@ void ns() {
 
 }
 
+ at interface ArrayProvider : NSObject
+- (NSArray *)arrayValue;
+ at end
+
 namespace fast_enumeration {
 
 NSArray *provideArray();
+void provideArrayToOutArgument(NSArray **);
+void consumeString(NSString *);
 
 void iterateLocal() {
   RetainPtr<NSArray> array = provideArray();
@@ -670,8 +676,98 @@ void iterateRawLocal() {
   NSArray *array = provideArray();
   // expected-warning at -1{{Local variable 'array' is a raw pointer to RetainPtr-capable type 'NSArray' [alpha.webkit.UnretainedLocalVarsChecker]}}
   for (SomeObj *obj in array)
+    [obj doWork];
+}
+
+void iterateRawLocalFromMessage(ArrayProvider *provider) {
+  NSArray *array = [provider arrayValue];
+  for (SomeObj *item in array)
+    [item doWork];
+}
+
+void iterateRawLocalFromMessageWithRangeBasedFor(ArrayProvider *provider) {
+  NSArray *array = [provider arrayValue];
+  for (SomeObj *item : array)
+    [item doWork];
+}
+
+void iterateRawLocalFromOutArgument() {
+  NSArray *array = nil;
+  provideArrayToOutArgument(&array);
+  for (SomeObj *obj in array)
+    [obj doWork];
+}
+
+void iterateRawLocalAssignedBeforeLoop(ArrayProvider *provider) {
+  NSArray *array = [provider arrayValue];
+  // expected-warning at -1{{Local variable 'array' is a raw pointer to RetainPtr-capable type 'NSArray' [alpha.webkit.UnretainedLocalVarsChecker]}}
+  array = provideArray();
+  for (SomeObj *item in array)
+    [item doWork];
+}
+
+void iterateRawLocalClearedInBody(ArrayProvider *provider) {
+  NSArray *array = [provider arrayValue];
+  for (SomeObj *item in array) {
+    // expected-warning at -1{{Local variable 'item' is a raw pointer to RetainPtr-capable type 'SomeObj' [alpha.webkit.UnretainedLocalVarsChecker]}}
+    array = nil;
+    [item doWork];
+  }
+}
+
+void iterateRawLocalOverwrittenViaOutArgumentInBody(ArrayProvider *provider) {
+  NSArray *array = [provider arrayValue];
+  for (SomeObj *item in array) {
+    // expected-warning at -1{{Local variable 'item' is a raw pointer to RetainPtr-capable type 'SomeObj' [alpha.webkit.UnretainedLocalVarsChecker]}}
+    provideArrayToOutArgument(&array);
+    [item doWork];
+  }
+}
+
+void iterateStaticRawLocal() {
+  static NSArray *array = @[ @"foo" ];
+  for (NSString *str in array)
+    consumeString(str);
+}
+
+void iterateParamWithIndirectCallOutsideBody(NSArray *array, void (*handler)()) {
+  handler();
+  for (SomeObj *obj in array)
+    [obj doWork];
+}
+
+void iterateParamOverwrittenViaOutArgumentInBody(NSArray *array) {
+  for (SomeObj *obj in array) {
     // expected-warning at -1{{Local variable 'obj' is a raw pointer to RetainPtr-capable type 'SomeObj' [alpha.webkit.UnretainedLocalVarsChecker]}}
+    provideArrayToOutArgument(&array);
     [obj doWork];
+  }
+}
+
+void iterateNested(NSArray *array) {
+  for (NSArray *inner in array) {
+    for (SomeObj *obj in inner)
+      [obj doWork];
+  }
+}
+
+void iterateNestedInLocal() {
+  RetainPtr<NSArray> array = provideArray();
+  for (NSArray *inner in array.get()) {
+    for (SomeObj *obj in inner)
+      [obj doWork];
+  }
+}
+
+void iterateNestedOuterElementReassignedInInnerBody(NSArray *array) {
+  for (NSArray *inner in array) {
+    // expected-warning at -1{{Local variable 'inner' is a raw pointer to RetainPtr-capable type 'NSArray' [alpha.webkit.UnretainedLocalVarsChecker]}}
+    for (SomeObj *obj in inner) {
+      // expected-warning at -1{{Local variable 'obj' is a raw pointer to RetainPtr-capable type 'SomeObj' [alpha.webkit.UnretainedLocalVarsChecker]}}
+      inner = provideArray();
+      [obj doWork];
+    }
+  }
 }
 
 } // namespace fast_enumeration



More information about the cfe-commits mailing list