[clang] [llvm] [alpha.webkit.UnretainedLambdaCapturesChecker] Add a WebKit checker for lambda capturing NS or CF types. (PR #128651)

Rashmi Mudduluru via cfe-commits cfe-commits at lists.llvm.org
Sat Mar 8 14:23:59 PST 2025


================
@@ -0,0 +1,296 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UnretainedLambdaCapturesChecker -verify %s
+
+#include "objc-mock-types.h"
+
+namespace std {
+
+template <typename T>
+class unique_ptr {
+private:
+  T *t;
+
+public:
+  unique_ptr() : t(nullptr) { }
+  unique_ptr(T *t) : t(t) { }
+  ~unique_ptr() {
+    if (t)
+      delete t;
+  }
+  template <typename U> unique_ptr(unique_ptr<U>&& u)
+    : t(u.t)
+  {
+    u.t = nullptr;
+  }
+  T *get() const { return t; }
+  T *operator->() const { return t; }
+  T &operator*() const { return *t; }
+  unique_ptr &operator=(T *) { return *this; }
+  explicit operator bool() const { return !!t; }
+};
+
+};
+
+namespace WTF {
+
+namespace Detail {
+
+template<typename Out, typename... In>
+class CallableWrapperBase {
+public:
+    virtual ~CallableWrapperBase() { }
+    virtual Out call(In...) = 0;
+};
+
+template<typename, typename, typename...> class CallableWrapper;
+
+template<typename CallableType, typename Out, typename... In>
+class CallableWrapper : public CallableWrapperBase<Out, In...> {
+public:
+    explicit CallableWrapper(CallableType& callable)
+        : m_callable(callable) { }
+    Out call(In... in) final { return m_callable(in...); }
+
+private:
+    CallableType m_callable;
+};
+
+} // namespace Detail
+
+template<typename> class Function;
+
+template<typename Out, typename... In> Function<Out(In...)> adopt(Detail::CallableWrapperBase<Out, In...>*);
+
+template <typename Out, typename... In>
+class Function<Out(In...)> {
+public:
+    using Impl = Detail::CallableWrapperBase<Out, In...>;
+
+    Function() = default;
+
+    template<typename FunctionType>
+    Function(FunctionType f)
+        : m_callableWrapper(new Detail::CallableWrapper<FunctionType, Out, In...>(f)) { }
+
+    Out operator()(In... in) const { return m_callableWrapper->call(in...); }
+    explicit operator bool() const { return !!m_callableWrapper; }
+
+private:
+    enum AdoptTag { Adopt };
+    Function(Impl* impl, AdoptTag)
+        : m_callableWrapper(impl)
+    {
+    }
+
+    friend Function adopt<Out, In...>(Impl*);
+
+    std::unique_ptr<Impl> m_callableWrapper;
+};
+
+template<typename Out, typename... In> Function<Out(In...)> adopt(Detail::CallableWrapperBase<Out, In...>* impl)
+{
+    return Function<Out(In...)>(impl, Function<Out(In...)>::Adopt);
+}
+
+template <typename KeyType, typename ValueType>
+class HashMap {
+public:
+  HashMap();
+  HashMap([[clang::noescape]] const Function<ValueType()>&);
+  void ensure(const KeyType&, [[clang::noescape]] const Function<ValueType()>&);
+  bool operator+([[clang::noescape]] const Function<ValueType()>&) const;
+  static void ifAny(HashMap, [[clang::noescape]] const Function<bool(ValueType)>&);
+
+private:
+  ValueType* m_table { nullptr };
+};
+
+} // namespace WTF
+
+struct A {
+  static void b();
+};
+
+SomeObj* make_obj();
+CFMutableArrayRef make_cf();
+
+void someFunction();
+template <typename Callback> void call(Callback callback) {
+  someFunction();
+  callback();
+}
+void callAsync(const WTF::Function<void()>&);
+
+void raw_ptr() {
+  SomeObj* obj = make_obj();
+  auto foo1 = [obj](){
+    // expected-warning at -1{{Captured raw-pointer 'obj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+    [obj doWork];
+  };
+  call(foo1);
+
+  auto foo2 = [&obj](){
+    // expected-warning at -1{{Captured raw-pointer 'obj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+    [obj doWork];
+  };
+  auto foo3 = [&](){
+    [obj doWork];
+    // expected-warning at -1{{Implicitly captured raw-pointer 'obj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+    obj = nullptr;
+  };
+  auto foo4 = [=](){
+    [obj doWork];
+    // expected-warning at -1{{Implicitly captured raw-pointer 'obj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+  };
+  
+  auto cf = make_cf();
+  auto bar1 = [cf](){
+    // expected-warning at -1{{Captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+    CFArrayAppendValue(cf, nullptr);
+  };
+  auto bar2 = [&cf](){
+    // expected-warning at -1{{Captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+    CFArrayAppendValue(cf, nullptr);
+  };
+  auto bar3 = [&](){
+    CFArrayAppendValue(cf, nullptr);
+    // expected-warning at -1{{Implicitly captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+    cf = nullptr;
+  };
+  auto bar4 = [=](){
+    CFArrayAppendValue(cf, nullptr);
+    // expected-warning at -1{{Implicitly captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+  };
+
+  call(foo1);
+  call(foo2);
+  call(foo3);
+  call(foo4);
+
+  call(bar1);
+  call(bar2);
+  call(bar3);
+  call(bar4);
+
+  // Confirm that the checker respects [[clang::suppress]].
+  SomeObj* suppressed_obj = nullptr;
+  [[clang::suppress]] auto foo5 = [suppressed_obj](){
+    [suppressed_obj doWork];
+  };
+  // no warning.
+  call(foo5);
+
+  // Confirm that the checker respects [[clang::suppress]].
+  CFMutableArrayRef suppressed_cf = nullptr;
+  [[clang::suppress]] auto bar5 = [suppressed_cf](){
+    CFArrayAppendValue(suppressed_cf, nullptr);
+  };
+  // no warning.
+  call(bar5);
+}
+
+void quiet() {
+// This code is not expected to trigger any warnings.
+  SomeObj *obj;
+
+  auto foo3 = [&]() {};
+  auto foo4 = [=]() {};
+
+  call(foo3);
+  call(foo4);
+
+  obj = nullptr;
+}
+
+template <typename Callback>
+void map(SomeObj* start, [[clang::noescape]] Callback&& callback)
+{
+  while (start) {
+    callback(start);
+    start = [start next];
+  }
+}
+
+template <typename Callback1, typename Callback2>
+void doubleMap(SomeObj* start, [[clang::noescape]] Callback1&& callback1, Callback2&& callback2)
+{
+  while (start) {
+    callback1(start);
+    callback2(start);
+    start = [start next];
+  }
+}
+
+template <typename Callback1, typename Callback2>
+void get_count_cf(CFArrayRef array, [[clang::noescape]] Callback1&& callback1, Callback2&& callback2)
+{
+  auto count = CFArrayGetCount(array);
+  callback1(count);
+  callback2(count);
+}
+
+void noescape_lambda() {
+  SomeObj* someObj = make_obj();
+  SomeObj* otherObj = make_obj();
+  map(make_obj(), [&](SomeObj *obj) {
+    [otherObj doWork];
+  });
+  doubleMap(make_obj(), [&](SomeObj *obj) {
+    [otherObj doWork];
+  }, [&](SomeObj *obj) {
+    [otherObj doWork];
+    // expected-warning at -1{{Implicitly captured raw-pointer 'otherObj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+  });
+  ([&] {
+    [someObj doWork];
+  })();
+
+  CFMutableArrayRef someCF = make_cf();
+  get_count_cf(make_cf(), [&](CFIndex count) {
+    CFArrayAppendValue(someCF, nullptr);
+  }, [&](CFIndex count) {
+    CFArrayAppendValue(someCF, nullptr);
+    // expected-warning at -1{{Implicitly captured reference 'someCF' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+  });
+}
+
+void callFunctionOpaque(WTF::Function<void()>&&);
+void callFunction(WTF::Function<void()>&& function) {
+  someFunction();
+  function();
+}
+
+void lambda_converted_to_function(SomeObj* obj, CFMutableArrayRef cf)
+{
+  callFunction([&]() {
+    [obj doWork];
+    // expected-warning at -1{{Implicitly captured raw-pointer 'obj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+    CFArrayAppendValue(cf, nullptr);
+    // expected-warning at -1{{Implicitly captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+  });
+  callFunctionOpaque([&]() {
+    [obj doWork];
+    // expected-warning at -1{{Implicitly captured raw-pointer 'obj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+    CFArrayAppendValue(cf, nullptr);
+    // expected-warning at -1{{Implicitly captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}}
+  });
+}
+
+ at interface ObjWithSelf : NSObject {
+  RetainPtr<id> delegate;
+}
+-(void)doWork;
+-(void)run;
+ at end
+
+ at implementation ObjWithSelf
+-(void)doWork {
+  auto doWork = [&] {
+    someFunction();
+    [delegate doWork];
+  };
+  callFunctionOpaque(doWork);
+}
+-(void)run {
+  someFunction();
+}
+ at end
----------------
t-rasmud wrote:

Thanks for adding test cases covering all common scenarios.

https://github.com/llvm/llvm-project/pull/128651


More information about the cfe-commits mailing list