// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UnretainedLambdaCapturesChecker -fobjc-arc -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();
dispatch_queue_t make_os();

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](){
    [obj doWork];
  };
  auto foo2 = [&obj](){
    [obj doWork];
  };
  auto foo3 = [&](){
    [obj doWork];
    obj = nullptr;
  };
  auto foo4 = [=](){
    [obj doWork];
  };
  
  auto cf = make_cf();
  auto bar1 = [cf](){
    // expected-warning@-1{{Captured variable 'cf' is a RetainPtr-capable type 'CFMutableArrayRef' [alpha.webkit.UnretainedLambdaCapturesChecker]}}
    CFArrayAppendValue(cf, nullptr);
  };
  auto bar2 = [&cf](){
    // expected-warning@-1{{Captured variable 'cf' is a RetainPtr-capable type 'CFMutableArrayRef' [alpha.webkit.UnretainedLambdaCapturesChecker]}}
    CFArrayAppendValue(cf, nullptr);
  };
  auto bar3 = [&](){
    CFArrayAppendValue(cf, nullptr);
    // expected-warning@-1{{Implicitly captured variable 'cf' is a RetainPtr-capable type 'CFMutableArrayRef' [alpha.webkit.UnretainedLambdaCapturesChecker]}}
    cf = nullptr;
  };
  auto bar4 = [=](){
    CFArrayAppendValue(cf, nullptr);
    // expected-warning@-1{{Implicitly captured variable 'cf' is a RetainPtr-capable type 'CFMutableArrayRef' [alpha.webkit.UnretainedLambdaCapturesChecker]}}
  };

  auto os = make_os();
  auto baz1 = [os](){
    dispatch_queue_get_label(os);
  };
  auto baz2 = [&os](){
    dispatch_queue_get_label(os);
  };
  auto baz3 = [&](){
    dispatch_queue_get_label(os);
    os = nullptr;
  };
  auto baz4 = [=](){
    dispatch_queue_get_label(os);
  };

  call(foo1);
  call(foo2);
  call(foo3);
  call(foo4);

  call(bar1);
  call(bar2);
  call(bar3);
  call(bar4);

  call(baz1);
  call(baz2);
  call(baz3);
  call(baz4);
}

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);
}

template <typename Callback1, typename Callback2>
void get_count_os(dispatch_queue_t queue, [[clang::noescape]] Callback1&& callback1, Callback2&& callback2)
{
  auto* label = dispatch_queue_get_label(queue);
  callback1(label);
  callback2(label);
}

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];
  });
  ([&] {
    [someObj doWork];
  })();

  CFMutableArrayRef someCF = make_cf();
  get_count_cf(make_cf(), [&](CFIndex count) {
    CFArrayAppendValue(someCF, nullptr);
  }, [&](CFIndex count) {
    CFArrayAppendValue(someCF, nullptr);
    // expected-warning@-1{{Implicitly captured variable 'someCF' is a RetainPtr-capable type 'CFMutableArrayRef' [alpha.webkit.UnretainedLambdaCapturesChecker]}}
  });

  dispatch_queue_t someOS = make_os();
  get_count_os(make_os(), [&](const char* label) {
    dispatch_queue_get_label(someOS);
  }, [&](const char* label) {
    dispatch_queue_get_label(someOS);
  });
}

void callFunctionOpaque(WTF::Function<void()>&&);
void callFunction(WTF::Function<void()>&& function) {
  someFunction();
  function();
}

void lambda_converted_to_function(SomeObj* obj, CFMutableArrayRef cf, dispatch_queue_t os)
{
  callFunction([&]() {
    [obj doWork];
    CFArrayAppendValue(cf, nullptr);
    // expected-warning@-1{{Implicitly captured variable 'cf' is a RetainPtr-capable type 'CFMutableArrayRef' [alpha.webkit.UnretainedLambdaCapturesChecker]}}
    dispatch_queue_get_label(os);
  });
  callFunctionOpaque([&]() {
    [obj doWork];
    CFArrayAppendValue(cf, nullptr);
    // expected-warning@-1{{Implicitly captured variable 'cf' is a RetainPtr-capable type 'CFMutableArrayRef' [alpha.webkit.UnretainedLambdaCapturesChecker]}}
    dispatch_queue_get_label(os);
  });
}

@interface ObjWithSelf : NSObject {
  RetainPtr<id> delegate;
  OSObjectPtr<dispatch_queue_t> queue;
}
-(void)doWork;
-(void)doMoreWork;
-(void)run;
@end

@implementation ObjWithSelf
-(void)doWork {
  auto doWork = [&] {
    someFunction();
    [delegate doWork];
  };
  auto doMoreWork = [=] {
    someFunction();
    [delegate doWork];
  };
  auto doExtraWork = [&, protectedSelf = retainPtr(self)] {
    someFunction();
    [delegate doWork];
  };
  auto* queuePtr = queue.get();
  auto doAdditionalWork = [&] {
    someFunction();
    dispatch_queue_get_label(queuePtr);
  };
  callFunctionOpaque(doWork);
  callFunctionOpaque(doMoreWork);
  callFunctionOpaque(doExtraWork);
  callFunctionOpaque(doAdditionalWork);
}

-(void)doMoreWork {
  auto doWork = [self, protectedSelf = retainPtr(self)] {
    someFunction();
    [self doWork];
  };
  callFunctionOpaque(doWork);
}

-(void)run {
  someFunction();
}
@end
