/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * 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 js_Utility_h
#define js_Utility_h

#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/Compiler.h"
#include "mozilla/Move.h"
#include "mozilla/TemplateLib.h"
#include "mozilla/UniquePtr.h"

#include <stdlib.h>
#include <string.h>

#ifdef JS_OOM_DO_BACKTRACES
#include <execinfo.h>
#include <stdio.h>
#endif

#include "jstypes.h"

#include "mozmemory.h"

/* The public JS engine namespace. */
namespace JS {}

/* The mozilla-shared reusable template/utility namespace. */
namespace mozilla {}

/* The private JS engine namespace. */
namespace js {}

#define JS_STATIC_ASSERT(cond)           static_assert(cond, "JS_STATIC_ASSERT")
#define JS_STATIC_ASSERT_IF(cond, expr)  MOZ_STATIC_ASSERT_IF(cond, expr, "JS_STATIC_ASSERT_IF")

extern MOZ_NORETURN MOZ_COLD JS_PUBLIC_API(void)
JS_Assert(const char* s, const char* file, int ln);

/*
 * Custom allocator support for SpiderMonkey
 */
#if defined JS_USE_CUSTOM_ALLOCATOR
# include "jscustomallocator.h"
#else

namespace js {

/*
 * Thread types are used to tag threads for certain kinds of testing (see
 * below), and also used to characterize threads in the thread scheduler (see
 * js/src/vm/HelperThreads.cpp).
 *
 * Please update oom::FirstThreadTypeToTest and oom::LastThreadTypeToTest when
 * adding new thread types.
 */
enum ThreadType {
    THREAD_TYPE_NONE = 0,       // 0
    THREAD_TYPE_MAIN,           // 1
    THREAD_TYPE_WASM,           // 2
    THREAD_TYPE_ION,            // 3
    THREAD_TYPE_PARSE,          // 4
    THREAD_TYPE_COMPRESS,       // 5
    THREAD_TYPE_GCHELPER,       // 6
    THREAD_TYPE_GCPARALLEL,     // 7
    THREAD_TYPE_PROMISE_TASK,   // 8
    THREAD_TYPE_ION_FREE,       // 9
    THREAD_TYPE_WASM_TIER2,     // 10
    THREAD_TYPE_WORKER,         // 11
    THREAD_TYPE_CURRENT,        // 12 -- Special code to only track the current thread.
    THREAD_TYPE_MAX             // Used to check shell function arguments
};

namespace oom {

/*
 * Theads are tagged only in certain debug contexts.  Notably, to make testing
 * OOM in certain helper threads more effective, we allow restricting the OOM
 * testing to a certain helper thread type. This allows us to fail e.g. in
 * off-thread script parsing without causing an OOM in the active thread first.
 *
 * Getter/Setter functions to encapsulate mozilla::ThreadLocal, implementation
 * is in jsutil.cpp.
 */
# if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)

// Define the range of threads tested by simulated OOM testing and the
// like. Testing worker threads is not supported.
const ThreadType FirstThreadTypeToTest = THREAD_TYPE_MAIN;
const ThreadType LastThreadTypeToTest = THREAD_TYPE_WASM_TIER2;
const ThreadType WorkerFirstThreadTypeToTest = THREAD_TYPE_CURRENT;
const ThreadType WorkerLastThreadTypeToTest = THREAD_TYPE_CURRENT;

extern bool InitThreadType(void);
extern void SetThreadType(ThreadType);
extern JS_FRIEND_API(uint32_t) GetThreadType(void);
extern JS_FRIEND_API(uint32_t) GetAllocationThreadType(void);
extern JS_FRIEND_API(uint32_t) GetStackCheckThreadType(void);
extern JS_FRIEND_API(uint32_t) GetInterruptCheckThreadType(void);

# else

inline bool InitThreadType(void) { return true; }
inline void SetThreadType(ThreadType t) {};
inline uint32_t GetThreadType(void) { return 0; }
inline uint32_t GetAllocationThreadType(void) { return 0; }
inline uint32_t GetStackCheckThreadType(void) { return 0; }
inline uint32_t GetInterruptCheckThreadType(void) { return 0; }

# endif

} /* namespace oom */
} /* namespace js */

# if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)

#ifdef JS_OOM_BREAKPOINT
#  if defined(_MSC_VER)
static MOZ_NEVER_INLINE void js_failedAllocBreakpoint() { __asm { }; }
#  else
static MOZ_NEVER_INLINE void js_failedAllocBreakpoint() { asm(""); }
#  endif
#define JS_OOM_CALL_BP_FUNC() js_failedAllocBreakpoint()
#else
#define JS_OOM_CALL_BP_FUNC() do {} while(0)
#endif

namespace js {
namespace oom {

/*
 * Out of memory testing support.  We provide various testing functions to
 * simulate OOM conditions and so we can test that they are handled correctly.
 */

extern JS_PUBLIC_DATA(uint32_t) targetThread;
extern JS_PUBLIC_DATA(uint64_t) maxAllocations;
extern JS_PUBLIC_DATA(uint64_t) counter;
extern JS_PUBLIC_DATA(bool) failAlways;

extern void
SimulateOOMAfter(uint64_t allocations, uint32_t thread, bool always);

extern void
ResetSimulatedOOM();

inline bool
IsThreadSimulatingOOM()
{
    return js::oom::targetThread &&
        js::oom::targetThread == js::oom::GetAllocationThreadType();
}

inline bool
IsSimulatedOOMAllocation()
{
    return IsThreadSimulatingOOM() &&
           (counter == maxAllocations || (counter > maxAllocations && failAlways));
}

inline bool
ShouldFailWithOOM()
{
    if (!IsThreadSimulatingOOM()) {
        return false;
    }

    counter++;
    if (IsSimulatedOOMAllocation()) {
        JS_OOM_CALL_BP_FUNC();
        return true;
    }
    return false;
}

inline bool
HadSimulatedOOM() {
    return counter >= maxAllocations;
}

/*
 * Out of stack space testing support, similar to OOM testing functions.
 */

extern JS_PUBLIC_DATA(uint32_t) stackTargetThread;
extern JS_PUBLIC_DATA(uint64_t) maxStackChecks;
extern JS_PUBLIC_DATA(uint64_t) stackCheckCounter;
extern JS_PUBLIC_DATA(bool) stackCheckFailAlways;

extern void
SimulateStackOOMAfter(uint64_t checks, uint32_t thread, bool always);

extern void
ResetSimulatedStackOOM();

inline bool
IsThreadSimulatingStackOOM()
{
    return js::oom::stackTargetThread &&
        js::oom::stackTargetThread == js::oom::GetStackCheckThreadType();
}

inline bool
IsSimulatedStackOOMCheck()
{
    return IsThreadSimulatingStackOOM() &&
           (stackCheckCounter == maxStackChecks || (stackCheckCounter > maxStackChecks && stackCheckFailAlways));
}

inline bool
ShouldFailWithStackOOM()
{
    if (!IsThreadSimulatingStackOOM()) {
        return false;
    }

    stackCheckCounter++;
    if (IsSimulatedStackOOMCheck()) {
        JS_OOM_CALL_BP_FUNC();
        return true;
    }
    return false;
}

inline bool
HadSimulatedStackOOM()
{
    return stackCheckCounter >= maxStackChecks;
}

/*
 * Interrupt testing support, similar to OOM testing functions.
 */

extern JS_PUBLIC_DATA(uint32_t) interruptTargetThread;
extern JS_PUBLIC_DATA(uint64_t) maxInterruptChecks;
extern JS_PUBLIC_DATA(uint64_t) interruptCheckCounter;
extern JS_PUBLIC_DATA(bool) interruptCheckFailAlways;

extern void
SimulateInterruptAfter(uint64_t checks, uint32_t thread, bool always);

extern void
ResetSimulatedInterrupt();

inline bool
IsThreadSimulatingInterrupt()
{
    return js::oom::interruptTargetThread &&
        js::oom::interruptTargetThread == js::oom::GetInterruptCheckThreadType();
}

inline bool
IsSimulatedInterruptCheck()
{
    return IsThreadSimulatingInterrupt() &&
           (interruptCheckCounter == maxInterruptChecks || (interruptCheckCounter > maxInterruptChecks && interruptCheckFailAlways));
}

inline bool
ShouldFailWithInterrupt()
{
    if (!IsThreadSimulatingInterrupt()) {
        return false;
    }

    interruptCheckCounter++;
    if (IsSimulatedInterruptCheck()) {
        JS_OOM_CALL_BP_FUNC();
        return true;
    }
    return false;
}

inline bool
HadSimulatedInterrupt()
{
    return interruptCheckCounter >= maxInterruptChecks;
}

} /* namespace oom */
} /* namespace js */

#  define JS_OOM_POSSIBLY_FAIL()                                              \
    do {                                                                      \
        if (js::oom::ShouldFailWithOOM())                                     \
            return nullptr;                                                   \
    } while (0)

#  define JS_OOM_POSSIBLY_FAIL_BOOL()                                         \
    do {                                                                      \
        if (js::oom::ShouldFailWithOOM())                                     \
            return false;                                                     \
    } while (0)

#  define JS_STACK_OOM_POSSIBLY_FAIL()                                        \
    do {                                                                      \
        if (js::oom::ShouldFailWithStackOOM())                                \
            return false;                                                     \
    } while (0)

#  define JS_STACK_OOM_POSSIBLY_FAIL_REPORT()                                 \
    do {                                                                      \
        if (js::oom::ShouldFailWithStackOOM()) {                              \
            ReportOverRecursed(cx);                                           \
            return false;                                                     \
        }                                                                     \
    } while (0)

#  define JS_INTERRUPT_POSSIBLY_FAIL()                                        \
    do {                                                                      \
        if (MOZ_UNLIKELY(js::oom::ShouldFailWithInterrupt())) {               \
            cx->requestInterrupt(js::InterruptReason::CallbackUrgent);        \
            return cx->handleInterrupt();                                     \
        }                                                                     \
    } while (0)

# else

#  define JS_OOM_POSSIBLY_FAIL() do {} while(0)
#  define JS_OOM_POSSIBLY_FAIL_BOOL() do {} while(0)
#  define JS_STACK_OOM_POSSIBLY_FAIL() do {} while(0)
#  define JS_STACK_OOM_POSSIBLY_FAIL_REPORT() do {} while(0)
#  define JS_INTERRUPT_POSSIBLY_FAIL() do {} while(0)
namespace js {
namespace oom {
static inline bool IsSimulatedOOMAllocation() { return false; }
static inline bool ShouldFailWithOOM() { return false; }
} /* namespace oom */
} /* namespace js */

# endif /* DEBUG || JS_OOM_BREAKPOINT */

namespace js {

/* Disable OOM testing in sections which are not OOM safe. */
struct MOZ_RAII JS_PUBLIC_DATA(AutoEnterOOMUnsafeRegion)
{
    MOZ_NORETURN MOZ_COLD void crash(const char* reason);
    MOZ_NORETURN MOZ_COLD void crash(size_t size, const char* reason);

    using AnnotateOOMAllocationSizeCallback = void(*)(size_t);
    static AnnotateOOMAllocationSizeCallback annotateOOMSizeCallback;
    static void setAnnotateOOMAllocationSizeCallback(AnnotateOOMAllocationSizeCallback callback) {
        annotateOOMSizeCallback = callback;
    }

#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
    AutoEnterOOMUnsafeRegion()
      : oomEnabled_(oom::IsThreadSimulatingOOM() && oom::maxAllocations != UINT64_MAX),
        oomAfter_(0)
    {
        if (oomEnabled_) {
            MOZ_ALWAYS_TRUE(owner_.compareExchange(nullptr, this));
            oomAfter_ = int64_t(oom::maxAllocations) - int64_t(oom::counter);
            oom::maxAllocations = UINT64_MAX;
        }
    }

    ~AutoEnterOOMUnsafeRegion() {
        if (oomEnabled_) {
            MOZ_ASSERT(oom::maxAllocations == UINT64_MAX);
            int64_t maxAllocations = int64_t(oom::counter) + oomAfter_;
            MOZ_ASSERT(maxAllocations >= 0,
                       "alloc count + oom limit exceeds range, your oom limit is probably too large");
            oom::maxAllocations = uint64_t(maxAllocations);
            MOZ_ALWAYS_TRUE(owner_.compareExchange(this, nullptr));
        }
    }

  private:
    // Used to catch concurrent use from other threads.
    static mozilla::Atomic<AutoEnterOOMUnsafeRegion*> owner_;

    bool oomEnabled_;
    int64_t oomAfter_;
#endif
};

} /* namespace js */

// Malloc allocation.

namespace js {

extern JS_PUBLIC_DATA(arena_id_t) MallocArena;
extern JS_PUBLIC_DATA(arena_id_t) ArrayBufferContentsArena;

extern void InitMallocAllocator();
extern void ShutDownMallocAllocator();

} /* namespace js */

static inline void* js_arena_malloc(arena_id_t arena, size_t bytes)
{
    JS_OOM_POSSIBLY_FAIL();
    return moz_arena_malloc(arena, bytes);
}

static inline void* js_malloc(size_t bytes)
{
    return js_arena_malloc(js::MallocArena, bytes);
}

static inline void* js_arena_calloc(arena_id_t arena, size_t nmemb, size_t size)
{
    JS_OOM_POSSIBLY_FAIL();
    return moz_arena_calloc(arena, nmemb, size);
}

static inline void* js_calloc(size_t bytes)
{
    return js_arena_calloc(js::MallocArena, bytes, 1);
}

static inline void* js_calloc(size_t nmemb, size_t size)
{
    return js_arena_calloc(js::MallocArena, nmemb, size);
}

static inline void* js_arena_realloc(arena_id_t arena, void* p, size_t bytes)
{
    // realloc() with zero size is not portable, as some implementations may
    // return nullptr on success and free |p| for this.  We assume nullptr
    // indicates failure and that |p| is still valid.
    MOZ_ASSERT(bytes != 0);

    JS_OOM_POSSIBLY_FAIL();
    return moz_arena_realloc(arena, p, bytes);
}

static inline void* js_realloc(void* p, size_t bytes)
{
    return js_arena_realloc(js::MallocArena, p, bytes);
}

static inline void js_free(void* p)
{
    // TODO: This should call |moz_arena_free(js::MallocArena, p)| but we
    // currently can't enforce that all memory freed here was allocated by
    // js_malloc().
    free(p);
}
#endif/* JS_USE_CUSTOM_ALLOCATOR */

#include <new>

/*
 * Low-level memory management in SpiderMonkey:
 *
 *  ** Do not use the standard malloc/free/realloc: SpiderMonkey allows these
 *     to be redefined (via JS_USE_CUSTOM_ALLOCATOR) and Gecko even #define's
 *     these symbols.
 *
 *  ** Do not use the builtin C++ operator new and delete: these throw on
 *     error and we cannot override them not to.
 *
 * Allocation:
 *
 * - If the lifetime of the allocation is tied to the lifetime of a GC-thing
 *   (that is, finalizing the GC-thing will free the allocation), call one of
 *   the following functions:
 *
 *     JSContext::{pod_malloc,pod_calloc,pod_realloc}
 *     Zone::{pod_malloc,pod_calloc,pod_realloc}
 *
 *   These functions accumulate the number of bytes allocated which is used as
 *   part of the GC-triggering heuristics.
 *
 *   The difference between the JSContext and Zone versions is that the
 *   cx version report an out-of-memory error on OOM. (This follows from the
 *   general SpiderMonkey idiom that a JSContext-taking function reports its
 *   own errors.)
 *
 *   If you don't want to report an error on failure, there are maybe_ versions
 *   of these methods available too, e.g. maybe_pod_malloc.
 *
 *   The methods above use templates to allow allocating memory suitable for an
 *   array of a given type and number of elements. There are _with_extra
 *   versions to allow allocating an area of memory which is larger by a
 *   specified number of bytes, e.g. pod_malloc_with_extra.
 *
 *   These methods are available on a JSRuntime, but calling them is
 *   discouraged. Memory attributed to a runtime can only be reclaimed by full
 *   GCs, and we try to avoid those where possible.
 *
 * - Otherwise, use js_malloc/js_realloc/js_calloc/js_new
 *
 * Deallocation:
 *
 * - Ordinarily, use js_free/js_delete.
 *
 * - For deallocations during GC finalization, use one of the following
 *   operations on the FreeOp provided to the finalizer:
 *
 *     FreeOp::{free_,delete_}
 */

/*
 * Given a class which should provide a 'new' method, add
 * JS_DECLARE_NEW_METHODS (see js::MallocProvider for an example).
 *
 * Note: Do not add a ; at the end of a use of JS_DECLARE_NEW_METHODS,
 * or the build will break.
 */
#define JS_DECLARE_NEW_METHODS(NEWNAME, ALLOCATOR, QUALIFIERS) \
    template <class T, typename... Args> \
    QUALIFIERS T * \
    NEWNAME(Args&&... args) MOZ_HEAP_ALLOCATOR { \
        void* memory = ALLOCATOR(sizeof(T)); \
        return MOZ_LIKELY(memory) \
            ? new(memory) T(std::forward<Args>(args)...) \
            : nullptr; \
    }

/*
 * Given a class which should provide 'make' methods, add
 * JS_DECLARE_MAKE_METHODS (see js::MallocProvider for an example).  This
 * method is functionally the same as JS_DECLARE_NEW_METHODS: it just declares
 * methods that return mozilla::UniquePtr instances that will singly-manage
 * ownership of the created object.
 *
 * Note: Do not add a ; at the end of a use of JS_DECLARE_MAKE_METHODS,
 * or the build will break.
 */
#define JS_DECLARE_MAKE_METHODS(MAKENAME, NEWNAME, QUALIFIERS)\
    template <class T, typename... Args> \
    QUALIFIERS mozilla::UniquePtr<T, JS::DeletePolicy<T>> \
    MAKENAME(Args&&... args) MOZ_HEAP_ALLOCATOR { \
        T* ptr = NEWNAME<T>(std::forward<Args>(args)...); \
        return mozilla::UniquePtr<T, JS::DeletePolicy<T>>(ptr); \
    }

JS_DECLARE_NEW_METHODS(js_new, js_malloc, static MOZ_ALWAYS_INLINE)

namespace js {

/*
 * Calculate the number of bytes needed to allocate |numElems| contiguous
 * instances of type |T|.  Return false if the calculation overflowed.
 */
template <typename T>
MOZ_MUST_USE inline bool
CalculateAllocSize(size_t numElems, size_t* bytesOut)
{
    *bytesOut = numElems * sizeof(T);
    return (numElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) == 0;
}

/*
 * Calculate the number of bytes needed to allocate a single instance of type
 * |T| followed by |numExtra| contiguous instances of type |Extra|.  Return
 * false if the calculation overflowed.
 */
template <typename T, typename Extra>
MOZ_MUST_USE inline bool
CalculateAllocSizeWithExtra(size_t numExtra, size_t* bytesOut)
{
    *bytesOut = sizeof(T) + numExtra * sizeof(Extra);
    return (numExtra & mozilla::tl::MulOverflowMask<sizeof(Extra)>::value) == 0 &&
           *bytesOut >= sizeof(T);
}

} /* namespace js */

template <class T>
static MOZ_ALWAYS_INLINE void
js_delete(const T* p)
{
    if (p) {
        p->~T();
        js_free(const_cast<T*>(p));
    }
}

template<class T>
static MOZ_ALWAYS_INLINE void
js_delete_poison(const T* p)
{
    if (p) {
        p->~T();
        memset(static_cast<void*>(const_cast<T*>(p)), 0x3B, sizeof(T));
        js_free(const_cast<T*>(p));
    }
}

template <class T>
static MOZ_ALWAYS_INLINE T*
js_pod_arena_malloc(arena_id_t arena, size_t numElems)
{
  size_t bytes;
  if (MOZ_UNLIKELY(!js::CalculateAllocSize<T>(numElems, &bytes))) {
    return nullptr;
  }
  return static_cast<T*>(js_arena_malloc(arena, bytes));
}

template <class T>
static MOZ_ALWAYS_INLINE T*
js_pod_malloc(size_t numElems)
{
    return js_pod_arena_malloc<T>(js::MallocArena, numElems);
}

template <class T>
static MOZ_ALWAYS_INLINE T*
js_pod_arena_calloc(arena_id_t arena, size_t numElems)
{
    size_t bytes;
    if (MOZ_UNLIKELY(!js::CalculateAllocSize<T>(numElems, &bytes))) {
        return nullptr;
    }
    return static_cast<T*>(js_arena_calloc(arena, bytes, 1));
}

template <class T>
static MOZ_ALWAYS_INLINE T*
js_pod_calloc(size_t numElems)
{
    return js_pod_arena_calloc<T>(js::MallocArena, numElems);
}

template <class T>
static MOZ_ALWAYS_INLINE T*
js_pod_realloc(T* prior, size_t oldSize, size_t newSize)
{
    MOZ_ASSERT(!(oldSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value));
    size_t bytes;
    if (MOZ_UNLIKELY(!js::CalculateAllocSize<T>(newSize, &bytes))) {
        return nullptr;
    }
    return static_cast<T*>(js_realloc(prior, bytes));
}

namespace JS {

template<typename T>
struct DeletePolicy
{
    constexpr DeletePolicy() {}

    template<typename U>
    MOZ_IMPLICIT DeletePolicy(DeletePolicy<U> other,
                              typename mozilla::EnableIf<mozilla::IsConvertible<U*, T*>::value,
                                                         int>::Type dummy = 0)
    {}

    void operator()(const T* ptr) {
        js_delete(const_cast<T*>(ptr));
    }
};

struct FreePolicy
{
    void operator()(const void* ptr) {
        js_free(const_cast<void*>(ptr));
    }
};

typedef mozilla::UniquePtr<char[], JS::FreePolicy> UniqueChars;
typedef mozilla::UniquePtr<char16_t[], JS::FreePolicy> UniqueTwoByteChars;

} // namespace JS

/* sixgill annotation defines */
#ifndef HAVE_STATIC_ANNOTATIONS
# define HAVE_STATIC_ANNOTATIONS
# ifdef XGILL_PLUGIN
#  define STATIC_PRECONDITION(COND)         __attribute__((precondition(#COND)))
#  define STATIC_PRECONDITION_ASSUME(COND)  __attribute__((precondition_assume(#COND)))
#  define STATIC_POSTCONDITION(COND)        __attribute__((postcondition(#COND)))
#  define STATIC_POSTCONDITION_ASSUME(COND) __attribute__((postcondition_assume(#COND)))
#  define STATIC_INVARIANT(COND)            __attribute__((invariant(#COND)))
#  define STATIC_INVARIANT_ASSUME(COND)     __attribute__((invariant_assume(#COND)))
#  define STATIC_ASSUME(COND)                        \
  JS_BEGIN_MACRO                                     \
    __attribute__((assume_static(#COND), unused))    \
    int STATIC_PASTE1(assume_static_, __COUNTER__);  \
  JS_END_MACRO
# else /* XGILL_PLUGIN */
#  define STATIC_PRECONDITION(COND)          /* nothing */
#  define STATIC_PRECONDITION_ASSUME(COND)   /* nothing */
#  define STATIC_POSTCONDITION(COND)         /* nothing */
#  define STATIC_POSTCONDITION_ASSUME(COND)  /* nothing */
#  define STATIC_INVARIANT(COND)             /* nothing */
#  define STATIC_INVARIANT_ASSUME(COND)      /* nothing */
#  define STATIC_ASSUME(COND)          JS_BEGIN_MACRO /* nothing */ JS_END_MACRO
# endif /* XGILL_PLUGIN */
# define STATIC_SKIP_INFERENCE STATIC_INVARIANT(skip_inference())
#endif /* HAVE_STATIC_ANNOTATIONS */

#endif /* js_Utility_h */
