/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_StaticPtr_h
#define mozilla_StaticPtr_h

#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/RefPtr.h"

namespace mozilla {

/**
 * StaticAutoPtr and StaticRefPtr are like nsAutoPtr and nsRefPtr, except they
 * are suitable for use as global variables.
 *
 * In particular, a global instance of Static{Auto,Ref}Ptr doesn't cause the
 * compiler to emit  a static initializer (in release builds, anyway).
 *
 * In order to accomplish this, Static{Auto,Ref}Ptr must have a trivial
 * constructor and destructor.  As a consequence, it cannot initialize its raw
 * pointer to 0 on construction, and it cannot delete/release its raw pointer
 * upon destruction.
 *
 * Since the compiler guarantees that all global variables are initialized to
 * 0, these trivial constructors are safe.  Since we rely on this, the clang
 * plugin, run as part of our "static analysis" builds, makes it a compile-time
 * error to use Static{Auto,Ref}Ptr as anything except a global variable.
 *
 * Static{Auto,Ref}Ptr have a limited interface as compared to ns{Auto,Ref}Ptr;
 * this is intentional, since their range of acceptable uses is smaller.
 */

template<class T>
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticAutoPtr
{
public:
  // In debug builds, check that mRawPtr is initialized for us as we expect
  // by the compiler.  In non-debug builds, don't declare a constructor
  // so that the compiler can see that the constructor is trivial.
#ifdef DEBUG
  StaticAutoPtr()
  {
    MOZ_ASSERT(!mRawPtr);
  }
#endif

  StaticAutoPtr<T>& operator=(T* aRhs)
  {
    Assign(aRhs);
    return *this;
  }

  T* get() const { return mRawPtr; }

  operator T*() const { return get(); }

  T* operator->() const
  {
    MOZ_ASSERT(mRawPtr);
    return get();
  }

  T& operator*() const { return *get(); }

private:
  // Disallow copy constructor, but only in debug mode.  We only define
  // a default constructor in debug mode (see above); if we declared
  // this constructor always, the compiler wouldn't generate a trivial
  // default constructor for us in non-debug mode.
#ifdef DEBUG
  StaticAutoPtr(StaticAutoPtr<T>& aOther);
#endif

  void Assign(T* aNewPtr)
  {
    MOZ_ASSERT(!aNewPtr || mRawPtr != aNewPtr);
    T* oldPtr = mRawPtr;
    mRawPtr = aNewPtr;
    delete oldPtr;
  }

  T* mRawPtr;
};

template<class T>
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticRefPtr
{
public:
  // In debug builds, check that mRawPtr is initialized for us as we expect
  // by the compiler.  In non-debug builds, don't declare a constructor
  // so that the compiler can see that the constructor is trivial.
#ifdef DEBUG
  StaticRefPtr()
  {
    MOZ_ASSERT(!mRawPtr);
  }
#endif

  StaticRefPtr<T>& operator=(T* aRhs)
  {
    AssignWithAddref(aRhs);
    return *this;
  }

  StaticRefPtr<T>& operator=(const StaticRefPtr<T>& aRhs)
  {
    return (this = aRhs.mRawPtr);
  }

  StaticRefPtr<T>& operator=(already_AddRefed<T>& aRhs)
  {
    AssignAssumingAddRef(aRhs.take());
    return *this;
  }

  StaticRefPtr<T>& operator=(already_AddRefed<T>&& aRhs)
  {
    AssignAssumingAddRef(aRhs.take());
    return *this;
  }

  already_AddRefed<T>
  forget()
  {
    T* temp = mRawPtr;
    mRawPtr = nullptr;
    return already_AddRefed<T>(temp);
  }

  T* get() const { return mRawPtr; }

  operator T*() const { return get(); }

  T* operator->() const
  {
    MOZ_ASSERT(mRawPtr);
    return get();
  }

  T& operator*() const { return *get(); }

private:
  void AssignWithAddref(T* aNewPtr)
  {
    if (aNewPtr) {
      aNewPtr->AddRef();
    }
    AssignAssumingAddRef(aNewPtr);
  }

  void AssignAssumingAddRef(T* aNewPtr)
  {
    T* oldPtr = mRawPtr;
    mRawPtr = aNewPtr;
    if (oldPtr) {
      oldPtr->Release();
    }
  }

  T* MOZ_OWNING_REF mRawPtr;
};

namespace StaticPtr_internal {
class Zero;
} // namespace StaticPtr_internal

#define REFLEXIVE_EQUALITY_OPERATORS(type1, type2, eq_fn, ...) \
  template<__VA_ARGS__>                                        \
  inline bool                                                  \
  operator==(type1 lhs, type2 rhs)                             \
  {                                                            \
    return eq_fn;                                              \
  }                                                            \
                                                               \
  template<__VA_ARGS__>                                        \
  inline bool                                                  \
  operator==(type2 lhs, type1 rhs)                             \
  {                                                            \
    return rhs == lhs;                                         \
  }                                                            \
                                                               \
  template<__VA_ARGS__>                                        \
  inline bool                                                  \
  operator!=(type1 lhs, type2 rhs)                             \
  {                                                            \
    return !(lhs == rhs);                                      \
  }                                                            \
                                                               \
  template<__VA_ARGS__>                                        \
  inline bool                                                  \
  operator!=(type2 lhs, type1 rhs)                             \
  {                                                            \
    return !(lhs == rhs);                                      \
  }

// StaticAutoPtr (in)equality operators

template<class T, class U>
inline bool
operator==(const StaticAutoPtr<T>& aLhs, const StaticAutoPtr<U>& aRhs)
{
  return aLhs.get() == aRhs.get();
}

template<class T, class U>
inline bool
operator!=(const StaticAutoPtr<T>& aLhs, const StaticAutoPtr<U>& aRhs)
{
  return !(aLhs == aRhs);
}

REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr<T>&, const U*,
                             lhs.get() == rhs, class T, class U)

REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr<T>&, U*,
                             lhs.get() == rhs, class T, class U)

// Let us compare StaticAutoPtr to 0.
REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr<T>&, StaticPtr_internal::Zero*,
                             lhs.get() == nullptr, class T)

// StaticRefPtr (in)equality operators

template<class T, class U>
inline bool
operator==(const StaticRefPtr<T>& aLhs, const StaticRefPtr<U>& aRhs)
{
  return aLhs.get() == aRhs.get();
}

template<class T, class U>
inline bool
operator!=(const StaticRefPtr<T>& aLhs, const StaticRefPtr<U>& aRhs)
{
  return !(aLhs == aRhs);
}

REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr<T>&, const U*,
                             lhs.get() == rhs, class T, class U)

REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr<T>&, U*,
                             lhs.get() == rhs, class T, class U)

// Let us compare StaticRefPtr to 0.
REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr<T>&, StaticPtr_internal::Zero*,
                             lhs.get() == nullptr, class T)

#undef REFLEXIVE_EQUALITY_OPERATORS

} // namespace mozilla

// Declared in mozilla/RefPtr.h
template<class T> template<class U>
RefPtr<T>::RefPtr(const mozilla::StaticRefPtr<U>& aOther)
  : RefPtr(aOther.get())
{}

template<class T> template<class U>
RefPtr<T>&
RefPtr<T>::operator=(const mozilla::StaticRefPtr<U>& aOther)
{
  return operator=(aOther.get());
}

#endif