/* -*- 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 #include #ifdef JS_OOM_DO_BACKTRACES #include #include #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 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 /* * 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 \ QUALIFIERS T * \ NEWNAME(Args&&... args) MOZ_HEAP_ALLOCATOR { \ void* memory = ALLOCATOR(sizeof(T)); \ return MOZ_LIKELY(memory) \ ? new(memory) T(std::forward(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 \ QUALIFIERS mozilla::UniquePtr> \ MAKENAME(Args&&... args) MOZ_HEAP_ALLOCATOR { \ T* ptr = NEWNAME(std::forward(args)...); \ return mozilla::UniquePtr>(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 MOZ_MUST_USE inline bool CalculateAllocSize(size_t numElems, size_t* bytesOut) { *bytesOut = numElems * sizeof(T); return (numElems & mozilla::tl::MulOverflowMask::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 MOZ_MUST_USE inline bool CalculateAllocSizeWithExtra(size_t numExtra, size_t* bytesOut) { *bytesOut = sizeof(T) + numExtra * sizeof(Extra); return (numExtra & mozilla::tl::MulOverflowMask::value) == 0 && *bytesOut >= sizeof(T); } } /* namespace js */ template static MOZ_ALWAYS_INLINE void js_delete(const T* p) { if (p) { p->~T(); js_free(const_cast(p)); } } template static MOZ_ALWAYS_INLINE void js_delete_poison(const T* p) { if (p) { p->~T(); memset(static_cast(const_cast(p)), 0x3B, sizeof(T)); js_free(const_cast(p)); } } template static MOZ_ALWAYS_INLINE T* js_pod_arena_malloc(arena_id_t arena, size_t numElems) { size_t bytes; if (MOZ_UNLIKELY(!js::CalculateAllocSize(numElems, &bytes))) { return nullptr; } return static_cast(js_arena_malloc(arena, bytes)); } template static MOZ_ALWAYS_INLINE T* js_pod_malloc(size_t numElems) { return js_pod_arena_malloc(js::MallocArena, numElems); } template static MOZ_ALWAYS_INLINE T* js_pod_arena_calloc(arena_id_t arena, size_t numElems) { size_t bytes; if (MOZ_UNLIKELY(!js::CalculateAllocSize(numElems, &bytes))) { return nullptr; } return static_cast(js_arena_calloc(arena, bytes, 1)); } template static MOZ_ALWAYS_INLINE T* js_pod_calloc(size_t numElems) { return js_pod_arena_calloc(js::MallocArena, numElems); } template static MOZ_ALWAYS_INLINE T* js_pod_realloc(T* prior, size_t oldSize, size_t newSize) { MOZ_ASSERT(!(oldSize & mozilla::tl::MulOverflowMask::value)); size_t bytes; if (MOZ_UNLIKELY(!js::CalculateAllocSize(newSize, &bytes))) { return nullptr; } return static_cast(js_realloc(prior, bytes)); } namespace JS { template struct DeletePolicy { constexpr DeletePolicy() {} template MOZ_IMPLICIT DeletePolicy(DeletePolicy other, typename mozilla::EnableIf::value, int>::Type dummy = 0) {} void operator()(const T* ptr) { js_delete(const_cast(ptr)); } }; struct FreePolicy { void operator()(const void* ptr) { js_free(const_cast(ptr)); } }; typedef mozilla::UniquePtr UniqueChars; typedef mozilla::UniquePtr 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 */