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 <[email protected]> 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() { } +@interface ArrayProvider : NSObject +- (NSArray *)arrayValue; +@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@-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@-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@-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@-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@-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@-1{{Local variable 'inner' is a raw pointer to RetainPtr-capable type 'NSArray' [alpha.webkit.UnretainedLocalVarsChecker]}} + for (SomeObj *obj in inner) { + // expected-warning@-1{{Local variable 'obj' is a raw pointer to RetainPtr-capable type 'SomeObj' [alpha.webkit.UnretainedLocalVarsChecker]}} + inner = provideArray(); + [obj doWork]; + } + } } } // namespace fast_enumeration _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
