Skip to content

[alpha.webkit.UnretainedLambdaCapturesChecker] Add a WebKit checker for lambda capturing NS or CF types. #128651

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 9, 2025

Conversation

rniwa
Copy link
Contributor

@rniwa rniwa commented Feb 25, 2025

Add a new WebKit checker for checking that lambda captures of CF types use RetainPtr either when ARC is disabled or enabled, and those of NS types use RetainPtr when ARC is disabled.

…or lambda capturing NS or CF types.

Add a new WebKit checker for checking that lambda captures of CF types use RetainPtr either when ARC is
disabled or enabled, and those of NS types use RetainPtr when ARC is disabled.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Feb 25, 2025
@llvmbot
Copy link
Member

llvmbot commented Feb 25, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-static-analyzer-1

Author: Ryosuke Niwa (rniwa)

Changes

Add a new WebKit checker for checking that lambda captures of CF types use RetainPtr either when ARC is disabled or enabled, and those of NS types use RetainPtr when ARC is disabled.


Patch is 45.38 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/128651.diff

10 Files Affected:

  • (modified) clang/docs/analyzer/checkers.rst (+12)
  • (modified) clang/include/clang/StaticAnalyzer/Checkers/Checkers.td (+4)
  • (modified) clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt (+2-2)
  • (renamed) clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp (+112-24)
  • (modified) clang/test/Analysis/Checkers/WebKit/objc-mock-types.h (+132-14)
  • (modified) clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures-decl-protects-this-crash.cpp (+2-2)
  • (modified) clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp (+17-17)
  • (added) clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures-arc.mm (+273)
  • (added) clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures.mm (+296)
  • (modified) llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn (+1-1)
diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst
index c1eedb33e74d2..57733d45167f5 100644
--- a/clang/docs/analyzer/checkers.rst
+++ b/clang/docs/analyzer/checkers.rst
@@ -3487,6 +3487,18 @@ Raw pointers and references to an object which supports CheckedPtr or CheckedRef
 
 See `WebKit Guidelines for Safer C++ Programming <https://github.com/WebKit/WebKit/wiki/Safer-CPP-Guidelines>`_ for details.
 
+alpha.webkit.UnretainedLambdaCapturesChecker
+""""""""""""""""""""""""""""""""""""""""""""
+Raw pointers and references to unretained types can't be captured in lambdas. Only RetainPtr is allowed.
+
+.. code-block:: cpp
+
+ void foo(NSObject *a, NSObject *b) {
+   [&, a](){ // warn about 'a'
+     do_something(b); // warn about 'b'
+   };
+ };
+
 .. _alpha-webkit-UncountedCallArgsChecker:
 
 alpha.webkit.UncountedCallArgsChecker
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 410f841630660..06aa083671b7a 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -1766,6 +1766,10 @@ def NoUncheckedPtrMemberChecker : Checker<"NoUncheckedPtrMemberChecker">,
   HelpText<"Check for no unchecked member variables.">,
   Documentation<HasDocumentation>;
 
+def UnretainedLambdaCapturesChecker : Checker<"UnretainedLambdaCapturesChecker">,
+  HelpText<"Check unretained lambda captures.">,
+  Documentation<HasDocumentation>;
+
 def UncountedCallArgsChecker : Checker<"UncountedCallArgsChecker">,
   HelpText<"Check uncounted call arguments.">,
   Documentation<HasDocumentation>;
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 5910043440987..9aac200cd7370 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -128,14 +128,14 @@ add_clang_library(clangStaticAnalyzerCheckers
   VLASizeChecker.cpp
   ValistChecker.cpp
   VirtualCallChecker.cpp
-  WebKit/RawPtrRefMemberChecker.cpp
   WebKit/ASTUtils.cpp
   WebKit/MemoryUnsafeCastChecker.cpp
   WebKit/PtrTypesSemantics.cpp
   WebKit/RefCntblBaseVirtualDtorChecker.cpp
   WebKit/RawPtrRefCallArgsChecker.cpp
-  WebKit/UncountedLambdaCapturesChecker.cpp
+  WebKit/RawPtrRefLambdaCapturesChecker.cpp
   WebKit/RawPtrRefLocalVarsChecker.cpp
+  WebKit/RawPtrRefMemberChecker.cpp
 
   LINK_LIBS
   clangAST
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp
similarity index 77%
rename from clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp
rename to clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp
index 506442f352288..314b3dc72c8e5 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp
@@ -21,15 +21,23 @@ using namespace clang;
 using namespace ento;
 
 namespace {
-class UncountedLambdaCapturesChecker
+class RawPtrRefLambdaCapturesChecker
     : public Checker<check::ASTDecl<TranslationUnitDecl>> {
 private:
-  BugType Bug{this, "Lambda capture of uncounted variable",
-              "WebKit coding guidelines"};
+  BugType Bug;
   mutable BugReporter *BR = nullptr;
   TrivialFunctionAnalysis TFA;
 
+protected:
+  mutable std::optional<RetainTypeChecker> RTC;
+
 public:
+  RawPtrRefLambdaCapturesChecker(const char *description)
+      : Bug(this, description, "WebKit coding guidelines") {}
+
+  virtual std::optional<bool> isUnsafePtr(QualType) const = 0;
+  virtual const char *ptrKind(QualType QT) const = 0;
+
   void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
                     BugReporter &BRArg) const {
     BR = &BRArg;
@@ -38,7 +46,7 @@ class UncountedLambdaCapturesChecker
     // visit template instantiations or lambda classes. We
     // want to visit those, so we make our own RecursiveASTVisitor.
     struct LocalVisitor : DynamicRecursiveASTVisitor {
-      const UncountedLambdaCapturesChecker *Checker;
+      const RawPtrRefLambdaCapturesChecker *Checker;
       llvm::DenseSet<const DeclRefExpr *> DeclRefExprsToIgnore;
       llvm::DenseSet<const LambdaExpr *> LambdasToIgnore;
       llvm::DenseSet<const ValueDecl *> ProtectedThisDecls;
@@ -46,7 +54,7 @@ class UncountedLambdaCapturesChecker
 
       QualType ClsType;
 
-      explicit LocalVisitor(const UncountedLambdaCapturesChecker *Checker)
+      explicit LocalVisitor(const RawPtrRefLambdaCapturesChecker *Checker)
           : Checker(Checker) {
         assert(Checker);
         ShouldVisitTemplateInstantiations = true;
@@ -60,16 +68,23 @@ class UncountedLambdaCapturesChecker
         return DynamicRecursiveASTVisitor::TraverseCXXMethodDecl(CXXMD);
       }
 
+      bool VisitTypedefDecl(TypedefDecl *TD) override {
+        if (Checker->RTC)
+          Checker->RTC->visitTypedef(TD);
+        return true;
+      }
+
       bool shouldCheckThis() {
-        auto result =
-            !ClsType.isNull() ? isUnsafePtr(ClsType, false) : std::nullopt;
+        auto result = !ClsType.isNull() ?
+            Checker->isUnsafePtr(ClsType) : std::nullopt;
         return result && *result;
       }
 
       bool VisitLambdaExpr(LambdaExpr *L) override {
         if (LambdasToIgnore.contains(L))
           return true;
-        Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L));
+        Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L),
+                                 ClsType);
         return true;
       }
 
@@ -97,7 +112,8 @@ class UncountedLambdaCapturesChecker
         if (!L)
           return true;
         LambdasToIgnore.insert(L);
-        Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L));
+        Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L),
+                                 ClsType);
         return true;
       }
 
@@ -123,7 +139,8 @@ class UncountedLambdaCapturesChecker
               LambdasToIgnore.insert(L);
               if (!Param->hasAttr<NoEscapeAttr>())
                 Checker->visitLambdaExpr(L, shouldCheckThis() &&
-                                                !hasProtectedThis(L));
+                                                !hasProtectedThis(L),
+                                         ClsType);
             }
             ++ArgIndex;
           }
@@ -144,7 +161,8 @@ class UncountedLambdaCapturesChecker
               LambdasToIgnore.insert(L);
               if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape)
                 Checker->visitLambdaExpr(L, shouldCheckThis() &&
-                                                !hasProtectedThis(L));
+                                                !hasProtectedThis(L),
+                                         ClsType);
             }
             ++ArgIndex;
           }
@@ -169,14 +187,22 @@ class UncountedLambdaCapturesChecker
         auto *CtorArg = CE->getArg(0)->IgnoreParenCasts();
         if (!CtorArg)
           return nullptr;
-        if (auto *Lambda = dyn_cast<LambdaExpr>(CtorArg)) {
+        auto *InnerCE = dyn_cast_or_null<CXXConstructExpr>(CtorArg);
+        if (InnerCE && InnerCE->getNumArgs())
+          CtorArg = InnerCE->getArg(0)->IgnoreParenCasts();
+        auto updateIgnoreList = [&] {
           ConstructToIgnore.insert(CE);
+          if (InnerCE)
+            ConstructToIgnore.insert(InnerCE);
+        };
+        if (auto *Lambda = dyn_cast<LambdaExpr>(CtorArg)) {
+          updateIgnoreList();
           return Lambda;
         }
         if (auto *TempExpr = dyn_cast<CXXBindTemporaryExpr>(CtorArg)) {
           E = TempExpr->getSubExpr()->IgnoreParenCasts();
           if (auto *Lambda = dyn_cast<LambdaExpr>(E)) {
-            ConstructToIgnore.insert(CE);
+            updateIgnoreList();
             return Lambda;
           }
         }
@@ -189,10 +215,14 @@ class UncountedLambdaCapturesChecker
         auto *Init = VD->getInit();
         if (!Init)
           return nullptr;
+        if (auto *Lambda = dyn_cast<LambdaExpr>(Init)) {
+          updateIgnoreList();
+          return Lambda;
+        }
         TempExpr = dyn_cast<CXXBindTemporaryExpr>(Init->IgnoreParenCasts());
         if (!TempExpr)
           return nullptr;
-        ConstructToIgnore.insert(CE);
+        updateIgnoreList();
         return dyn_cast_or_null<LambdaExpr>(TempExpr->getSubExpr());
       }
 
@@ -226,7 +256,7 @@ class UncountedLambdaCapturesChecker
         DeclRefExprsToIgnore.insert(ArgRef);
         LambdasToIgnore.insert(L);
         Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L),
-                                 /* ignoreParamVarDecl */ true);
+                                 ClsType, /* ignoreParamVarDecl */ true);
       }
 
       bool hasProtectedThis(LambdaExpr *L) {
@@ -293,10 +323,13 @@ class UncountedLambdaCapturesChecker
     };
 
     LocalVisitor visitor(this);
+    if (RTC)
+      RTC->visitTranslationUnitDecl(TUD);
     visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
   }
 
   void visitLambdaExpr(LambdaExpr *L, bool shouldCheckThis,
+                       const QualType T,
                        bool ignoreParamVarDecl = false) const {
     if (TFA.isTrivial(L->getBody()))
       return;
@@ -306,13 +339,13 @@ class UncountedLambdaCapturesChecker
         if (ignoreParamVarDecl && isa<ParmVarDecl>(CapturedVar))
           continue;
         QualType CapturedVarQualType = CapturedVar->getType();
-        auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType(), false);
+        auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType());
         if (IsUncountedPtr && *IsUncountedPtr)
           reportBug(C, CapturedVar, CapturedVarQualType);
       } else if (C.capturesThis() && shouldCheckThis) {
         if (ignoreParamVarDecl) // this is always a parameter to this function.
           continue;
-        reportBugOnThisPtr(C);
+        reportBugOnThisPtr(C, T);
       }
     }
   }
@@ -320,6 +353,9 @@ class UncountedLambdaCapturesChecker
   void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar,
                  const QualType T) const {
     assert(CapturedVar);
+    
+    if (isa<ImplicitParamDecl>(CapturedVar) && !Capture.getLocation().isValid())
+      return; // Ignore implicit captruing of self.
 
     SmallString<100> Buf;
     llvm::raw_svector_ostream Os(Buf);
@@ -329,22 +365,22 @@ class UncountedLambdaCapturesChecker
     } else {
       Os << "Implicitly captured ";
     }
-    if (T->isPointerType()) {
+    if (isa<PointerType>(T) || isa<ObjCObjectPointerType>(T)) {
       Os << "raw-pointer ";
     } else {
-      assert(T->isReferenceType());
       Os << "reference ";
     }
 
-    printQuotedQualifiedName(Os, Capture.getCapturedVar());
-    Os << " to ref-counted type or CheckedPtr-capable type is unsafe.";
+    printQuotedQualifiedName(Os, CapturedVar);
+    Os << " to " << ptrKind(T) << " type is unsafe.";
 
     PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
     BR->emitReport(std::move(Report));
   }
 
-  void reportBugOnThisPtr(const LambdaCapture &Capture) const {
+  void reportBugOnThisPtr(const LambdaCapture &Capture,
+                          const QualType T) const {
     SmallString<100> Buf;
     llvm::raw_svector_ostream Os(Buf);
 
@@ -354,14 +390,57 @@ class UncountedLambdaCapturesChecker
       Os << "Implicitly captured ";
     }
 
-    Os << "raw-pointer 'this' to ref-counted type or CheckedPtr-capable type "
-          "is unsafe.";
+    Os << "raw-pointer 'this' to " << ptrKind(T) << " type is unsafe.";
 
     PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
     BR->emitReport(std::move(Report));
   }
 };
+
+class UncountedLambdaCapturesChecker : public RawPtrRefLambdaCapturesChecker {
+public:
+  UncountedLambdaCapturesChecker()
+      : RawPtrRefLambdaCapturesChecker("Lambda capture of uncounted or "
+                                       "unchecked variable") {}
+
+  std::optional<bool> isUnsafePtr(QualType QT) const final {
+    auto result1 = isUncountedPtr(QT);
+    auto result2 = isUncheckedPtr(QT);
+    if (result1 && *result1)
+      return true;
+    if (result2 && *result2)
+      return true;
+    if (result1)
+      return *result1;
+    return result2;
+  }
+
+  const char *ptrKind(QualType QT) const final {
+    if (isUncounted(QT))
+      return "uncounted";
+    return "unchecked";
+  }
+
+};
+
+class UnretainedLambdaCapturesChecker : public RawPtrRefLambdaCapturesChecker {
+public:
+  UnretainedLambdaCapturesChecker()
+      : RawPtrRefLambdaCapturesChecker("Lambda capture of unretained "
+                                       "variables") {
+    RTC = RetainTypeChecker();
+  }
+
+  std::optional<bool> isUnsafePtr(QualType QT) const final {
+    return RTC->isUnretained(QT);
+  }
+
+  const char *ptrKind(QualType QT) const final {
+    return "unretained";
+  }
+};
+
 } // namespace
 
 void ento::registerUncountedLambdaCapturesChecker(CheckerManager &Mgr) {
@@ -372,3 +451,12 @@ bool ento::shouldRegisterUncountedLambdaCapturesChecker(
     const CheckerManager &mgr) {
   return true;
 }
+
+void ento::registerUnretainedLambdaCapturesChecker(CheckerManager &Mgr) {
+  Mgr.registerChecker<UnretainedLambdaCapturesChecker>();
+}
+
+bool ento::shouldRegisterUnretainedLambdaCapturesChecker(
+    const CheckerManager &mgr) {
+  return true;
+}
diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h
index 7bb33bcb6cf44..9b13810d0c5c9 100644
--- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h
+++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h
@@ -1,30 +1,94 @@
 @class NSString;
 @class NSArray;
 @class NSMutableArray;
+#define nil ((id)0)
 #define CF_BRIDGED_TYPE(T) __attribute__((objc_bridge(T)))
+#define CF_BRIDGED_MUTABLE_TYPE(T) __attribute__((objc_bridge_mutable(T)))
 typedef CF_BRIDGED_TYPE(id) void * CFTypeRef;
+typedef signed char BOOL;
 typedef signed long CFIndex;
-typedef const struct __CFAllocator * CFAllocatorRef;
+typedef unsigned long NSUInteger;
+typedef const struct CF_BRIDGED_TYPE(id) __CFAllocator * CFAllocatorRef;
 typedef const struct CF_BRIDGED_TYPE(NSString) __CFString * CFStringRef;
 typedef const struct CF_BRIDGED_TYPE(NSArray) __CFArray * CFArrayRef;
-typedef struct CF_BRIDGED_TYPE(NSMutableArray) __CFArray * CFMutableArrayRef;
+typedef struct CF_BRIDGED_MUTABLE_TYPE(NSMutableArray) __CFArray * CFMutableArrayRef;
+typedef struct CF_BRIDGED_MUTABLE_TYPE(CFRunLoopRef) __CFRunLoop * CFRunLoopRef;
 extern const CFAllocatorRef kCFAllocatorDefault;
+typedef struct _NSZone NSZone;
 CFMutableArrayRef CFArrayCreateMutable(CFAllocatorRef allocator, CFIndex capacity);
 extern void CFArrayAppendValue(CFMutableArrayRef theArray, const void *value);
 CFArrayRef CFArrayCreate(CFAllocatorRef allocator, const void **values, CFIndex numValues);
 CFIndex CFArrayGetCount(CFArrayRef theArray);
+CFRunLoopRef CFRunLoopGetCurrent(void);
+CFRunLoopRef CFRunLoopGetMain(void);
 extern CFTypeRef CFRetain(CFTypeRef cf);
 extern void CFRelease(CFTypeRef cf);
+#define CFSTR(cStr) ((CFStringRef) __builtin___CFStringMakeConstantString ("" cStr ""))
+extern Class NSClassFromString(NSString *aClassName);
 
 __attribute__((objc_root_class))
 @interface NSObject
 + (instancetype) alloc;
++ (Class) class;
++ (Class) superclass;
 - (instancetype) init;
 - (instancetype)retain;
 - (void)release;
+- (BOOL)isKindOfClass:(Class)aClass;
+@end
+
+@protocol NSCopying
+- (id)copyWithZone:(NSZone *)zone;
+@end
+
+@protocol NSFastEnumeration
+- (int)countByEnumeratingWithState:(void *)state objects:(id *)objects count:(unsigned)count;
+- (void)protocolMethod;
+@end
+
+@interface NSEnumerator <NSFastEnumeration>
+@end
+
+@interface NSDictionary : NSObject <NSCopying>
+- (NSUInteger)count;
+- (id)objectForKey:(id)aKey;
+- (id)objectForKeyedSubscript:(id)aKey;
+- (NSEnumerator *)keyEnumerator;
++ (id)dictionary;
++ (id)dictionaryWithObject:(id)object forKey:(id <NSCopying>)key;
++ (instancetype)dictionaryWithObjects:(const id [])objects forKeys:(const id <NSCopying> [])keys count:(NSUInteger)cnt;
+@end
+
+@interface NSArray : NSObject <NSCopying, NSFastEnumeration>
+- (NSUInteger)count;
+- (NSEnumerator *)objectEnumerator;
++ (NSArray *)arrayWithObjects:(const id [])objects count:(NSUInteger)count;
+@end
+
+@interface NSString : NSObject <NSCopying>
+- (NSUInteger)length;
+- (NSString *)stringByAppendingString:(NSString *)aString;
+- ( const char *)UTF8String;
+- (id)initWithUTF8String:(const char *)nullTerminatedCString;
++ (id)stringWithUTF8String:(const char *)nullTerminatedCString;
+@end
+
+@interface NSMutableString : NSString
+@end
+
+@interface NSValue : NSObject <NSCopying>
+- (void)getValue:(void *)value;
+@end
+
+@interface NSNumber : NSValue
+- (char)charValue;
+- (id)initWithInt:(int)value;
++ (NSNumber *)numberWithInt:(int)value;
 @end
 
 @interface SomeObj : NSObject
+- (SomeObj *)mutableCopy;
+- (SomeObj *)copyWithValue:(int)value;
 - (void)doWork;
 - (SomeObj *)other;
 - (SomeObj *)next;
@@ -57,28 +121,34 @@ template <typename T> struct RetainPtr {
   PtrType t;
 
   RetainPtr() : t(nullptr) { }
-
   RetainPtr(PtrType t)
     : t(t) {
     if (t)
-      CFRetain(t);
+      CFRetain(toCFTypeRef(t));
   }
-  RetainPtr(RetainPtr&& o)
-    : RetainPtr(o.t)
-  {
-    o.t = nullptr;
-  }
-  RetainPtr(const RetainPtr& o)
+  RetainPtr(RetainPtr&&);
+  RetainPtr(const RetainPtr&);
+  template <typename U>
+  RetainPtr(const RetainPtr<U>& o)
     : RetainPtr(o.t)
+  {}
+  RetainPtr operator=(const RetainPtr& o)
   {
+    if (t)
+      CFRelease(toCFTypeRef(t));
+    t = o.t;
+    if (t)
+      CFRetain(toCFTypeRef(t));
+    return *this;
   }
-  RetainPtr operator=(const RetainPtr& o)
+  template <typename U>
+  RetainPtr operator=(const RetainPtr<U>& o)
   {
     if (t)
-      CFRelease(t);
+      CFRelease(toCFTypeRef(t));
     t = o.t;
     if (t)
-      CFRetain(t);
+      CFRetain(toCFTypeRef(t));
     return *this;
   }
   ~RetainPtr() {
@@ -86,7 +156,7 @@ template <typename T> struct RetainPtr {
   }
   void clear() {
     if (t)
-      CFRelease(t);
+      CFRelease(toCFTypeRef(t));
     t = nullptr;
   }
   void swap(RetainPtr& o) {
@@ -102,10 +172,19 @@ template <typename T> struct RetainPtr {
     swap(o);
     return *this;
   }
+  PtrType leakRef()
+  {
+    PtrType s = t;
+    t = nullptr;
+    return s;
+  }
   operator PtrType() const { return t; }
   operator bool() const { return t; }
 
 private:
+  CFTypeRef toCFTypeRef(id ptr) { return (__bridge CFTypeRef)ptr; }
+  CFTypeRef toCFTypeRef(const void* ptr) { return (CFTypeRef)ptr; }
+
   template <typename U> friend RetainPtr<U> adoptNS(U*);
   template <typename U> friend RetainPtr<U> adoptCF(U);
 
@@ -113,9 +192,26 @@ template <typename T> struct RetainPtr {
   RetainPtr(PtrType t, AdoptTag) : t(t) { }
 };
 
+template <typename T>
+RetainPtr<T>::RetainPtr(RetainPtr<T>&& o)
+  : RetainPtr(o.t)
+{
+  o.t = nullptr;
+}
+
+template <typename T>
+RetainPtr<T>::RetainPtr(const RetainPtr<T>& o)
+  : RetainPtr(o.t)
+{
+}
+
 template <typename T>
 RetainPtr<T> adoptNS(T* t) {
+#if __has_feature(objc_arc)
+  return t;
+#else
   return RetainPtr<T>(t, RetainPtr<T>::Adopt);
+#endif
 }
 
 template <typename T>
@@ -123,9 +219,31 @@ RetainPtr<T> adoptCF(T t) {
   return RetainPtr<T>(t, RetainPtr<T>::Adopt);
 }
 
+template<typename T> inline RetainPtr<T> retainPtr(T ptr)
+{
+  return ptr;
+}
+
+template<typename T> inline RetainPtr<T> retainPtr(T* ptr)
+{
+  return ptr;
+}
+
+inline NSObject *bridge_cast(CFTypeRef object)
+{
+    return (__bridge NSObject *)object;
+}
+
+inline CFTypeRef bridge_cast(NSObject *object)
+{
+    return (__bridge CFTypeRef)object;
+}
+
 }
 
 using WTF::RetainPtr;
 using WTF::adoptNS;
 using WTF::adoptCF;
+using WTF::retainPtr;
 using WTF::downcast;
+using WTF::bridge_cast;
\ No newline at end of file
diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures-decl-protects-this-crash.cpp b/clang/test/Analysis/Check...
[truncated]

Copy link

github-actions bot commented Feb 25, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@t-rasmud t-rasmud left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@rniwa
Copy link
Contributor Author

rniwa commented Mar 9, 2025

Thanks for the review!

@rniwa rniwa merged commit 5c3b059 into llvm:main Mar 9, 2025
12 checks passed
@rniwa rniwa deleted the add-unretained-lambda-captures-checker branch March 9, 2025 21:59
@llvm-ci
Copy link
Collaborator

llvm-ci commented Mar 10, 2025

LLVM Buildbot has detected a new failure on builder sanitizer-x86_64-linux-android running on sanitizer-buildbot-android while building clang,llvm at step 2 "annotate".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/186/builds/7174

Here is the relevant piece of the build log for the reference
Step 2 (annotate) failure: 'python ../sanitizer_buildbot/sanitizers/zorg/buildbot/builders/sanitizers/buildbot_selector.py' (failure)
...
[       OK ] AddressSanitizer.AtoiAndFriendsOOBTest (2301 ms)
[ RUN      ] AddressSanitizer.HasFeatureAddressSanitizerTest
[       OK ] AddressSanitizer.HasFeatureAddressSanitizerTest (0 ms)
[ RUN      ] AddressSanitizer.CallocReturnsZeroMem
[       OK ] AddressSanitizer.CallocReturnsZeroMem (13 ms)
[ DISABLED ] AddressSanitizer.DISABLED_TSDTest
[ RUN      ] AddressSanitizer.IgnoreTest
[       OK ] AddressSanitizer.IgnoreTest (0 ms)
[ RUN      ] AddressSanitizer.SignalTest
[       OK ] AddressSanitizer.SignalTest (185 ms)
[ RUN      ] AddressSanitizer.ReallocTest
[       OK ] AddressSanitizer.ReallocTest (24 ms)
[ RUN      ] AddressSanitizer.WrongFreeTest
[       OK ] AddressSanitizer.WrongFreeTest (117 ms)
[ RUN      ] AddressSanitizer.LongJmpTest
[       OK ] AddressSanitizer.LongJmpTest (0 ms)
[ RUN      ] AddressSanitizer.ThreadStackReuseTest
[       OK ] AddressSanitizer.ThreadStackReuseTest (14 ms)
[ DISABLED ] AddressSanitizer.DISABLED_MemIntrinsicUnalignedAccessTest
[ DISABLED ] AddressSanitizer.DISABLED_LargeFunctionSymbolizeTest
[ DISABLED ] AddressSanitizer.DISABLED_MallocFreeUnwindAndSymbolizeTest
[ RUN      ] AddressSanitizer.UseThenFreeThenUseTest
[       OK ] AddressSanitizer.UseThenFreeThenUseTest (135 ms)
[ RUN      ] AddressSanitizer.FileNameInGlobalReportTest
[       OK ] AddressSanitizer.FileNameInGlobalReportTest (122 ms)
[ DISABLED ] AddressSanitizer.DISABLED_StressStackReuseAndExceptionsTest
[ RUN      ] AddressSanitizer.MlockTest
[       OK ] AddressSanitizer.MlockTest (0 ms)
[ DISABLED ] AddressSanitizer.DISABLED_DemoThreadedTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoStackTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoThreadStackTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFLowIn
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFLowLeft
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFLowRight
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFHigh
[ DISABLED ] AddressSanitizer.DISABLED_DemoOOM
[ DISABLED ] AddressSanitizer.DISABLED_DemoDoubleFreeTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoNullDerefTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoFunctionStaticTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoTooMuchMemoryTest
[ RUN      ] AddressSanitizer.LongDoubleNegativeTest
[       OK ] AddressSanitizer.LongDoubleNegativeTest (0 ms)
[----------] 19 tests from AddressSanitizer (27909 ms total)

[----------] Global test environment tear-down
[==========] 22 tests from 2 test suites ran. (27926 ms total)
[  PASSED  ] 22 tests.

  YOU HAVE 1 DISABLED TEST

Step 34 (run instrumented asan tests [aarch64/bluejay-userdebug/TQ3A.230805.001]) failure: run instrumented asan tests [aarch64/bluejay-userdebug/TQ3A.230805.001] (failure)
...
[ RUN      ] AddressSanitizer.HasFeatureAddressSanitizerTest
[       OK ] AddressSanitizer.HasFeatureAddressSanitizerTest (0 ms)
[ RUN      ] AddressSanitizer.CallocReturnsZeroMem
[       OK ] AddressSanitizer.CallocReturnsZeroMem (13 ms)
[ DISABLED ] AddressSanitizer.DISABLED_TSDTest
[ RUN      ] AddressSanitizer.IgnoreTest
[       OK ] AddressSanitizer.IgnoreTest (0 ms)
[ RUN      ] AddressSanitizer.SignalTest
[       OK ] AddressSanitizer.SignalTest (185 ms)
[ RUN      ] AddressSanitizer.ReallocTest
[       OK ] AddressSanitizer.ReallocTest (24 ms)
[ RUN      ] AddressSanitizer.WrongFreeTest
[       OK ] AddressSanitizer.WrongFreeTest (117 ms)
[ RUN      ] AddressSanitizer.LongJmpTest
[       OK ] AddressSanitizer.LongJmpTest (0 ms)
[ RUN      ] AddressSanitizer.ThreadStackReuseTest
[       OK ] AddressSanitizer.ThreadStackReuseTest (14 ms)
[ DISABLED ] AddressSanitizer.DISABLED_MemIntrinsicUnalignedAccessTest
[ DISABLED ] AddressSanitizer.DISABLED_LargeFunctionSymbolizeTest
[ DISABLED ] AddressSanitizer.DISABLED_MallocFreeUnwindAndSymbolizeTest
[ RUN      ] AddressSanitizer.UseThenFreeThenUseTest
[       OK ] AddressSanitizer.UseThenFreeThenUseTest (135 ms)
[ RUN      ] AddressSanitizer.FileNameInGlobalReportTest
[       OK ] AddressSanitizer.FileNameInGlobalReportTest (122 ms)
[ DISABLED ] AddressSanitizer.DISABLED_StressStackReuseAndExceptionsTest
[ RUN      ] AddressSanitizer.MlockTest
[       OK ] AddressSanitizer.MlockTest (0 ms)
[ DISABLED ] AddressSanitizer.DISABLED_DemoThreadedTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoStackTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoThreadStackTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFLowIn
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFLowLeft
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFLowRight
[ DISABLED ] AddressSanitizer.DISABLED_DemoUAFHigh
[ DISABLED ] AddressSanitizer.DISABLED_DemoOOM
[ DISABLED ] AddressSanitizer.DISABLED_DemoDoubleFreeTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoNullDerefTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoFunctionStaticTest
[ DISABLED ] AddressSanitizer.DISABLED_DemoTooMuchMemoryTest
[ RUN      ] AddressSanitizer.LongDoubleNegativeTest
[       OK ] AddressSanitizer.LongDoubleNegativeTest (0 ms)
[----------] 19 tests from AddressSanitizer (27909 ms total)

[----------] Global test environment tear-down
[==========] 22 tests from 2 test suites ran. (27926 ms total)
[  PASSED  ] 22 tests.

  YOU HAVE 1 DISABLED TEST
program finished with exit code 0
elapsedTime=2736.851156

rniwa added a commit to rniwa/llvm-project that referenced this pull request Mar 11, 2025
…or lambda capturing NS or CF types. (llvm#128651)

Add a new WebKit checker for checking that lambda captures of CF types
use RetainPtr either when ARC is disabled or enabled, and those of NS
types use RetainPtr when ARC is disabled.
rniwa added a commit to rniwa/llvm-project that referenced this pull request Apr 22, 2025
…or lambda capturing NS or CF types. (llvm#128651)

Add a new WebKit checker for checking that lambda captures of CF types
use RetainPtr either when ARC is disabled or enabled, and those of NS
types use RetainPtr when ARC is disabled.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:static analyzer clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants