// RUN: %clang_analyze_cc1 -analyzer-checker=webkit.UncountedLambdaCapturesChecker -verify %s

#include "mock-types.h"

namespace std {

template <typename T>
T&& move(T& t) {
  return static_cast<T&&>(t);
}

namespace ranges {

template<typename IteratorType, typename CallbackType>
void for_each(IteratorType first, IteratorType last, CallbackType callback) {
  for (auto it = first; !(it == last); ++it)
    callback(*it);
}

struct all_of_impl {
  template <typename Collection, typename Predicate>
  constexpr bool operator()(const Collection& collection, Predicate predicate) const {
    for (auto it = collection.begin(); it != collection.end(); ++it) {
      if (!predicate(*it))
        return false;
    }
    return true;
  }
};
inline constexpr auto all_of = all_of_impl {};

}

}

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

class ScopeExit final {
public:
  template<typename ExitFunctionParameter>
  explicit ScopeExit(ExitFunctionParameter&& exitFunction)
    : m_exitFunction(std::move(exitFunction)) {
  }

  ScopeExit(ScopeExit&& other)
    : m_exitFunction(std::move(other.m_exitFunction))
    , m_execute(std::move(other.m_execute)) {
  }

  ~ScopeExit() {
    if (m_execute)
      m_exitFunction();
  }

  WTF::Function<void()> take() {
    m_execute = false;
    return std::move(m_exitFunction);
  }

  void release() { m_execute = false; }

  ScopeExit(const ScopeExit&) = delete;
  ScopeExit& operator=(const ScopeExit&) = delete;
  ScopeExit& operator=(ScopeExit&&) = delete;

private:
  WTF::Function<void()> m_exitFunction;
  bool m_execute { true };
};

template<typename ExitFunction> ScopeExit makeScopeExit(ExitFunction&&);
template<typename ExitFunction>
ScopeExit makeScopeExit(ExitFunction&& exitFunction)
{
    return ScopeExit(std::move(exitFunction));
}

// Visitor adapted from http://stackoverflow.com/questions/25338795/is-there-a-name-for-this-tuple-creation-idiom

template<class A, class... B> struct Visitor : Visitor<A>, Visitor<B...> {
    Visitor(A a, B... b)
        : Visitor<A>(a)
        , Visitor<B...>(b...)
    {
    }

    using Visitor<A>::operator ();
    using Visitor<B...>::operator ();
};
  
template<class A> struct Visitor<A> : A {
    Visitor(A a)
        : A(a)
    {
    }

    using A::operator();
};
 
template<class... F> Visitor<F...> makeVisitor(F... f)
{
    return Visitor<F...>(f...);
}

void opaqueFunction();
template <typename Visitor, typename... Variants> void visit(Visitor&& v, Variants&&... values)
{
  opaqueFunction();
}

} // namespace WTF

struct A {
  static void b();
};

RefCountable* make_obj();

void someFunction();
template <typename Callback> void call(Callback callback) {
  someFunction();
  callback();
}
void callAsync(const WTF::Function<void()>&);

void raw_ptr() {
  RefCountable* ref_countable = make_obj();
  auto foo1 = [ref_countable](){
    // expected-warning@-1{{Captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
    ref_countable->method();
  };
  auto foo2 = [&ref_countable](){
    // expected-warning@-1{{Captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
    ref_countable->method();
  };
  auto foo3 = [&](){
    ref_countable->method();
    // expected-warning@-1{{Implicitly captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
    ref_countable = nullptr;
  };

  auto foo4 = [=](){
    ref_countable->method();
    // expected-warning@-1{{Implicitly captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
  };

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

  // Confirm that the checker respects [[clang::suppress]].
  RefCountable* suppressed_ref_countable = nullptr;
  [[clang::suppress]] auto foo5 = [suppressed_ref_countable](){};
  // no warning.
  call(foo5);
}

void references() {
  RefCountable automatic;
  RefCountable& ref_countable_ref = automatic;
  auto foo1 = [ref_countable_ref](){ ref_countable_ref.constMethod(); };
  auto foo2 = [&ref_countable_ref](){ ref_countable_ref.method(); };
  // expected-warning@-1{{Captured reference 'ref_countable_ref' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
  auto foo3 = [&](){ ref_countable_ref.method(); };
  // expected-warning@-1{{Implicitly captured reference 'ref_countable_ref' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
  auto foo4 = [=](){ ref_countable_ref.constMethod(); };

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

void quiet() {
// This code is not expected to trigger any warnings.
  {
    RefCountable automatic;
    RefCountable &ref_countable_ref = automatic;
  }

  auto foo3 = [&]() {};
  auto foo4 = [=]() {};

  call(foo3);
  call(foo4);

  RefCountable *ref_countable = nullptr;
}

template <typename Callback>
void map(RefCountable* start, [[clang::noescape]] Callback&& callback)
{
  while (start) {
    callback(*start);
    start = start->next();
  }
}

template <typename Callback1, typename Callback2>
void doubleMap(RefCountable* start, [[clang::noescape]] Callback1&& callback1, Callback2&& callback2)
{
  while (start) {
    callback1(*start);
    callback2(*start);
    start = start->next();
  }
}

void noescape_lambda() {
  RefCountable* someObj = make_obj();
  RefCountable* otherObj = make_obj();
  map(make_obj(), [&](RefCountable& obj) {
    otherObj->method();
  });
  doubleMap(make_obj(), [&](RefCountable& obj) {
    otherObj->method();
  }, [&](RefCountable& obj) {
    otherObj->method();
    // expected-warning@-1{{Implicitly captured raw-pointer 'otherObj' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
  });
  ([&] {
    someObj->method();
  })();
}

void lambda_capture_param(RefCountable* obj) {
  auto someLambda = [&]() {
    obj->method();
  };
  someLambda();
  someLambda();
}

struct RefCountableWithLambdaCapturingThis {
  void ref() const;
  void deref() const;
  void nonTrivial();

  void method_captures_this_safe() {
    auto lambda = [&]() {
      nonTrivial();
    };
    lambda();
  }

  void method_captures_this_unsafe() {
    auto lambda = [&]() {
      nonTrivial();
      // expected-warning@-1{{Implicitly captured raw-pointer 'this' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
    };
    call(lambda);
  }

  void method_captures_this_unsafe_capture_local_var_explicitly() {
    RefCountable* x = make_obj();
    call([this, protectedThis = RefPtr { this }, x]() {
      // expected-warning@-1{{Captured raw-pointer 'x' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
      nonTrivial();
      x->method();
    });
  }

  void method_captures_this_with_other_protected_var() {
    RefCountable* x = make_obj();
    call([this, protectedX = RefPtr { x }]() {
      // expected-warning@-1{{Captured raw-pointer 'this' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
      nonTrivial();
      protectedX->method();
    });
  }

  void method_captures_this_unsafe_capture_local_var_explicitly_with_deref() {
    RefCountable* x = make_obj();
    call([this, protectedThis = Ref { *this }, x]() {
      // expected-warning@-1{{Captured raw-pointer 'x' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
      nonTrivial();
      x->method();
    });
  }

  void method_captures_this_unsafe_local_var_via_vardecl() {
    RefCountable* x = make_obj();
    auto lambda = [this, protectedThis = Ref { *this }, x]() {
      // expected-warning@-1{{Captured raw-pointer 'x' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
      nonTrivial();
      x->method();
    };
    call(lambda);
  }

  void method_captures_this_with_guardian() {
    auto lambda = [this, protectedThis = Ref { *this }]() {
      nonTrivial();
    };
    call(lambda);
  }

  void method_captures_this_with_guardian_refptr() {
    auto lambda = [this, protectedThis = RefPtr { &*this }]() {
      nonTrivial();
    };
    call(lambda);
  }

  void forEach(const WTF::Function<void(RefCountable&)>&);
  void method_captures_this_with_lambda_with_no_escape() {
    auto run = [&]([[clang::noescape]] const WTF::Function<void(RefCountable&)>& func) {
      forEach(func);
    };
    run([&](RefCountable&) {
      nonTrivial();
    });
  }

  static void callLambda([[clang::noescape]] const WTF::Function<RefPtr<RefCountable>()>&);
  void method_captures_this_in_template_method() {
    RefCountable* obj = make_obj();
    WTF::HashMap<int, RefPtr<RefCountable>> nextMap;
    nextMap.ensure(3, [&] {
      return obj->next();
    });
    nextMap+[&] {
      return obj->next();
    };
    WTF::HashMap<int, RefPtr<RefCountable>>::ifAny(nextMap, [&](auto& item) -> bool {
      return item->next() && obj->next();
    });
    callLambda([&]() -> RefPtr<RefCountable> {
      return obj->next();
    });
    WTF::HashMap<int, RefPtr<RefCountable>> anotherMap([&] {
      return obj->next();
    });
  }

  void callAsyncNoescape([[clang::noescape]] WTF::Function<bool(RefCountable&)>&&);
  void method_temp_lambda(RefCountable* obj) {
    callAsyncNoescape([this, otherObj = RefPtr { obj }](auto& obj) {
      return otherObj == &obj;
    });
  }

  void method_nested_lambda() {
    callAsync([this, protectedThis = Ref { *this }] {
      callAsync([this, protectedThis = static_cast<const Ref<RefCountableWithLambdaCapturingThis>&&>(protectedThis)] {
        nonTrivial();
      });
    });
  }

  void method_nested_lambda2() {
    callAsync([this, protectedThis = RefPtr { this }] {
      callAsync([this, protectedThis = std::move(*protectedThis)] {
        nonTrivial();
      });
    });
  }

  void method_nested_lambda3() {
    callAsync([this, protectedThis = RefPtr { this }] {
      callAsync([this] {
        // expected-warning@-1{{Captured raw-pointer 'this' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
        nonTrivial();
      });
    });
  }

  void method_nested_lambda4() {
    callAsync([this, protectedThis = RefPtr { this }] {
      callAsync([this, protectedThis = WTF::move(*protectedThis)] {
        nonTrivial();
      });
    });
  }
};

struct NonRefCountableWithLambdaCapturingThis {
  void nonTrivial();

  void method_captures_this_safe() {
    auto lambda = [&]() {
      nonTrivial();
    };
    lambda();
  }

  void method_captures_this_unsafe() {
    auto lambda = [&]() {
      nonTrivial();
    };
    call(lambda);
  }
};

void trivial_lambda() {
  RefCountable* ref_countable = make_obj();
  auto trivial_lambda = [&]() {
    return ref_countable->trivial();
  };
  trivial_lambda();
}

bool call_lambda_var_decl() {
  RefCountable* ref_countable = make_obj();
  auto lambda1 = [&]() -> bool {
    return ref_countable->next();
  };
  auto lambda2 = [=]() -> bool {
    return ref_countable->next();
  };
  return lambda1() && lambda2();
}

void lambda_with_args(RefCountable* obj) {
  auto trivial_lambda = [&](int v) {
    obj->method();
  };
  trivial_lambda(1);
}

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

void lambda_converted_to_function(RefCountable* obj)
{
  callFunction([&]() {
    obj->method();
    // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
  });
  callFunctionOpaque([&]() {
    obj->method();
    // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
  });
}

void capture_copy_in_lambda(CheckedObj& checked) {
  callFunctionOpaque([checked]() mutable {
    checked.method();
  });
  auto* ptr = &checked;
  callFunctionOpaque([ptr]() mutable {
    // expected-warning@-1{{Captured raw-pointer 'ptr' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
    ptr->method();
  });
}

struct TemplateFunctionCallsLambda {
  void ref() const;
  void deref() const;

  RefCountable* obj();

  template <typename T>
  RefPtr<T> method(T* t) {
    auto ret = ([&]() -> RefPtr<T> {
      if constexpr (T::isEncodable)
        return t;
      return obj() ? t : nullptr;
    })();
    return ret;
  }
};

class Iterator {
public:
  Iterator(void* array, unsigned long sizeOfElement, unsigned int index);
  Iterator(const Iterator&);
  Iterator& operator=(const Iterator&);
  bool operator==(const Iterator&);

  Iterator& operator++();
  int& operator*();

private:
  void* current { nullptr };
  unsigned long sizeOfElement { 0 };
};

void ranges_for_each(RefCountable* obj) {
  int array[] = { 1, 2, 3, 4, 5 };
  std::ranges::for_each(Iterator(array, sizeof(*array), 0), Iterator(array, sizeof(*array), 5), [&](int& item) {
    obj->method();
    ++item;
  });
}

class IntCollection {
public:
  int* begin();
  int* end();
  const int* begin() const;
  const int* end() const;
};

class RefCountedObj {
public:
  void ref();
  void deref();

  bool allOf(const IntCollection&);
  bool isMatch(int);

  void call() const;
  void callLambda([[clang::noescape]] const WTF::Function<void ()>& callback) const;
  void doSomeWork() const;
};

bool RefCountedObj::allOf(const IntCollection& collection) {
  return std::ranges::all_of(collection, [&](auto& number) {
    return isMatch(number);
  });
}

void RefCountedObj::callLambda([[clang::noescape]] const WTF::Function<void ()>& callback) const
{
    callback();
}

void RefCountedObj::call() const
{
    auto lambda = [&] {
        doSomeWork();
    };
    callLambda(lambda);
}

void scope_exit(RefCountable* obj) {
  auto scope = WTF::makeScopeExit([&] {
    obj->method();
  });
  someFunction();
  WTF::ScopeExit scope2([&] {
    obj->method();
  });
  someFunction();
}

void doWhateverWith(WTF::ScopeExit& obj);

void scope_exit_with_side_effect(RefCountable* obj) {
  auto scope = WTF::makeScopeExit([&] {
    obj->method();
    // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
  });
  doWhateverWith(scope);
}

void scope_exit_static(RefCountable* obj) {
  static auto scope = WTF::makeScopeExit([&] {
    obj->method();
    // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
  });
}

WTF::Function<void()> scope_exit_take_lambda(RefCountable* obj) {
  auto scope = WTF::makeScopeExit([&] {
    obj->method();
    // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
  });
  return scope.take();
}

// FIXME: Ideally, we treat release() as a trivial function.
void scope_exit_release(RefCountable* obj) {
  auto scope = WTF::makeScopeExit([&] {
    obj->method();
    // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
  });
  scope.release();
}

void make_visitor(RefCountable* obj) {
  auto visitor = WTF::makeVisitor([&] {
    obj->method();
  });
}

void use_visitor(RefCountable* obj) {
  auto visitor = WTF::makeVisitor([&] {
    obj->method();
  });
  WTF::visit(visitor, obj);
}

template <typename Visitor, typename ObjectType>
void bad_visit(Visitor&, ObjectType*) {
  someFunction();
}

void static_visitor(RefCountable* obj) {
  static auto visitor = WTF::makeVisitor([&] {
    obj->method();
    // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
  });
}

void bad_use_visitor(RefCountable* obj) {
  auto visitor = WTF::makeVisitor([&] {
    obj->method();
    // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
  });
  bad_visit(visitor, obj);
}
