////////////////////////////////////////////////////////////////////////////
//
// Copyright 2020 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or utilied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

#ifndef REALM_OS_CHECKED_MUTEX_HPP
#define REALM_OS_CHECKED_MUTEX_HPP

#include <realm/util/assert.hpp>

#include <memory>
#include <mutex>

// Clang's thread safety analysis can be used to statically check that variables
// which should be guarded by a mutex actually are only accessed with that
// mutex acquired. This requires annotating variables and functions with which
// "capabilities" (i.e. a generalization of the concept of a mutex) are required.
//
// libc++ has an option to apply this for *all* uses of <mutex> by defining
// _LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS, but this doesn't make it easy to
// incrementally adopt the annotations (and it's not obvious that it's even
// worth trying to universally adopt them), so instead we have some wrappers
// for std::mutex and locks which add annotations.

// See https://clang.llvm.org/docs/ThreadSafetyAnalysis.html for more information on this

#if defined(__clang__)
#define REALM_THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
#else
#define REALM_THREAD_ANNOTATION_ATTRIBUTE__(x)
#endif

#define CAPABILITY(x) REALM_THREAD_ANNOTATION_ATTRIBUTE__(capability(x))

#define RETURN_CAPABILITY(x) REALM_THREAD_ANNOTATION_ATTRIBUTE__(return_capability(x))

#define SCOPED_CAPABILITY REALM_THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)

#define GUARDED_BY(x) REALM_THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))

#define REQUIRES(...) REALM_THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))

#define ACQUIRE(...) REALM_THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))

#define RELEASE(...) REALM_THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))

#define EXCLUDES(...) REALM_THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))

#define NO_THREAD_SAFETY_ANALYSIS REALM_THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
#define ASSERT_CAPABILITY(x) REALM_THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))

namespace realm {
namespace util {

// std::unique_lock with thread safety annotations
class SCOPED_CAPABILITY CheckedUniqueLock {
    using Impl = std::unique_lock<std::mutex>;

public:
    template <typename Mutex>
    CheckedUniqueLock(Mutex const& m) ACQUIRE(m)
        : m_impl(m.lock())
    {
    }
    ~CheckedUniqueLock() RELEASE() {}

    CheckedUniqueLock(CheckedUniqueLock&&) = default;
    CheckedUniqueLock& operator=(CheckedUniqueLock&&) = default;

    void lock() ACQUIRE()
    {
        m_impl.lock();
    }
    void unlock() RELEASE()
    {
        m_impl.unlock();
    }
    void lock_unchecked()
    {
        m_impl.lock();
    }
    void unlock_unchecked()
    {
        m_impl.unlock();
    }
    bool owns_lock() const noexcept
    {
        return m_impl.owns_lock();
    }

    Impl& native_handle()
    {
        return m_impl;
    }

private:
    Impl m_impl;
};

// std::lock_guard with thread safety annotations
class SCOPED_CAPABILITY CheckedLockGuard {
    using Impl = std::unique_lock<std::mutex>;

public:
    template <typename Mutex>
    CheckedLockGuard(Mutex const& m) ACQUIRE(m)
        : m_impl(m.lock())
    {
    }
    ~CheckedLockGuard() RELEASE() {}

    CheckedLockGuard(CheckedLockGuard&&) = delete;
    CheckedLockGuard& operator=(CheckedLockGuard&&) = delete;

    Impl& native_handle()
    {
        return m_impl;
    }

private:
    Impl m_impl;
};

// std::mutex with thread safety annotations
class CAPABILITY("mutex") CheckedMutex {
public:
    CheckedMutex() = default;

    // Required for REQUIRES(!m); do not actually call
    CheckedMutex const& operator!() const
    {
        return *this;
    }

    // Thread-safety analysis is purely function-local, so when we pass a
    // UniqueLock to a function, the analysis doesn't know what mutex is
    // released by unlock(). Unlocking via this function tells it which one is
    // used.
    void unlock(CheckedUniqueLock& lock) RELEASE()
    {
        REALM_ASSERT(lock.owns_lock());
        REALM_ASSERT(lock.native_handle().mutex() == &m_mutex);
        lock.unlock_unchecked();
    }

private:
    mutable std::mutex m_mutex;
    friend class CheckedUniqueLock;
    friend class CheckedLockGuard;

    std::unique_lock<std::mutex> lock() const
    {
        return std::unique_lock<std::mutex>(m_mutex);
    }
};

// An "optional" mutex. If constructed with enable=true, it works like a normal
// std::mutex. If constructed with enable=false, locking and unlocking it is
// a no-op.
class CAPABILITY("mutex") CheckedOptionalMutex {
public:
    CheckedOptionalMutex(bool enable = false);
    CheckedOptionalMutex(CheckedOptionalMutex const&);
    CheckedOptionalMutex& operator=(CheckedOptionalMutex const&);
    CheckedOptionalMutex(CheckedOptionalMutex&&) = default;
    CheckedOptionalMutex& operator=(CheckedOptionalMutex&&) = default;

    // Required for REQUIRE(!m); do not actually call
    CheckedOptionalMutex const& operator!() const
    {
        return *this;
    }

private:
    mutable std::unique_ptr<std::mutex> m_mutex;
    friend class CheckedUniqueLock;
    friend class CheckedLockGuard;

    std::unique_lock<std::mutex> lock() const;
};

inline CheckedOptionalMutex::CheckedOptionalMutex(bool enable)
    : m_mutex(enable ? std::make_unique<std::mutex>() : nullptr)
{
}

inline CheckedOptionalMutex::CheckedOptionalMutex(CheckedOptionalMutex const& o)
    : CheckedOptionalMutex(!!o.m_mutex)
{
}

inline CheckedOptionalMutex& CheckedOptionalMutex::operator=(CheckedOptionalMutex const& o)
{
    if (&o != this) {
        if (o.m_mutex)
            m_mutex = std::make_unique<std::mutex>();
        else
            m_mutex.reset();
    }
    return *this;
}

inline std::unique_lock<std::mutex> CheckedOptionalMutex::lock() const
{
    return m_mutex ? std::unique_lock<std::mutex>(*m_mutex) : std::unique_lock<std::mutex>();
}

} // namespace util
} // namespace realm

#endif // REALM_OS_CHECKED_MUTEX_HPP
