diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/public | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/public')
136 files changed, 35496 insertions, 0 deletions
diff --git a/js/public/AllocPolicy.h b/js/public/AllocPolicy.h new file mode 100644 index 0000000000..7d8388b140 --- /dev/null +++ b/js/public/AllocPolicy.h @@ -0,0 +1,248 @@ +/* -*- 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/. */ + +/* + * JS allocation policies. + * + * The allocators here are for system memory with lifetimes which are not + * managed by the GC. See the comment at the top of vm/MallocProvider.h. + */ + +#ifndef js_AllocPolicy_h +#define js_AllocPolicy_h + +#include "js/TypeDecls.h" +#include "js/Utility.h" + +extern MOZ_COLD JS_PUBLIC_API void JS_ReportOutOfMemory(JSContext* cx); + +namespace js { + +class FrontendContext; + +enum class AllocFunction { Malloc, Calloc, Realloc }; + +/* Base class allocation policies providing allocation methods. */ +class AllocPolicyBase { + public: + template <typename T> + T* maybe_pod_arena_malloc(arena_id_t arenaId, size_t numElems) { + return js_pod_arena_malloc<T>(arenaId, numElems); + } + template <typename T> + T* maybe_pod_arena_calloc(arena_id_t arenaId, size_t numElems) { + return js_pod_arena_calloc<T>(arenaId, numElems); + } + template <typename T> + T* maybe_pod_arena_realloc(arena_id_t arenaId, T* p, size_t oldSize, + size_t newSize) { + return js_pod_arena_realloc<T>(arenaId, p, oldSize, newSize); + } + template <typename T> + T* pod_arena_malloc(arena_id_t arenaId, size_t numElems) { + return maybe_pod_arena_malloc<T>(arenaId, numElems); + } + template <typename T> + T* pod_arena_calloc(arena_id_t arenaId, size_t numElems) { + return maybe_pod_arena_calloc<T>(arenaId, numElems); + } + template <typename T> + T* pod_arena_realloc(arena_id_t arenaId, T* p, size_t oldSize, + size_t newSize) { + return maybe_pod_arena_realloc<T>(arenaId, p, oldSize, newSize); + } + + template <typename T> + T* maybe_pod_malloc(size_t numElems) { + return maybe_pod_arena_malloc<T>(js::MallocArena, numElems); + } + template <typename T> + T* maybe_pod_calloc(size_t numElems) { + return maybe_pod_arena_calloc<T>(js::MallocArena, numElems); + } + template <typename T> + T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) { + return maybe_pod_arena_realloc<T>(js::MallocArena, p, oldSize, newSize); + } + template <typename T> + T* pod_malloc(size_t numElems) { + return pod_arena_malloc<T>(js::MallocArena, numElems); + } + template <typename T> + T* pod_calloc(size_t numElems) { + return pod_arena_calloc<T>(js::MallocArena, numElems); + } + template <typename T> + T* pod_realloc(T* p, size_t oldSize, size_t newSize) { + return pod_arena_realloc<T>(js::MallocArena, p, oldSize, newSize); + } + + template <typename T> + void free_(T* p, size_t numElems = 0) { + js_free(p); + } +}; + +/* Policy for using system memory functions and doing no error reporting. */ +class SystemAllocPolicy : public AllocPolicyBase { + public: + void reportAllocOverflow() const {} + bool checkSimulatedOOM() const { return !js::oom::ShouldFailWithOOM(); } +}; + +MOZ_COLD JS_PUBLIC_API void ReportOutOfMemory(JSContext* cx); +MOZ_COLD JS_PUBLIC_API void ReportOutOfMemory(FrontendContext* fc); + +/* + * Allocation policy that calls the system memory functions and reports errors + * to the context. Since the JSContext given on construction is stored for + * the lifetime of the container, this policy may only be used for containers + * whose lifetime is a shorter than the given JSContext. + * + * FIXME bug 647103 - rewrite this in terms of temporary allocation functions, + * not the system ones. + */ +class JS_PUBLIC_API TempAllocPolicy : public AllocPolicyBase { + // Type tag for context_bits_ + static constexpr uintptr_t JsContextTag = 0x1; + + // Either a JSContext* (if JsContextTag is set), or FrontendContext* + uintptr_t const context_bits_; + + MOZ_ALWAYS_INLINE bool hasJSContext() const { + return (context_bits_ & JsContextTag) == JsContextTag; + } + + MOZ_ALWAYS_INLINE JSContext* cx() const { + MOZ_ASSERT(hasJSContext()); + return reinterpret_cast<JSContext*>(context_bits_ ^ JsContextTag); + } + + MOZ_ALWAYS_INLINE FrontendContext* fc() const { + MOZ_ASSERT(!hasJSContext()); + return reinterpret_cast<FrontendContext*>(context_bits_); + } + + /* + * Non-inline helper to call JSRuntime::onOutOfMemory with minimal + * code bloat. + */ + void* onOutOfMemory(arena_id_t arenaId, AllocFunction allocFunc, + size_t nbytes, void* reallocPtr = nullptr); + + template <typename T> + T* onOutOfMemoryTyped(arena_id_t arenaId, AllocFunction allocFunc, + size_t numElems, void* reallocPtr = nullptr) { + size_t bytes; + if (MOZ_UNLIKELY(!CalculateAllocSize<T>(numElems, &bytes))) { + return nullptr; + } + return static_cast<T*>( + onOutOfMemory(arenaId, allocFunc, bytes, reallocPtr)); + } + +#ifdef DEBUG + void assertNotJSContextOnHelperThread() const; +#else + MOZ_ALWAYS_INLINE void assertNotJSContextOnHelperThread() const {} +#endif /* DEBUG */ + + public: + MOZ_IMPLICIT TempAllocPolicy(JSContext* cx) + : context_bits_(uintptr_t(cx) | JsContextTag) { + MOZ_ASSERT((uintptr_t(cx) & JsContextTag) == 0); + } + MOZ_IMPLICIT TempAllocPolicy(FrontendContext* fc) + : context_bits_(uintptr_t(fc)) { + MOZ_ASSERT((uintptr_t(fc) & JsContextTag) == 0); + } + + template <typename T> + T* pod_arena_malloc(arena_id_t arenaId, size_t numElems) { + assertNotJSContextOnHelperThread(); + T* p = this->maybe_pod_arena_malloc<T>(arenaId, numElems); + if (MOZ_UNLIKELY(!p)) { + p = onOutOfMemoryTyped<T>(arenaId, AllocFunction::Malloc, numElems); + } + return p; + } + + template <typename T> + T* pod_arena_calloc(arena_id_t arenaId, size_t numElems) { + assertNotJSContextOnHelperThread(); + T* p = this->maybe_pod_arena_calloc<T>(arenaId, numElems); + if (MOZ_UNLIKELY(!p)) { + p = onOutOfMemoryTyped<T>(arenaId, AllocFunction::Calloc, numElems); + } + return p; + } + + template <typename T> + T* pod_arena_realloc(arena_id_t arenaId, T* prior, size_t oldSize, + size_t newSize) { + assertNotJSContextOnHelperThread(); + T* p2 = this->maybe_pod_arena_realloc<T>(arenaId, prior, oldSize, newSize); + if (MOZ_UNLIKELY(!p2)) { + p2 = onOutOfMemoryTyped<T>(arenaId, AllocFunction::Realloc, newSize, + prior); + } + return p2; + } + + template <typename T> + T* pod_malloc(size_t numElems) { + return pod_arena_malloc<T>(js::MallocArena, numElems); + } + + template <typename T> + T* pod_calloc(size_t numElems) { + return pod_arena_calloc<T>(js::MallocArena, numElems); + } + + template <typename T> + T* pod_realloc(T* prior, size_t oldSize, size_t newSize) { + return pod_arena_realloc<T>(js::MallocArena, prior, oldSize, newSize); + } + + template <typename T> + void free_(T* p, size_t numElems = 0) { + js_free(p); + } + + void reportAllocOverflow() const; + + bool checkSimulatedOOM() const { + if (js::oom::ShouldFailWithOOM()) { + if (hasJSContext()) { + ReportOutOfMemory(cx()); + } else { + ReportOutOfMemory(fc()); + } + return false; + } + + return true; + } +}; + +/* + * A replacement for MallocAllocPolicy that allocates in the JS heap and adds no + * extra behaviours. + * + * This is currently used for allocating source buffers for parsing. Since these + * are temporary and will not be freed by GC, the memory is not tracked by the + * usual accounting. + */ +class MallocAllocPolicy : public AllocPolicyBase { + public: + void reportAllocOverflow() const {} + + [[nodiscard]] bool checkSimulatedOOM() const { return true; } +}; + +} /* namespace js */ + +#endif /* js_AllocPolicy_h */ diff --git a/js/public/AllocationLogging.h b/js/public/AllocationLogging.h new file mode 100644 index 0000000000..a714819344 --- /dev/null +++ b/js/public/AllocationLogging.h @@ -0,0 +1,75 @@ +/* -*- 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/. */ + +/* + * Embedder-supplied tracking of the allocation and deallocation of various + * non-garbage-collected objects in SpiderMonkey. + * + * This functionality is intended to track allocation of non-user-visible + * structures, for debugging the C++ of an embedding. It is not intended to + * give the end user visibility into allocations in JS. Instead see + * AllocationRecording.h for such functionality. + */ + +#ifndef js_AllocationLogging_h +#define js_AllocationLogging_h + +#include <stdint.h> // uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +namespace JS { + +using LogCtorDtor = void (*)(void*, const char*, uint32_t); + +/** + * Set global functions used to monitor classes to highlight leaks. + * + * For each C++ class that uses these mechanisms, the allocation of an instance + * will log the constructor call, and its subsequent deallocation will log the + * destructor call. If only the former occurs, the instance/allocation is + * leaked. With carefully-written logging functions, this can be used to debug + * the origin of the leaks. + */ +extern JS_PUBLIC_API void SetLogCtorDtorFunctions(LogCtorDtor ctor, + LogCtorDtor dtor); + +/** + * Log the allocation of |self|, having a type uniquely identified by the string + * |type|, with allocation size |sz|. + * + * You generally should use |JS_COUNT_CTOR| and |JS_COUNT_DTOR| instead of + * using this function directly. + */ +extern JS_PUBLIC_API void LogCtor(void* self, const char* type, uint32_t sz); + +/** + * Log the deallocation of |self|, having a type uniquely identified by the + * string |type|, with allocation size |sz|. + * + * You generally should use |JS_COUNT_CTOR| and |JS_COUNT_DTOR| instead of + * using this function directly. + */ +extern JS_PUBLIC_API void LogDtor(void* self, const char* type, uint32_t sz); + +/** + * Within each non-delegating constructor of a |Class|, use + * |JS_COUNT_CTOR(Class);| to log the allocation of |this|. (If you do this in + * delegating constructors, you might count a single allocation multiple times.) + */ +#define JS_COUNT_CTOR(Class) \ + (::JS::LogCtor(static_cast<void*>(this), #Class, sizeof(Class))) + +/** + * Within the destructor of a |Class|, use |JS_COUNT_DTOR(Class);| to log the + * deallocation of |this|. + */ +#define JS_COUNT_DTOR(Class) \ + (::JS::LogDtor(static_cast<void*>(this), #Class, sizeof(Class))) + +} // namespace JS + +#endif // js_AllocationLogging_h diff --git a/js/public/AllocationRecording.h b/js/public/AllocationRecording.h new file mode 100644 index 0000000000..942d94b08e --- /dev/null +++ b/js/public/AllocationRecording.h @@ -0,0 +1,73 @@ +/* -*- 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 js_AllocationRecording_h +#define js_AllocationRecording_h + +#include <stdint.h> +#include "jstypes.h" +#include "js/TypeDecls.h" + +namespace JS { + +/** + * This struct holds the information needed to create a profiler marker payload + * that can represent a JS allocation. It translates JS engine specific classes, + * into something that can be used in the profiler. + */ +struct RecordAllocationInfo { + RecordAllocationInfo(const char16_t* typeName, const char* className, + const char16_t* descriptiveTypeName, + const char* coarseType, uint64_t size, bool inNursery) + : typeName(typeName), + className(className), + descriptiveTypeName(descriptiveTypeName), + coarseType(coarseType), + size(size), + inNursery(inNursery) {} + + // These pointers are borrowed from the UbiNode, and can point to live data. + // It is important for the consumers of this struct to correctly + // duplicate the strings to take ownership of them. + const char16_t* typeName; + const char* className; + const char16_t* descriptiveTypeName; + + // The coarseType points to a string literal, so does not need to be + // duplicated. + const char* coarseType; + + // The size in bytes of the allocation. + uint64_t size; + + // Whether or not the allocation is in the nursery or not. + bool inNursery; +}; + +typedef void (*RecordAllocationsCallback)(RecordAllocationInfo&& info); + +/** + * Enable recording JS allocations. This feature hooks into the object creation + * in the JavaScript engine, and reports back the allocation info through the + * callback. This allocation tracking is turned on for all encountered realms. + * The JS Debugger API can also turn on allocation tracking with its own + * probability. If both allocation tracking mechanisms are turned on at the same + * time, the Debugger's probability defers to the EnableRecordingAllocations's + * probability setting. + */ +JS_PUBLIC_API void EnableRecordingAllocations( + JSContext* cx, RecordAllocationsCallback callback, double probability); + +/** + * Turn off JS allocation recording. If any JS Debuggers are also recording + * allocations, then the probability will be reset to the Debugger's desired + * setting. + */ +JS_PUBLIC_API void DisableRecordingAllocations(JSContext* cx); + +} // namespace JS + +#endif /* js_AllocationRecording_h */ diff --git a/js/public/Array.h b/js/public/Array.h new file mode 100644 index 0000000000..f019090167 --- /dev/null +++ b/js/public/Array.h @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* Array-related operations. */ + +#ifndef js_Array_h +#define js_Array_h + +#include <stddef.h> // size_t +#include <stdint.h> // uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/TypeDecls.h" + +namespace JS { + +class HandleValueArray; + +/** + * Create an Array from the current realm with the given contents. + */ +extern JS_PUBLIC_API JSObject* NewArrayObject(JSContext* cx, + const HandleValueArray& contents); + +/** + * Create an Array from the current realm with the given length and allocate + * memory for all its elements. (The elements nonetheless will not exist as + * properties on the returned array until values have been assigned to them.) + */ +extern JS_PUBLIC_API JSObject* NewArrayObject(JSContext* cx, size_t length); + +/** + * Determine whether |value| is an Array object or a wrapper around one. (An + * ES6 proxy whose target is an Array object, e.g. + * |var target = [], handler = {}; Proxy.revocable(target, handler).proxy|, is + * not considered to be an Array.) + * + * On success set |*isArray| accordingly and return true; on failure return + * false. + */ +extern JS_PUBLIC_API bool IsArrayObject(JSContext* cx, Handle<Value> value, + bool* isArray); + +/** + * Determine whether |obj| is an Array object or a wrapper around one. (An + * ES6 proxy whose target is an Array object, e.g. + * |var target = [], handler = {}; Proxy.revocable(target, handler).proxy|, is + * not considered to be an Array.) + * + * On success set |*isArray| accordingly and return true; on failure return + * false. + */ +extern JS_PUBLIC_API bool IsArrayObject(JSContext* cx, Handle<JSObject*> obj, + bool* isArray); + +/** + * Store |*lengthp = ToLength(obj.length)| and return true on success, else + * return false. + * + * If the length does not fit in |uint32_t|, an exception is reported and false + * is returned. + * + * |ToLength| converts its input to an integer usable to index an + * array-like object. + * + * If |obj| is an Array, this overall operation is the same as getting + * |obj.length|. + */ +extern JS_PUBLIC_API bool GetArrayLength(JSContext* cx, Handle<JSObject*> obj, + uint32_t* lengthp); + +/** + * Perform |obj.length = length| as if in strict mode code, with a fast path for + * the case where |obj| is an Array. + * + * This operation is exactly and only assigning to a "length" property. In + * general, it can invoke an existing "length" setter, throw if the property is + * non-writable, or do anything else a property-set operation might do. + */ +extern JS_PUBLIC_API bool SetArrayLength(JSContext* cx, Handle<JSObject*> obj, + uint32_t length); + +/** + * The answer to a successful query as to whether an object is an Array per + * ES6's internal |IsArray| operation (as exposed by |Array.isArray|). + */ +enum class IsArrayAnswer { Array, NotArray, RevokedProxy }; + +/** + * ES6 7.2.2. + * + * Returns false on failure, otherwise returns true and sets |*isArray| + * indicating whether the object passes ECMAScript's IsArray test. This is the + * same test performed by |Array.isArray|. + * + * This is NOT the same as asking whether |obj| is an Array or a wrapper around + * one. If |obj| is a proxy created by |Proxy.revocable()| and has been + * revoked, or if |obj| is a proxy whose target (at any number of hops) is a + * revoked proxy, this method throws a TypeError and returns false. + */ +extern JS_PUBLIC_API bool IsArray(JSContext* cx, Handle<JSObject*> obj, + bool* isArray); + +/** + * Identical to IsArray above, but the nature of the object (if successfully + * determined) is communicated via |*answer|. In particular this method + * returns true and sets |*answer = IsArrayAnswer::RevokedProxy| when called on + * a revoked proxy. + * + * Most users will want the overload above, not this one. + */ +extern JS_PUBLIC_API bool IsArray(JSContext* cx, Handle<JSObject*> obj, + IsArrayAnswer* answer); + +} // namespace JS + +#endif // js_Array_h diff --git a/js/public/ArrayBuffer.h b/js/public/ArrayBuffer.h new file mode 100644 index 0000000000..b4911c819e --- /dev/null +++ b/js/public/ArrayBuffer.h @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* ArrayBuffer functionality. */ + +#ifndef js_ArrayBuffer_h +#define js_ArrayBuffer_h + +#include <stddef.h> // size_t +#include <stdint.h> // uint32_t + +#include "jstypes.h" // JS_PUBLIC_API +#include "js/TypeDecls.h" + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; + +namespace JS { + +class JS_PUBLIC_API AutoRequireNoGC; + +// CREATION + +/** + * Create a new ArrayBuffer with the given byte length. + */ +extern JS_PUBLIC_API JSObject* NewArrayBuffer(JSContext* cx, size_t nbytes); + +/** + * Create a new ArrayBuffer with the given |contents|, which may be null only + * if |nbytes == 0|. |contents| must be allocated compatible with deallocation + * by |JS_free|. + * + * If and only if an ArrayBuffer is successfully created and returned, + * ownership of |contents| is transferred to the new ArrayBuffer. + * + * Care must be taken that |nbytes| bytes of |content| remain valid for the + * duration of this call. In particular, passing the length/pointer of existing + * typed array or ArrayBuffer data is generally unsafe: if a GC occurs during a + * call to this function, it could move those contents to a different location + * and invalidate the provided pointer. + */ +extern JS_PUBLIC_API JSObject* NewArrayBufferWithContents(JSContext* cx, + size_t nbytes, + void* contents); + +/** + * Create a new ArrayBuffer, whose bytes are set to the values of the bytes in + * the provided ArrayBuffer. + * + * |maybeArrayBuffer| is asserted to be non-null. An error is thrown if + * |maybeArrayBuffer| would fail the |IsArrayBufferObject| test given further + * below or if |maybeArrayBuffer| is detached. + * + * |maybeArrayBuffer| may store its contents in any fashion (i.e. it doesn't + * matter whether |maybeArrayBuffer| was allocated using |JS::NewArrayBuffer|, + * |JS::NewExternalArrayBuffer|, or any other ArrayBuffer-allocating function). + * + * The newly-created ArrayBuffer is effectively creatd as if by + * |JS::NewArrayBufferWithContents| passing in |maybeArrayBuffer|'s internal + * data pointer and length, in a manner safe against |maybeArrayBuffer|'s data + * being moved around by the GC. In particular, the new ArrayBuffer will not + * behave like one created for WASM or asm.js, so it *can* be detached. + */ +extern JS_PUBLIC_API JSObject* CopyArrayBuffer( + JSContext* cx, JS::Handle<JSObject*> maybeArrayBuffer); + +using BufferContentsFreeFunc = void (*)(void* contents, void* userData); + +/** + * Create a new ArrayBuffer with the given contents. The contents must not be + * modified by any other code, internal or external. + * + * When the ArrayBuffer is ready to be disposed of, `freeFunc(contents, + * freeUserData)` will be called to release the ArrayBuffer's reference on the + * contents. + * + * `freeFunc()` must not call any JSAPI functions that could cause a garbage + * collection. + * + * The caller must keep the buffer alive until `freeFunc()` is called, or, if + * `freeFunc` is null, until the JSRuntime is destroyed. + * + * The caller must not access the buffer on other threads. The JS engine will + * not allow the buffer to be transferred to other threads. If you try to + * transfer an external ArrayBuffer to another thread, the data is copied to a + * new malloc buffer. `freeFunc()` must be threadsafe, and may be called from + * any thread. + * + * This allows ArrayBuffers to be used with embedder objects that use reference + * counting, for example. In that case the caller is responsible + * for incrementing the reference count before passing the contents to this + * function. This also allows using non-reference-counted contents that must be + * freed with some function other than free(). + */ +extern JS_PUBLIC_API JSObject* NewExternalArrayBuffer( + JSContext* cx, size_t nbytes, void* contents, + BufferContentsFreeFunc freeFunc, void* freeUserData = nullptr); + +/** + * Create a new ArrayBuffer with the given non-null |contents|. + * + * Ownership of |contents| remains with the caller: it isn't transferred to the + * returned ArrayBuffer. Callers of this function *must* ensure that they + * perform these two steps, in this order, to properly relinquish ownership of + * |contents|: + * + * 1. Call |JS::DetachArrayBuffer| on the buffer returned by this function. + * (|JS::DetachArrayBuffer| is generally fallible, but a call under these + * circumstances is guaranteed to succeed.) + * 2. |contents| may be deallocated or discarded consistent with the manner + * in which it was allocated. + * + * Do not simply allow the returned buffer to be garbage-collected before + * deallocating |contents|, because in general there is no way to know *when* + * an object is fully garbage-collected to the point where this would be safe. + */ +extern JS_PUBLIC_API JSObject* NewArrayBufferWithUserOwnedContents( + JSContext* cx, size_t nbytes, void* contents); + +/** + * Create a new mapped ArrayBuffer with the given memory mapped contents. It + * must be legal to free the contents pointer by unmapping it. On success, + * ownership is transferred to the new mapped ArrayBuffer. + */ +extern JS_PUBLIC_API JSObject* NewMappedArrayBufferWithContents(JSContext* cx, + size_t nbytes, + void* contents); + +/** + * Create memory mapped ArrayBuffer contents. + * Caller must take care of closing fd after calling this function. + */ +extern JS_PUBLIC_API void* CreateMappedArrayBufferContents(int fd, + size_t offset, + size_t length); + +/** + * Release the allocated resource of mapped ArrayBuffer contents before the + * object is created. + * If a new object has been created by JS::NewMappedArrayBufferWithContents() + * with this content, then JS::DetachArrayBuffer() should be used instead to + * release the resource used by the object. + */ +extern JS_PUBLIC_API void ReleaseMappedArrayBufferContents(void* contents, + size_t length); + +// TYPE TESTING + +/* + * Check whether obj supports the JS::GetArrayBuffer* APIs. Note that this may + * return false if a security wrapper is encountered that denies the unwrapping. + * If this test succeeds, then it is safe to call the various predicate and + * accessor JSAPI calls defined below. + */ +extern JS_PUBLIC_API bool IsArrayBufferObject(JSObject* obj); + +// PREDICATES + +/** + * Check whether the obj is a detached ArrayBufferObject. Note that this may + * return false if a security wrapper is encountered that denies the + * unwrapping. + */ +extern JS_PUBLIC_API bool IsDetachedArrayBufferObject(JSObject* obj); + +/** + * Check whether the obj is ArrayBufferObject and memory mapped. Note that this + * may return false if a security wrapper is encountered that denies the + * unwrapping. + */ +extern JS_PUBLIC_API bool IsMappedArrayBufferObject(JSObject* obj); + +/** + * Return true if the ArrayBuffer |obj| contains any data, i.e. it is not a + * detached ArrayBuffer. (ArrayBuffer.prototype is not an ArrayBuffer.) + * + * |obj| must have passed a JS::IsArrayBufferObject test, or somehow be known + * that it would pass such a test: it is an ArrayBuffer or a wrapper of an + * ArrayBuffer, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API bool ArrayBufferHasData(JSObject* obj); + +// ACCESSORS + +extern JS_PUBLIC_API JSObject* UnwrapArrayBuffer(JSObject* obj); + +/** + * Attempt to unwrap |obj| as an ArrayBuffer. + * + * If |obj| *is* an ArrayBuffer, return it unwrapped and set |*length| and + * |*data| to weakly refer to the ArrayBuffer's contents. + * + * If |obj| isn't an ArrayBuffer, return nullptr and do not modify |*length| or + * |*data|. + */ +extern JS_PUBLIC_API JSObject* GetObjectAsArrayBuffer(JSObject* obj, + size_t* length, + uint8_t** data); + +/** + * Return the available byte length of an ArrayBuffer. + * + * |obj| must have passed a JS::IsArrayBufferObject test, or somehow be known + * that it would pass such a test: it is an ArrayBuffer or a wrapper of an + * ArrayBuffer, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API size_t GetArrayBufferByteLength(JSObject* obj); + +// This one isn't inlined because there are a bunch of different ArrayBuffer +// classes that would have to be individually handled here. +// +// There is an isShared out argument for API consistency (eases use from DOM). +// It will always be set to false. +extern JS_PUBLIC_API void GetArrayBufferLengthAndData(JSObject* obj, + size_t* length, + bool* isSharedMemory, + uint8_t** data); + +/** + * Return a pointer to the start of the data referenced by a typed array. The + * data is still owned by the typed array, and should not be modified on + * another thread. Furthermore, the pointer can become invalid on GC (if the + * data is small and fits inside the array's GC header), so callers must take + * care not to hold on across anything that could GC. + * + * |obj| must have passed a JS::IsArrayBufferObject test, or somehow be known + * that it would pass such a test: it is an ArrayBuffer or a wrapper of an + * ArrayBuffer, and the unwrapping will succeed. + * + * |*isSharedMemory| is always set to false. The argument is present to + * simplify its use from code that also interacts with SharedArrayBuffer. + */ +extern JS_PUBLIC_API uint8_t* GetArrayBufferData(JSObject* obj, + bool* isSharedMemory, + const AutoRequireNoGC&); + +// MUTATORS + +/** + * Detach an ArrayBuffer, causing all associated views to no longer refer to + * the ArrayBuffer's original attached memory. + * + * This function throws only if it is provided a non-ArrayBuffer object or if + * the provided ArrayBuffer is a WASM-backed ArrayBuffer or an ArrayBuffer used + * in asm.js code. + */ +extern JS_PUBLIC_API bool DetachArrayBuffer(JSContext* cx, + Handle<JSObject*> obj); + +// Indicates if an object has a defined [[ArrayBufferDetachKey]] internal slot, +// which indicates an ArrayBuffer cannot be detached +extern JS_PUBLIC_API bool HasDefinedArrayBufferDetachKey(JSContext* cx, + Handle<JSObject*> obj, + bool* isDefined); + +/** + * Steal the contents of the given ArrayBuffer. The ArrayBuffer has its length + * set to 0 and its contents array cleared. The caller takes ownership of the + * return value and must free it or transfer ownership via + * JS::NewArrayBufferWithContents when done using it. + */ +extern JS_PUBLIC_API void* StealArrayBufferContents(JSContext* cx, + Handle<JSObject*> obj); + +/** + * Copy data from one array buffer to another. + * + * Both fromBuffer and toBuffer must be (possibly wrapped) + * ArrayBufferObjectMaybeShared. + * + * This method may throw if the sizes don't match, or if unwrapping fails. + * + * The API for this is modelled on CopyDataBlockBytes from the spec: + * https://tc39.es/ecma262/#sec-copydatablockbytes + */ +[[nodiscard]] extern JS_PUBLIC_API bool ArrayBufferCopyData( + JSContext* cx, Handle<JSObject*> toBlock, size_t toIndex, + Handle<JSObject*> fromBlock, size_t fromIndex, size_t count); + +/** + * Copy data from one array buffer to another. + * + * srcBuffer must be a (possibly wrapped) ArrayBufferObjectMaybeShared. + * + * This method may throw if unwrapping or allocation fails. + * + * The API for this is modelled on CloneArrayBuffer from the spec: + * https://tc39.es/ecma262/#sec-clonearraybuffer + */ +extern JS_PUBLIC_API JSObject* ArrayBufferClone(JSContext* cx, + Handle<JSObject*> srcBuffer, + size_t srcByteOffset, + size_t srcLength); + +} // namespace JS + +#endif /* js_ArrayBuffer_h */ diff --git a/js/public/ArrayBufferMaybeShared.h b/js/public/ArrayBufferMaybeShared.h new file mode 100644 index 0000000000..d7393a4fcd --- /dev/null +++ b/js/public/ArrayBufferMaybeShared.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/** + * Functions for working with either ArrayBuffer or SharedArrayBuffer objects + * in agnostic fashion. + */ + +#ifndef js_ArrayBufferMaybeShared_h +#define js_ArrayBufferMaybeShared_h + +#include <stdint.h> // uint8_t, uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; + +namespace JS { + +class JS_PUBLIC_API AutoRequireNoGC; + +// TYPE TESTING + +/** + * Check whether obj supports the JS::GetArrayBufferMaybeShared* APIs. Note + * that this may return false if a security wrapper is encountered that denies + * the unwrapping. If this test succeeds, then it is safe to call the various + * predicate and accessor JSAPI calls defined below. + */ +extern JS_PUBLIC_API bool IsArrayBufferObjectMaybeShared(JSObject* obj); + +// ACCESSORS + +/* + * Test for ArrayBufferMaybeShared subtypes and return the unwrapped object if + * so, else nullptr. Never throws. + */ +extern JS_PUBLIC_API JSObject* UnwrapArrayBufferMaybeShared(JSObject* obj); + +/** + * Get the length, sharedness, and data from an ArrayBufferMaybeShared subtypes. + * + * The computed length and data pointer may be invalidated by a GC or by an + * unshared array buffer becoming detached. Callers must take care not to + * perform any actions that could trigger a GC or result in an unshared array + * buffer becoming detached. If such actions nonetheless must be performed, + * callers should perform this call a second time (and sensibly handle results + * that may be different from those returned the first time). (Sharedness is an + * immutable characteristic of an array buffer or shared array buffer, so that + * boolean remains valid across GC or detaching.) + * + * |obj| must be an ArrayBufferMaybeShared subtype: an ArrayBuffer or a + * SharedArrayBuffer. + * + * |*length| will be set to bytes in the buffer. + * + * |*isSharedMemory| will be set to true if it is a SharedArrayBuffer, otherwise + * to false. + * + * |*data| will be set to a pointer to the bytes in the buffer. + */ +extern JS_PUBLIC_API void GetArrayBufferMaybeSharedLengthAndData( + JSObject* obj, size_t* length, bool* isSharedMemory, uint8_t** data); + +/** + * Return a pointer to the start of the array buffer's data, and indicate + * whether the data is from a shared array buffer through an outparam. + * + * The returned data pointer may be invalidated by a GC or by an unshared array + * buffer becoming detached. Callers must take care not to perform any actions + * that could trigger a GC or result in an unshared array buffer becoming + * detached. If such actions nonetheless must be performed, callers should + * perform this call a second time (and sensibly handle results that may be + * different from those returned the first time). (Sharedness is an immutable + * characteristic of an array buffer or shared array buffer, so that boolean + * remains valid across GC or detaching.) + * + * |obj| must have passed a JS::IsArrayBufferObjectMaybeShared test, or somehow + * be known that it would pass such a test: it is an ArrayBuffer or + * SharedArrayBuffer or a wrapper of an ArrayBuffer/SharedArrayBuffer, and the + * unwrapping will succeed. + * + * |*isSharedMemory| will be set to true if the typed array maps a + * SharedArrayBuffer, otherwise to false. + */ +extern JS_PUBLIC_API uint8_t* GetArrayBufferMaybeSharedData( + JSObject* obj, bool* isSharedMemory, const AutoRequireNoGC&); + +/** + * Returns whether the passed array buffer is 'large': its byteLength >= 2 GB. + * + * |obj| must pass a JS::IsArrayBufferObjectMaybeShared test. + */ +extern JS_PUBLIC_API bool IsLargeArrayBufferMaybeShared(JSObject* obj); + +} // namespace JS + +#endif /* js_ArrayBufferMaybeShared_h */ diff --git a/js/public/BigInt.h b/js/public/BigInt.h new file mode 100644 index 0000000000..94f4aab5c4 --- /dev/null +++ b/js/public/BigInt.h @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* BigInt. */ + +#ifndef js_BigInt_h +#define js_BigInt_h + +#include "mozilla/Span.h" // mozilla::Span + +#include <limits> // std::numeric_limits +#include <stdint.h> // int64_t, uint64_t +#include <type_traits> // std::enable_if_t, std::{true,false}_type, std::is_{integral,signed,unsigned}_v + +#include "jstypes.h" // JS_PUBLIC_API +#include "js/TypeDecls.h" + +namespace mozilla { +template <typename T> +class Range; +} + +namespace JS { + +class JS_PUBLIC_API BigInt; + +namespace detail { + +using Int64Limits = std::numeric_limits<int64_t>; +using Uint64Limits = std::numeric_limits<uint64_t>; + +extern JS_PUBLIC_API BigInt* BigIntFromInt64(JSContext* cx, int64_t num); +extern JS_PUBLIC_API BigInt* BigIntFromUint64(JSContext* cx, uint64_t num); +extern JS_PUBLIC_API BigInt* BigIntFromBool(JSContext* cx, bool b); + +template <typename T, typename = void> +struct NumberToBigIntConverter; + +template <typename SignedIntT> +struct NumberToBigIntConverter< + SignedIntT, + std::enable_if_t< + std::is_integral_v<SignedIntT> && std::is_signed_v<SignedIntT> && + Int64Limits::min() <= std::numeric_limits<SignedIntT>::min() && + std::numeric_limits<SignedIntT>::max() <= Int64Limits::max()>> { + static BigInt* convert(JSContext* cx, SignedIntT num) { + return BigIntFromInt64(cx, num); + } +}; + +template <typename UnsignedIntT> +struct NumberToBigIntConverter< + UnsignedIntT, + std::enable_if_t< + std::is_integral_v<UnsignedIntT> && std::is_unsigned_v<UnsignedIntT> && + std::numeric_limits<UnsignedIntT>::max() <= Uint64Limits::max()>> { + static BigInt* convert(JSContext* cx, UnsignedIntT num) { + return BigIntFromUint64(cx, num); + } +}; + +template <> +struct NumberToBigIntConverter<bool> { + static BigInt* convert(JSContext* cx, bool b) { + return BigIntFromBool(cx, b); + } +}; + +extern JS_PUBLIC_API bool BigIntIsInt64(BigInt* bi, int64_t* result); +extern JS_PUBLIC_API bool BigIntIsUint64(BigInt* bi, uint64_t* result); + +template <typename T, typename = void> +struct BigIntToNumberChecker; + +template <typename SignedIntT> +struct BigIntToNumberChecker< + SignedIntT, + std::enable_if_t< + std::is_integral_v<SignedIntT> && std::is_signed_v<SignedIntT> && + Int64Limits::min() <= std::numeric_limits<SignedIntT>::min() && + std::numeric_limits<SignedIntT>::max() <= Int64Limits::max()>> { + using TypeLimits = std::numeric_limits<SignedIntT>; + + static bool fits(BigInt* bi, SignedIntT* result) { + int64_t innerResult; + if (!BigIntIsInt64(bi, &innerResult)) { + return false; + } + if (TypeLimits::min() <= innerResult && innerResult <= TypeLimits::max()) { + *result = SignedIntT(innerResult); + return true; + } + return false; + } +}; + +template <typename UnsignedIntT> +struct BigIntToNumberChecker< + UnsignedIntT, + std::enable_if_t< + std::is_integral_v<UnsignedIntT> && std::is_unsigned_v<UnsignedIntT> && + std::numeric_limits<UnsignedIntT>::max() <= Uint64Limits::max()>> { + static bool fits(BigInt* bi, UnsignedIntT* result) { + uint64_t innerResult; + if (!BigIntIsUint64(bi, &innerResult)) { + return false; + } + if (innerResult <= std::numeric_limits<UnsignedIntT>::max()) { + *result = UnsignedIntT(innerResult); + return true; + } + return false; + } +}; + +} // namespace detail + +/** + * Create a BigInt from an integer value. All integral types not larger than 64 + * bits in size are supported. + */ +template <typename NumericT> +static inline BigInt* NumberToBigInt(JSContext* cx, NumericT val) { + return detail::NumberToBigIntConverter<NumericT>::convert(cx, val); +} + +/** + * Create a BigInt from a floating-point value. If the number isn't integral + * (that is, if it's NaN, an infinity, or contains a fractional component), + * this function returns null and throws an exception. + * + * Passing -0.0 will produce the bigint 0n. + */ +extern JS_PUBLIC_API BigInt* NumberToBigInt(JSContext* cx, double num); + +/** + * Create a BigInt by parsing a string using the ECMAScript StringToBigInt + * algorithm (https://tc39.es/ecma262/#sec-stringtobigint). Latin1 and two-byte + * character ranges are supported. It may be convenient to use + * JS::ConstLatin1Chars or JS::ConstTwoByteChars. + * + * (StringToBigInt performs parsing similar to that performed by the |Number| + * global function when passed a string, but it doesn't allow infinities, + * decimal points, or exponential notation, and neither algorithm allows numeric + * separators or an 'n' suffix character. This fast-and-loose description is + * offered purely as a convenience to the reader: see the specification + * algorithm for exact behavior.) + * + * If parsing fails, this function returns null and throws an exception. + */ +extern JS_PUBLIC_API BigInt* StringToBigInt( + JSContext* cx, mozilla::Range<const Latin1Char> chars); + +extern JS_PUBLIC_API BigInt* StringToBigInt( + JSContext* cx, mozilla::Range<const char16_t> chars); + +/** + * Create a BigInt by parsing a string consisting of an optional sign character + * followed by one or more alphanumeric ASCII digits in the provided radix. + * + * If the radix is not in the range [2, 36], or the string fails to parse, this + * function returns null and throws an exception. + */ +extern JS_PUBLIC_API BigInt* SimpleStringToBigInt( + JSContext* cx, mozilla::Span<const char> chars, uint8_t radix); + +/** + * Convert a JS::Value to a BigInt using the ECMAScript ToBigInt algorithm + * (https://tc39.es/ecma262/#sec-tobigint). + * + * (Note in particular that this will throw if passed a value whose type is + * 'number'. To convert a number to a BigInt, use one of the overloads of + * JS::NumberToBigInt().) + */ +extern JS_PUBLIC_API BigInt* ToBigInt(JSContext* cx, Handle<Value> val); + +/** + * Convert the given BigInt, modulo 2**64, to a signed 64-bit integer. + */ +extern JS_PUBLIC_API int64_t ToBigInt64(BigInt* bi); + +/** + * Convert the given BigInt, modulo 2**64, to an unsigned 64-bit integer. + */ +extern JS_PUBLIC_API uint64_t ToBigUint64(BigInt* bi); + +/** + * Convert the given BigInt to a Number value as if calling the Number + * constructor on it + * (https://tc39.es/ecma262/#sec-number-constructor-number-value). The value + * may be rounded if it doesn't fit without loss of precision. + */ +extern JS_PUBLIC_API double BigIntToNumber(BigInt* bi); + +/** + * Return true if the given BigInt is negative. + */ +extern JS_PUBLIC_API bool BigIntIsNegative(BigInt* bi); + +/** + * Return true if the given BigInt fits inside the given NumericT type without + * loss of precision, and store the value in the out parameter. Otherwise return + * false and leave the value of the out parameter unspecified. + */ +template <typename NumericT> +static inline bool BigIntFits(BigInt* bi, NumericT* out) { + return detail::BigIntToNumberChecker<NumericT>::fits(bi, out); +} + +/** + * Same as BigIntFits(), but checks if the value fits inside a JS Number value. + */ +extern JS_PUBLIC_API bool BigIntFitsNumber(BigInt* bi, double* out); + +/** + * Convert the given BigInt to a String value as if toString() were called on + * it. + * + * If the radix is not in the range [2, 36], then this function returns null and + * throws an exception. + */ +extern JS_PUBLIC_API JSString* BigIntToString(JSContext* cx, Handle<BigInt*> bi, + uint8_t radix); + +} // namespace JS + +#endif /* js_BigInt_h */ diff --git a/js/public/BuildId.h b/js/public/BuildId.h new file mode 100644 index 0000000000..4f090dacc5 --- /dev/null +++ b/js/public/BuildId.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/** + * Embedding-provided build ID information, used by SpiderMonkey to tag cached + * compilation data so that cached data can be reused when possible, or + * discarded and regenerated if necessary. + */ + +#ifndef js_BuildId_h +#define js_BuildId_h + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Vector.h" // js::Vector + +namespace js { + +class SystemAllocPolicy; + +} // namespace js + +namespace JS { + +/** Vector of characters used for holding build ids. */ +using BuildIdCharVector = js::Vector<char, 0, js::SystemAllocPolicy>; + +/** + * Return the buildId (represented as a sequence of characters) associated with + * the currently-executing build. If the JS engine is embedded such that a + * single cache entry can be observed by different compiled versions of the JS + * engine, it is critical that the buildId shall change for each new build of + * the JS engine. + */ +using BuildIdOp = bool (*)(BuildIdCharVector* buildId); + +/** + * Embedder hook to set the buildId-generating function. + */ +extern JS_PUBLIC_API void SetProcessBuildIdOp(BuildIdOp buildIdOp); + +/** + * Some cached data is, in addition to being build-specific, CPU-specific: the + * cached data depends on CPU features like a particular level of SSE support. + * + * This function produces a buildId that includes: + * + * * the buildId defined by the embedder-provided BuildIdOp set by + * JS::SetProcessBuildIdOp, and + * * CPU feature information for the current CPU. + * + * Embedders may use this function to tag cached data whose validity depends + * on having consistent buildId *and* on the CPU supporting features identical + * to those in play when the cached data was computed. + */ +[[nodiscard]] extern JS_PUBLIC_API bool GetOptimizedEncodingBuildId( + BuildIdCharVector* buildId); + +/** + * Script bytecode is dependent on the buildId and a few other things. + * + * This function produces a buildId that includes: + * + * * The buildId defined by the embedder-provided BuildIdOp set by + * JS::SetProcessBuildIdOp. + * * Additional bytes describing things like endianness, pointer size and + * other state XDR buffers depend on. + * + * Note: this value may depend on runtime preferences so isn't guaranteed to be + * stable across restarts. + * + * Embedders should use this function to tag transcoded bytecode. + * See Transcoding.h. + */ +[[nodiscard]] extern JS_PUBLIC_API bool GetScriptTranscodingBuildId( + BuildIdCharVector* buildId); + +} // namespace JS + +#endif /* js_BuildId_h */ diff --git a/js/public/CallAndConstruct.h b/js/public/CallAndConstruct.h new file mode 100644 index 0000000000..cd3007cbb9 --- /dev/null +++ b/js/public/CallAndConstruct.h @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* Call and construct API. */ + +#ifndef js_CallAndConstruct_h +#define js_CallAndConstruct_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/RootingAPI.h" // JS::Handle, JS::MutableHandle +#include "js/Value.h" // JS::Value, JS::ObjectValue +#include "js/ValueArray.h" // JS::HandleValueArray + +struct JSContext; +class JSObject; +class JSFunction; + +/* + * API for determining callability and constructability. [[Call]] and + * [[Construct]] are internal methods that aren't present on all objects, so it + * is useful to ask if they are there or not. The standard itself asks these + * questions routinely. + */ +namespace JS { + +/** + * Return true if the given object is callable. In ES6 terms, an object is + * callable if it has a [[Call]] internal method. + * + * Implements: ES6 7.2.3 IsCallable(argument). + * + * Functions are callable. A scripted proxy or wrapper is callable if its + * target is callable. Most other objects aren't callable. + */ +extern JS_PUBLIC_API bool IsCallable(JSObject* obj); + +/** + * Return true if the given object is a constructor. In ES6 terms, an object is + * a constructor if it has a [[Construct]] internal method. The expression + * `new obj()` throws a TypeError if obj is not a constructor. + * + * Implements: ES6 7.2.4 IsConstructor(argument). + * + * JS functions and classes are constructors. Arrow functions and most builtin + * functions are not. A scripted proxy or wrapper is a constructor if its + * target is a constructor. + */ +extern JS_PUBLIC_API bool IsConstructor(JSObject* obj); + +} /* namespace JS */ + +/** + * Call a function, passing a this-value and arguments. This is the C++ + * equivalent of `rval = Reflect.apply(fun, obj, args)`. + * + * Implements: ES6 7.3.12 Call(F, V, [argumentsList]). + * Use this function to invoke the [[Call]] internal method. + */ +extern JS_PUBLIC_API bool JS_CallFunctionValue( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JS::Value> fval, + const JS::HandleValueArray& args, JS::MutableHandle<JS::Value> rval); + +extern JS_PUBLIC_API bool JS_CallFunction(JSContext* cx, + JS::Handle<JSObject*> obj, + JS::Handle<JSFunction*> fun, + const JS::HandleValueArray& args, + JS::MutableHandle<JS::Value> rval); + +/** + * Perform the method call `rval = obj[name](args)`. + */ +extern JS_PUBLIC_API bool JS_CallFunctionName( + JSContext* cx, JS::Handle<JSObject*> obj, const char* name, + const JS::HandleValueArray& args, JS::MutableHandle<JS::Value> rval); + +namespace JS { + +static inline bool Call(JSContext* cx, Handle<JSObject*> thisObj, + Handle<JSFunction*> fun, const HandleValueArray& args, + MutableHandle<Value> rval) { + return !!JS_CallFunction(cx, thisObj, fun, args, rval); +} + +static inline bool Call(JSContext* cx, Handle<JSObject*> thisObj, + Handle<Value> fun, const HandleValueArray& args, + MutableHandle<Value> rval) { + return !!JS_CallFunctionValue(cx, thisObj, fun, args, rval); +} + +static inline bool Call(JSContext* cx, Handle<JSObject*> thisObj, + const char* name, const HandleValueArray& args, + MutableHandle<Value> rval) { + return !!JS_CallFunctionName(cx, thisObj, name, args, rval); +} + +extern JS_PUBLIC_API bool Call(JSContext* cx, Handle<Value> thisv, + Handle<Value> fun, const HandleValueArray& args, + MutableHandle<Value> rval); + +static inline bool Call(JSContext* cx, Handle<Value> thisv, + Handle<JSObject*> funObj, const HandleValueArray& args, + MutableHandle<Value> rval) { + MOZ_ASSERT(funObj); + Rooted<Value> fun(cx, ObjectValue(*funObj)); + return Call(cx, thisv, fun, args, rval); +} + +/** + * Invoke a constructor. This is the C++ equivalent of + * `rval = Reflect.construct(fun, args, newTarget)`. + * + * Construct() takes a `newTarget` argument that most callers don't need. + * Consider using the four-argument Construct signature instead. (But if you're + * implementing a subclass or a proxy handler's construct() method, this is the + * right function to call.) + * + * Implements: ES6 7.3.13 Construct(F, [argumentsList], [newTarget]). + * Use this function to invoke the [[Construct]] internal method. + */ +extern JS_PUBLIC_API bool Construct(JSContext* cx, Handle<Value> fun, + Handle<JSObject*> newTarget, + const HandleValueArray& args, + MutableHandle<JSObject*> objp); + +/** + * Invoke a constructor. This is the C++ equivalent of + * `rval = new fun(...args)`. + * + * Implements: ES6 7.3.13 Construct(F, [argumentsList], [newTarget]), when + * newTarget is omitted. + */ +extern JS_PUBLIC_API bool Construct(JSContext* cx, Handle<Value> fun, + const HandleValueArray& args, + MutableHandle<JSObject*> objp); + +} /* namespace JS */ + +#endif /* js_CallAndConstruct_h */ diff --git a/js/public/CallArgs.h b/js/public/CallArgs.h new file mode 100644 index 0000000000..e40790fdec --- /dev/null +++ b/js/public/CallArgs.h @@ -0,0 +1,363 @@ +/* -*- 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/. */ + +/* + * [SMDOC] JS::CallArgs API + * + * Helper classes encapsulating access to the callee, |this| value, arguments, + * and argument count for a call/construct operation. + * + * JS::CallArgs encapsulates access to a JSNative's un-abstracted + * |unsigned argc, Value* vp| arguments. The principal way to create a + * JS::CallArgs is using JS::CallArgsFromVp: + * + * // If provided no arguments or a non-numeric first argument, return zero. + * // Otherwise return |this| exactly as given, without boxing. + * static bool + * Func(JSContext* cx, unsigned argc, JS::Value* vp) + * { + * JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + * + * // Guard against no arguments or a non-numeric arg0. + * if (args.length() == 0 || !args[0].isNumber()) { + * args.rval().setInt32(0); + * return true; + * } + * + * // Access to the callee must occur before accessing/setting + * // the return value. + * JSObject& callee = args.callee(); + * args.rval().setObject(callee); + * + * // callee() and calleev() will now assert. + * + * // It's always fine to access thisv(). + * HandleValue thisv = args.thisv(); + * args.rval().set(thisv); + * + * // As the return value was last set to |this|, returns |this|. + * return true; + * } + * + * CallArgs is exposed publicly and used internally. Not all parts of its + * public interface are meant to be used by embedders! See inline comments to + * for details. + * + * It's possible (albeit deprecated) to manually index into |vp| to access the + * callee, |this|, and arguments of a function, and to set its return value. + * This does not have the error-handling or moving-GC correctness of CallArgs. + * New code should use CallArgs instead whenever possible. + * + * The eventual plan is to change JSNative to take |const CallArgs&| directly, + * for automatic assertion of correct use and to make calling functions more + * efficient. Embedders should start internally switching away from using + * |argc| and |vp| directly, except to create a |CallArgs|. Then, when an + * eventual release making that change occurs, porting efforts will require + * changing methods' signatures but won't require invasive changes to the + * methods' implementations, potentially under time pressure. + */ + +#ifndef js_CallArgs_h +#define js_CallArgs_h + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" + +#include <type_traits> + +#include "jstypes.h" + +#include "js/RootingAPI.h" +#include "js/Value.h" + +/* Typedef for native functions called by the JS VM. */ +using JSNative = bool (*)(JSContext* cx, unsigned argc, JS::Value* vp); + +namespace JS { + +extern JS_PUBLIC_DATA const HandleValue UndefinedHandleValue; + +namespace detail { + +/* + * Compute |this| for the |vp| inside a JSNative, either boxing primitives or + * replacing with the global object as necessary. + */ +extern JS_PUBLIC_API bool ComputeThis(JSContext* cx, JS::Value* vp, + MutableHandleObject thisObject); + +#ifdef JS_DEBUG +extern JS_PUBLIC_API void CheckIsValidConstructible(const Value& v); +#endif + +class MOZ_STACK_CLASS IncludeUsedRval { + mutable bool usedRval_ = false; + + public: + bool usedRval() const { return usedRval_; } + void setUsedRval() const { usedRval_ = true; } + void clearUsedRval() const { usedRval_ = false; } + void assertUnusedRval() const { MOZ_ASSERT(!usedRval_); } +}; + +class MOZ_STACK_CLASS NoUsedRval { + public: + bool usedRval() const { return false; } + void setUsedRval() const {} + void clearUsedRval() const {} + void assertUnusedRval() const {} +}; + +template <class WantUsedRval> +class MOZ_STACK_CLASS CallArgsBase { + static_assert(std::is_same_v<WantUsedRval, IncludeUsedRval> || + std::is_same_v<WantUsedRval, NoUsedRval>, + "WantUsedRval can only be IncludeUsedRval or NoUsedRval"); + + protected: + Value* argv_ = nullptr; + unsigned argc_ = 0; + bool constructing_ : 1; + + // True if the caller does not use the return value. + bool ignoresReturnValue_ : 1; + +#ifdef JS_DEBUG + WantUsedRval wantUsedRval_; + bool usedRval() const { return wantUsedRval_.usedRval(); } + void setUsedRval() const { wantUsedRval_.setUsedRval(); } + void clearUsedRval() const { wantUsedRval_.clearUsedRval(); } + void assertUnusedRval() const { wantUsedRval_.assertUnusedRval(); } +#else + bool usedRval() const { return false; } + void setUsedRval() const {} + void clearUsedRval() const {} + void assertUnusedRval() const {} +#endif + + CallArgsBase() : constructing_(false), ignoresReturnValue_(false) {} + + public: + // CALLEE ACCESS + + /* + * Returns the function being called, as a value. Must not be called after + * rval() has been used! + */ + HandleValue calleev() const { + this->assertUnusedRval(); + return HandleValue::fromMarkedLocation(&argv_[-2]); + } + + /* + * Returns the function being called, as an object. Must not be called + * after rval() has been used! + */ + JSObject& callee() const { return calleev().toObject(); } + + // CALLING/CONSTRUCTING-DIFFERENTIATIONS + + bool isConstructing() const { + if (!argv_[-1].isMagic()) { + return false; + } + +#ifdef JS_DEBUG + if (!this->usedRval()) { + CheckIsValidConstructible(calleev()); + } +#endif + + return true; + } + + bool ignoresReturnValue() const { return ignoresReturnValue_; } + + MutableHandleValue newTarget() const { + MOZ_ASSERT(constructing_); + return MutableHandleValue::fromMarkedLocation(&this->argv_[argc_]); + } + + /* + * Returns the |this| value passed to the function. This method must not + * be called when the function is being called as a constructor via |new|. + * The value may or may not be an object: it is the individual function's + * responsibility to box the value if needed. + */ + HandleValue thisv() const { + // Some internal code uses thisv() in constructing cases, so don't do + // this yet. + // MOZ_ASSERT(!argv_[-1].isMagic(JS_IS_CONSTRUCTING)); + return HandleValue::fromMarkedLocation(&argv_[-1]); + } + + bool computeThis(JSContext* cx, MutableHandleObject thisObject) const { + if (thisv().isObject()) { + thisObject.set(&thisv().toObject()); + return true; + } + + return ComputeThis(cx, base(), thisObject); + } + + // ARGUMENTS + + /* Returns the number of arguments. */ + unsigned length() const { return argc_; } + + /* Returns the i-th zero-indexed argument. */ + MutableHandleValue operator[](unsigned i) const { + MOZ_ASSERT(i < argc_); + return MutableHandleValue::fromMarkedLocation(&this->argv_[i]); + } + + /* + * Returns the i-th zero-indexed argument, or |undefined| if there's no + * such argument. + */ + HandleValue get(unsigned i) const { + return i < length() ? HandleValue::fromMarkedLocation(&this->argv_[i]) + : UndefinedHandleValue; + } + + /* + * Returns true if the i-th zero-indexed argument is present and is not + * |undefined|. + */ + bool hasDefined(unsigned i) const { + return i < argc_ && !this->argv_[i].isUndefined(); + } + + // RETURN VALUE + + /* + * Returns the currently-set return value. The initial contents of this + * value are unspecified. Once this method has been called, callee() and + * calleev() can no longer be used. (If you're compiling against a debug + * build of SpiderMonkey, these methods will assert to aid debugging.) + * + * If the method you're implementing succeeds by returning true, you *must* + * set this. (SpiderMonkey doesn't currently assert this, but it will do + * so eventually.) You don't need to use or change this if your method + * fails. + */ + MutableHandleValue rval() const { + this->setUsedRval(); + return MutableHandleValue::fromMarkedLocation(&argv_[-2]); + } + + /* + * Returns true if there are at least |required| arguments passed in. If + * false, it reports an error message on the context. + */ + JS_PUBLIC_API inline bool requireAtLeast(JSContext* cx, const char* fnname, + unsigned required) const; + + public: + // These methods are publicly exposed, but they are *not* to be used when + // implementing a JSNative method and encapsulating access to |vp| within + // it. You probably don't want to use these! + + void setCallee(const Value& aCalleev) const { + this->clearUsedRval(); + argv_[-2] = aCalleev; + } + + void setThis(const Value& aThisv) const { argv_[-1] = aThisv; } + + MutableHandleValue mutableThisv() const { + return MutableHandleValue::fromMarkedLocation(&argv_[-1]); + } + + public: + // These methods are publicly exposed, but we're unsure of the interfaces + // (because they're hackish and drop assertions). Avoid using these if you + // can. + + Value* array() const { return argv_; } + Value* end() const { return argv_ + argc_ + constructing_; } + + public: + // These methods are only intended for internal use. Embedders shouldn't + // use them! + + Value* base() const { return argv_ - 2; } + + Value* spAfterCall() const { + this->setUsedRval(); + return argv_ - 1; + } +}; + +} // namespace detail + +class MOZ_STACK_CLASS CallArgs + : public detail::CallArgsBase<detail::IncludeUsedRval> { + private: + friend CallArgs CallArgsFromVp(unsigned argc, Value* vp); + friend CallArgs CallArgsFromSp(unsigned stackSlots, Value* sp, + bool constructing, bool ignoresReturnValue); + + static CallArgs create(unsigned argc, Value* argv, bool constructing, + bool ignoresReturnValue = false) { + CallArgs args; + args.clearUsedRval(); + args.argv_ = argv; + args.argc_ = argc; + args.constructing_ = constructing; + args.ignoresReturnValue_ = ignoresReturnValue; +#ifdef DEBUG + AssertValueIsNotGray(args.thisv()); + AssertValueIsNotGray(args.calleev()); + for (unsigned i = 0; i < argc; ++i) { + AssertValueIsNotGray(argv[i]); + } +#endif + return args; + } + + public: + /* + * Helper for requireAtLeast to report the actual exception. Public + * so we can call it from CallArgsBase and not need multiple + * per-template instantiations of it. + */ + static JS_PUBLIC_API void reportMoreArgsNeeded(JSContext* cx, + const char* fnname, + unsigned required, + unsigned actual); +}; + +namespace detail { +template <class WantUsedRval> +JS_PUBLIC_API inline bool CallArgsBase<WantUsedRval>::requireAtLeast( + JSContext* cx, const char* fnname, unsigned required) const { + if (MOZ_LIKELY(required <= length())) { + return true; + } + + CallArgs::reportMoreArgsNeeded(cx, fnname, required, length()); + return false; +} +} // namespace detail + +MOZ_ALWAYS_INLINE CallArgs CallArgsFromVp(unsigned argc, Value* vp) { + return CallArgs::create(argc, vp + 2, vp[1].isMagic(JS_IS_CONSTRUCTING)); +} + +// This method is only intended for internal use in SpiderMonkey. We may +// eventually move it to an internal header. Embedders should use +// JS::CallArgsFromVp! +MOZ_ALWAYS_INLINE CallArgs CallArgsFromSp(unsigned stackSlots, Value* sp, + bool constructing = false, + bool ignoresReturnValue = false) { + return CallArgs::create(stackSlots - constructing, sp - stackSlots, + constructing, ignoresReturnValue); +} + +} // namespace JS + +#endif /* js_CallArgs_h */ diff --git a/js/public/CallNonGenericMethod.h b/js/public/CallNonGenericMethod.h new file mode 100644 index 0000000000..80359f7a6f --- /dev/null +++ b/js/public/CallNonGenericMethod.h @@ -0,0 +1,123 @@ +/* -*- 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 js_CallNonGenericMethod_h +#define js_CallNonGenericMethod_h + +#include "jstypes.h" + +#include "js/CallArgs.h" + +namespace JS { + +// Returns true if |v| is considered an acceptable this-value. +typedef bool (*IsAcceptableThis)(HandleValue v); + +// Implements the guts of a method; guaranteed to be provided an acceptable +// this-value, as determined by a corresponding IsAcceptableThis method. +typedef bool (*NativeImpl)(JSContext* cx, const CallArgs& args); + +namespace detail { + +// DON'T CALL THIS DIRECTLY. It's for use only by CallNonGenericMethod! +extern JS_PUBLIC_API bool CallMethodIfWrapped(JSContext* cx, + IsAcceptableThis test, + NativeImpl impl, + const CallArgs& args); + +} // namespace detail + +// Methods usually act upon |this| objects only from a single global object and +// compartment. Sometimes, however, a method must act upon |this| values from +// multiple global objects or compartments. In such cases the |this| value a +// method might see will be wrapped, such that various access to the object -- +// to its class, its private data, its reserved slots, and so on -- will not +// work properly without entering that object's compartment. This method +// implements a solution to this problem. +// +// To implement a method that accepts |this| values from multiple compartments, +// define two functions. The first function matches the IsAcceptableThis type +// and indicates whether the provided value is an acceptable |this| for the +// method; it must be a pure function only of its argument. +// +// static const JSClass AnswerClass = { ... }; +// +// static bool +// IsAnswerObject(const Value& v) +// { +// if (!v.isObject()) { +// return false; +// } +// return JS_GetClass(&v.toObject()) == &AnswerClass; +// } +// +// The second function implements the NativeImpl signature and defines the +// behavior of the method when it is provided an acceptable |this| value. +// Aside from some typing niceties -- see the CallArgs interface for details -- +// its interface is the same as that of JSNative. +// +// static bool +// answer_getAnswer_impl(JSContext* cx, JS::CallArgs args) +// { +// args.rval().setInt32(42); +// return true; +// } +// +// The implementation function is guaranteed to be called *only* with a |this| +// value which is considered acceptable. +// +// Now to implement the actual method, write a JSNative that calls the method +// declared below, passing the appropriate template and runtime arguments. +// +// static bool +// answer_getAnswer(JSContext* cx, unsigned argc, JS::Value* vp) +// { +// JS::CallArgs args = JS::CallArgsFromVp(argc, vp); +// return JS::CallNonGenericMethod<IsAnswerObject, +// answer_getAnswer_impl>(cx, args); +// } +// +// Note that, because they are used as template arguments, the predicate +// and implementation functions must have external linkage. (This is +// unfortunate, but GCC wasn't inlining things as one would hope when we +// passed them as function arguments.) +// +// JS::CallNonGenericMethod will test whether |args.thisv()| is acceptable. If +// it is, it will call the provided implementation function, which will return +// a value and indicate success. If it is not, it will attempt to unwrap +// |this| and call the implementation function on the unwrapped |this|. If +// that succeeds, all well and good. If it doesn't succeed, a TypeError will +// be thrown. +// +// Note: JS::CallNonGenericMethod will only work correctly if it's called in +// tail position in a JSNative. Do not call it from any other place. +// +template <IsAcceptableThis Test, NativeImpl Impl> +MOZ_ALWAYS_INLINE bool CallNonGenericMethod(JSContext* cx, + const CallArgs& args) { + HandleValue thisv = args.thisv(); + if (Test(thisv)) { + return Impl(cx, args); + } + + return detail::CallMethodIfWrapped(cx, Test, Impl, args); +} + +MOZ_ALWAYS_INLINE bool CallNonGenericMethod(JSContext* cx, + IsAcceptableThis Test, + NativeImpl Impl, + const CallArgs& args) { + HandleValue thisv = args.thisv(); + if (Test(thisv)) { + return Impl(cx, args); + } + + return detail::CallMethodIfWrapped(cx, Test, Impl, args); +} + +} // namespace JS + +#endif /* js_CallNonGenericMethod_h */ diff --git a/js/public/CharacterEncoding.h b/js/public/CharacterEncoding.h new file mode 100644 index 0000000000..bf67e27aca --- /dev/null +++ b/js/public/CharacterEncoding.h @@ -0,0 +1,439 @@ +/* -*- 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 js_CharacterEncoding_h +#define js_CharacterEncoding_h + +#include "mozilla/Range.h" +#include "mozilla/Span.h" + +#include "js/TypeDecls.h" +#include "js/Utility.h" + +class JSLinearString; + +namespace mozilla { +union Utf8Unit; +} + +namespace JS { + +/* + * By default, all C/C++ 1-byte-per-character strings passed into the JSAPI + * are treated as ISO/IEC 8859-1, also known as Latin-1. That is, each + * byte is treated as a 2-byte character, and there is no way to pass in a + * string containing characters beyond U+00FF. + */ +class Latin1Chars : public mozilla::Range<Latin1Char> { + typedef mozilla::Range<Latin1Char> Base; + + public: + using CharT = Latin1Char; + + Latin1Chars() = default; + Latin1Chars(char* aBytes, size_t aLength) + : Base(reinterpret_cast<Latin1Char*>(aBytes), aLength) {} + Latin1Chars(const Latin1Char* aBytes, size_t aLength) + : Base(const_cast<Latin1Char*>(aBytes), aLength) {} + Latin1Chars(const char* aBytes, size_t aLength) + : Base(reinterpret_cast<Latin1Char*>(const_cast<char*>(aBytes)), + aLength) {} +}; + +/* + * Like Latin1Chars, but the chars are const. + */ +class ConstLatin1Chars : public mozilla::Range<const Latin1Char> { + typedef mozilla::Range<const Latin1Char> Base; + + public: + using CharT = Latin1Char; + + ConstLatin1Chars() = default; + ConstLatin1Chars(const Latin1Char* aChars, size_t aLength) + : Base(aChars, aLength) {} +}; + +/* + * A Latin1Chars, but with \0 termination for C compatibility. + */ +class Latin1CharsZ : public mozilla::RangedPtr<Latin1Char> { + typedef mozilla::RangedPtr<Latin1Char> Base; + + public: + using CharT = Latin1Char; + + Latin1CharsZ() : Base(nullptr, 0) {} // NOLINT + + Latin1CharsZ(char* aBytes, size_t aLength) + : Base(reinterpret_cast<Latin1Char*>(aBytes), aLength) { + MOZ_ASSERT(aBytes[aLength] == '\0'); + } + + Latin1CharsZ(Latin1Char* aBytes, size_t aLength) : Base(aBytes, aLength) { + MOZ_ASSERT(aBytes[aLength] == '\0'); + } + + using Base::operator=; + + char* c_str() { return reinterpret_cast<char*>(get()); } +}; + +class UTF8Chars : public mozilla::Range<unsigned char> { + typedef mozilla::Range<unsigned char> Base; + + public: + using CharT = unsigned char; + + UTF8Chars() = default; + UTF8Chars(char* aBytes, size_t aLength) + : Base(reinterpret_cast<unsigned char*>(aBytes), aLength) {} + UTF8Chars(const char* aBytes, size_t aLength) + : Base(reinterpret_cast<unsigned char*>(const_cast<char*>(aBytes)), + aLength) {} + UTF8Chars(mozilla::Utf8Unit* aUnits, size_t aLength) + : UTF8Chars(reinterpret_cast<char*>(aUnits), aLength) {} + UTF8Chars(const mozilla::Utf8Unit* aUnits, size_t aLength) + : UTF8Chars(reinterpret_cast<const char*>(aUnits), aLength) {} +}; + +/* + * SpiderMonkey also deals directly with UTF-8 encoded text in some places. + */ +class UTF8CharsZ : public mozilla::RangedPtr<unsigned char> { + typedef mozilla::RangedPtr<unsigned char> Base; + + public: + using CharT = unsigned char; + + UTF8CharsZ() : Base(nullptr, 0) {} // NOLINT + + UTF8CharsZ(char* aBytes, size_t aLength) + : Base(reinterpret_cast<unsigned char*>(aBytes), aLength) { + MOZ_ASSERT(aBytes[aLength] == '\0'); + } + + UTF8CharsZ(unsigned char* aBytes, size_t aLength) : Base(aBytes, aLength) { + MOZ_ASSERT(aBytes[aLength] == '\0'); + } + + UTF8CharsZ(mozilla::Utf8Unit* aUnits, size_t aLength) + : UTF8CharsZ(reinterpret_cast<char*>(aUnits), aLength) {} + + using Base::operator=; + + char* c_str() { return reinterpret_cast<char*>(get()); } +}; + +/* + * A wrapper for a "const char*" that is encoded using UTF-8. + * This class does not manage ownership of the data; that is left + * to others. This differs from UTF8CharsZ in that the chars are + * const and it disallows assignment. + */ +class JS_PUBLIC_API ConstUTF8CharsZ { + const char* data_; + + public: + using CharT = unsigned char; + + ConstUTF8CharsZ() : data_(nullptr) {} + + ConstUTF8CharsZ(const char* aBytes, size_t aLength) : data_(aBytes) { + MOZ_ASSERT(aBytes[aLength] == '\0'); +#ifdef DEBUG + validate(aLength); +#endif + } + + const void* get() const { return data_; } + + const char* c_str() const { return data_; } + + explicit operator bool() const { return data_ != nullptr; } + + private: +#ifdef DEBUG + void validate(size_t aLength); +#endif +}; + +/* + * SpiderMonkey uses a 2-byte character representation: it is a + * 2-byte-at-a-time view of a UTF-16 byte stream. This is similar to UCS-2, + * but unlike UCS-2, we do not strip UTF-16 extension bytes. This allows a + * sufficiently dedicated JavaScript program to be fully unicode-aware by + * manually interpreting UTF-16 extension characters embedded in the JS + * string. + */ +class TwoByteChars : public mozilla::Range<char16_t> { + typedef mozilla::Range<char16_t> Base; + + public: + using CharT = char16_t; + + TwoByteChars() = default; + TwoByteChars(char16_t* aChars, size_t aLength) : Base(aChars, aLength) {} + TwoByteChars(const char16_t* aChars, size_t aLength) + : Base(const_cast<char16_t*>(aChars), aLength) {} +}; + +/* + * A TwoByteChars, but \0 terminated for compatibility with JSFlatString. + */ +class TwoByteCharsZ : public mozilla::RangedPtr<char16_t> { + typedef mozilla::RangedPtr<char16_t> Base; + + public: + using CharT = char16_t; + + TwoByteCharsZ() : Base(nullptr, 0) {} // NOLINT + + TwoByteCharsZ(char16_t* chars, size_t length) : Base(chars, length) { + MOZ_ASSERT(chars[length] == '\0'); + } + + using Base::operator=; +}; + +typedef mozilla::RangedPtr<const char16_t> ConstCharPtr; + +/* + * Like TwoByteChars, but the chars are const. + */ +class ConstTwoByteChars : public mozilla::Range<const char16_t> { + typedef mozilla::Range<const char16_t> Base; + + public: + using CharT = char16_t; + + ConstTwoByteChars() = default; + ConstTwoByteChars(const char16_t* aChars, size_t aLength) + : Base(aChars, aLength) {} +}; + +/* + * Convert a 2-byte character sequence to "ISO-Latin-1". This works by + * truncating each 2-byte pair in the sequence to a 1-byte pair. If the source + * contains any UTF-16 extension characters, then this may give invalid Latin1 + * output. The returned string is zero terminated. The returned string or the + * returned string's |start()| must be freed with JS_free or js_free, + * respectively. If allocation fails, an OOM error will be set and the method + * will return a nullptr chars (which can be tested for with the ! operator). + * This method cannot trigger GC. + */ +extern Latin1CharsZ LossyTwoByteCharsToNewLatin1CharsZ( + JSContext* cx, const mozilla::Range<const char16_t> tbchars); + +inline Latin1CharsZ LossyTwoByteCharsToNewLatin1CharsZ(JSContext* cx, + const char16_t* begin, + size_t length) { + const mozilla::Range<const char16_t> tbchars(begin, length); + return JS::LossyTwoByteCharsToNewLatin1CharsZ(cx, tbchars); +} + +template <typename CharT, typename Allocator> +extern UTF8CharsZ CharsToNewUTF8CharsZ(Allocator* alloc, + const mozilla::Range<CharT> chars); + +JS_PUBLIC_API char32_t Utf8ToOneUcs4Char(const uint8_t* utf8Buffer, + int utf8Length); + +/* + * Inflate bytes in UTF-8 encoding to char16_t. + * - On error, returns an empty TwoByteCharsZ. + * - On success, returns a malloc'd TwoByteCharsZ, and updates |outlen| to hold + * its length; the length value excludes the trailing null. + */ +extern JS_PUBLIC_API TwoByteCharsZ +UTF8CharsToNewTwoByteCharsZ(JSContext* cx, const UTF8Chars utf8, size_t* outlen, + arena_id_t destArenaId); + +/* + * Like UTF8CharsToNewTwoByteCharsZ, but for ConstUTF8CharsZ. + */ +extern JS_PUBLIC_API TwoByteCharsZ +UTF8CharsToNewTwoByteCharsZ(JSContext* cx, const ConstUTF8CharsZ& utf8, + size_t* outlen, arena_id_t destArenaId); + +/* + * The same as UTF8CharsToNewTwoByteCharsZ(), except that any malformed UTF-8 + * characters will be replaced by \uFFFD. No exception will be thrown for + * malformed UTF-8 input. + */ +extern JS_PUBLIC_API TwoByteCharsZ +LossyUTF8CharsToNewTwoByteCharsZ(JSContext* cx, const UTF8Chars utf8, + size_t* outlen, arena_id_t destArenaId); + +extern JS_PUBLIC_API TwoByteCharsZ +LossyUTF8CharsToNewTwoByteCharsZ(JSContext* cx, const ConstUTF8CharsZ& utf8, + size_t* outlen, arena_id_t destArenaId); + +/* + * Returns the length of the char buffer required to encode |s| as UTF8. + * Does not include the null-terminator. + */ +JS_PUBLIC_API size_t GetDeflatedUTF8StringLength(JSLinearString* s); + +/* + * Encode whole scalar values of |src| into |dst| as UTF-8 until |src| is + * exhausted or too little space is available in |dst| to fit the scalar + * value. Lone surrogates are converted to REPLACEMENT CHARACTER. Return + * the number of bytes of |dst| that were filled. + * + * Use |JS_EncodeStringToUTF8BufferPartial| if your string isn't already + * linear. + * + * Given |JSString* str = JS_FORGET_STRING_LINEARNESS(src)|, + * if |JS::StringHasLatin1Chars(str)|, then |src| is always fully converted + * if |dst.Length() >= JS_GetStringLength(str) * 2|. Otherwise |src| is + * always fully converted if |dst.Length() >= JS_GetStringLength(str) * 3|. + * + * The exact space required is always |GetDeflatedUTF8StringLength(str)|. + */ +JS_PUBLIC_API size_t DeflateStringToUTF8Buffer(JSLinearString* src, + mozilla::Span<char> dst); + +/* + * The smallest character encoding capable of fully representing a particular + * string. + */ +enum class SmallestEncoding { ASCII, Latin1, UTF16 }; + +/* + * Returns the smallest encoding possible for the given string: if all + * codepoints are <128 then ASCII, otherwise if all codepoints are <256 + * Latin-1, else UTF16. + */ +JS_PUBLIC_API SmallestEncoding FindSmallestEncoding(UTF8Chars utf8); + +/* + * Return a null-terminated Latin-1 string copied from the input string, + * storing its length (excluding null terminator) in |*outlen|. Fail and + * report an error if the string contains non-Latin-1 codepoints. Returns + * Latin1CharsZ() on failure. + */ +extern JS_PUBLIC_API Latin1CharsZ +UTF8CharsToNewLatin1CharsZ(JSContext* cx, const UTF8Chars utf8, size_t* outlen, + arena_id_t destArenaId); + +/* + * Return a null-terminated Latin-1 string copied from the input string, + * storing its length (excluding null terminator) in |*outlen|. Non-Latin-1 + * codepoints are replaced by '?'. Returns Latin1CharsZ() on failure. + */ +extern JS_PUBLIC_API Latin1CharsZ +LossyUTF8CharsToNewLatin1CharsZ(JSContext* cx, const UTF8Chars utf8, + size_t* outlen, arena_id_t destArenaId); + +/* + * Returns true if all characters in the given null-terminated string are + * ASCII, i.e. < 0x80, false otherwise. + */ +extern JS_PUBLIC_API bool StringIsASCII(const char* s); + +/* + * Returns true if all characters in the given span are ASCII, + * i.e. < 0x80, false otherwise. + */ +extern JS_PUBLIC_API bool StringIsASCII(mozilla::Span<const char> s); + +/** + * Encode a narrow multibyte character string to a UTF-8 string. + * + * NOTE: Should only be used when interacting with POSIX/OS functions and not + * for encoding ASCII/Latin-1/etc. strings to UTF-8. + */ +extern JS_PUBLIC_API JS::UniqueChars EncodeNarrowToUtf8(JSContext* cx, + const char* chars); + +/** + * Encode a wide string to a UTF-8 string. + * + * NOTE: Should only be used when interacting with Windows API functions. + */ +extern JS_PUBLIC_API JS::UniqueChars EncodeWideToUtf8(JSContext* cx, + const wchar_t* chars); + +/** + * Encode a UTF-8 string to a narrow multibyte character string. + * + * NOTE: Should only be used when interacting with POSIX/OS functions and not + * for encoding UTF-8 to ASCII/Latin-1/etc. strings. + */ +extern JS_PUBLIC_API JS::UniqueChars EncodeUtf8ToNarrow(JSContext* cx, + const char* chars); + +/** + * Encode a UTF-8 string to a wide string. + * + * NOTE: Should only be used when interacting with Windows API functions. + */ +extern JS_PUBLIC_API JS::UniqueWideChars EncodeUtf8ToWide(JSContext* cx, + const char* chars); + +} // namespace JS + +inline void JS_free(JS::Latin1CharsZ& ptr) { js_free((void*)ptr.get()); } +inline void JS_free(JS::UTF8CharsZ& ptr) { js_free((void*)ptr.get()); } + +/** + * DEPRECATED + * + * Allocate memory sufficient to contain the characters of |str| truncated to + * Latin-1 and a trailing null terminator, fill the memory with the characters + * interpreted in that manner plus the null terminator, and return a pointer to + * the memory. + * + * This function *loses information* when it copies the characters of |str| if + * |str| contains code units greater than 0xFF. Additionally, users that + * depend on null-termination will misinterpret the copied characters if |str| + * contains any nulls. Avoid using this function if possible, because it will + * eventually be removed. + */ +extern JS_PUBLIC_API JS::UniqueChars JS_EncodeStringToLatin1(JSContext* cx, + JSString* str); + +/** + * DEPRECATED + * + * Same behavior as JS_EncodeStringToLatin1(), but encode into a UTF-8 string. + * + * This function *loses information* when it copies the characters of |str| if + * |str| contains invalid UTF-16: U+FFFD REPLACEMENT CHARACTER will be copied + * instead. + * + * The returned string is also subject to misinterpretation if |str| contains + * any nulls (which are faithfully transcribed into the returned string, but + * which will implicitly truncate the string if it's passed to functions that + * expect null-terminated strings). + * + * Avoid using this function if possible, because we'll remove it once we can + * devise a better API for the task. + */ +extern JS_PUBLIC_API JS::UniqueChars JS_EncodeStringToUTF8( + JSContext* cx, JS::Handle<JSString*> str); + +/** + * DEPRECATED + * + * Same behavior as JS_EncodeStringToLatin1(), but encode into an ASCII string. + * + * This function asserts in debug mode that the input string contains only + * ASCII characters. + * + * The returned string is also subject to misinterpretation if |str| contains + * any nulls (which are faithfully transcribed into the returned string, but + * which will implicitly truncate the string if it's passed to functions that + * expect null-terminated strings). + * + * Avoid using this function if possible, because we'll remove it once we can + * devise a better API for the task. + */ +extern JS_PUBLIC_API JS::UniqueChars JS_EncodeStringToASCII(JSContext* cx, + JSString* str); + +#endif /* js_CharacterEncoding_h */ diff --git a/js/public/Class.h b/js/public/Class.h new file mode 100644 index 0000000000..5664198584 --- /dev/null +++ b/js/public/Class.h @@ -0,0 +1,837 @@ +/* -*- 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/. */ + +/* JSClass definition and its component types, plus related interfaces. */ + +#ifndef js_Class_h +#define js_Class_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include "jstypes.h" + +#include "js/CallArgs.h" +#include "js/HeapAPI.h" +#include "js/Id.h" +#include "js/TypeDecls.h" + +/* + * A JSClass acts as a vtable for JS objects that allows JSAPI clients to + * control various aspects of the behavior of an object like property lookup. + * It contains some engine-private extensions that allows more control over + * object behavior and, e.g., allows custom slow layout. + */ + +struct JSAtomState; +struct JSFunctionSpec; + +namespace js { + +class PropertyResult; + +// These are equal to js::FunctionClass / js::ExtendedFunctionClass. +extern JS_PUBLIC_DATA const JSClass* const FunctionClassPtr; +extern JS_PUBLIC_DATA const JSClass* const FunctionExtendedClassPtr; + +} // namespace js + +namespace JS { + +/** + * Per ES6, the [[DefineOwnProperty]] internal method has three different + * possible outcomes: + * + * - It can throw an exception (which we indicate by returning false). + * + * - It can return true, indicating unvarnished success. + * + * - It can return false, indicating "strict failure". The property could + * not be defined. It's an error, but no exception was thrown. + * + * It's not just [[DefineOwnProperty]]: all the mutating internal methods have + * the same three outcomes. (The other affected internal methods are [[Set]], + * [[Delete]], [[SetPrototypeOf]], and [[PreventExtensions]].) + * + * If you think this design is awful, you're not alone. But as it's the + * standard, we must represent these boolean "success" values somehow. + * ObjectOpSuccess is the class for this. It's like a bool, but when it's false + * it also stores an error code. + * + * Typical usage: + * + * ObjectOpResult result; + * if (!DefineProperty(cx, obj, id, ..., result)) { + * return false; + * } + * if (!result) { + * return result.reportError(cx, obj, id); + * } + * + * Users don't have to call `result.report()`; another possible ending is: + * + * argv.rval().setBoolean(result.ok()); + * return true; + */ +class ObjectOpResult { + private: + /** + * code_ is either one of the special codes OkCode or Uninitialized, or an + * error code. For now the error codes are JS friend API and are defined in + * js/public/friend/ErrorNumbers.msg. + * + * code_ is uintptr_t (rather than uint32_t) for the convenience of the + * JITs, which would otherwise have to deal with either padding or stack + * alignment on 64-bit platforms. + */ + uintptr_t code_; + + public: + enum SpecialCodes : uintptr_t { OkCode = 0, Uninitialized = uintptr_t(-1) }; + + ObjectOpResult() : code_(Uninitialized) {} + + /* Return true if succeed() was called. */ + bool ok() const { + MOZ_ASSERT(code_ != Uninitialized); + return code_ == OkCode; + } + + explicit operator bool() const { return ok(); } + + /* Set this ObjectOpResult to true and return true. */ + bool succeed() { + code_ = OkCode; + return true; + } + + /* + * Set this ObjectOpResult to false with an error code. + * + * Always returns true, as a convenience. Typical usage will be: + * + * if (funny condition) { + * return result.fail(JSMSG_CANT_DO_THE_THINGS); + * } + * + * The true return value indicates that no exception is pending, and it + * would be OK to ignore the failure and continue. + */ + bool fail(uint32_t msg) { + MOZ_ASSERT(msg != OkCode); + code_ = msg; + return true; + } + + JS_PUBLIC_API bool failCantRedefineProp(); + JS_PUBLIC_API bool failReadOnly(); + JS_PUBLIC_API bool failGetterOnly(); + JS_PUBLIC_API bool failCantDelete(); + + JS_PUBLIC_API bool failCantSetInterposed(); + JS_PUBLIC_API bool failCantDefineWindowElement(); + JS_PUBLIC_API bool failCantDeleteWindowElement(); + JS_PUBLIC_API bool failCantDefineWindowNamedProperty(); + JS_PUBLIC_API bool failCantDeleteWindowNamedProperty(); + JS_PUBLIC_API bool failCantPreventExtensions(); + JS_PUBLIC_API bool failCantSetProto(); + JS_PUBLIC_API bool failNoNamedSetter(); + JS_PUBLIC_API bool failNoIndexedSetter(); + JS_PUBLIC_API bool failNotDataDescriptor(); + JS_PUBLIC_API bool failInvalidDescriptor(); + + // Careful: This case has special handling in Object.defineProperty. + JS_PUBLIC_API bool failCantDefineWindowNonConfigurable(); + + JS_PUBLIC_API bool failBadArrayLength(); + JS_PUBLIC_API bool failBadIndex(); + + uint32_t failureCode() const { + MOZ_ASSERT(!ok()); + return uint32_t(code_); + } + + /* + * Report an error if necessary; return true to proceed and + * false if an error was reported. + * + * The precise rules are like this: + * + * - If ok(), then we succeeded. Do nothing and return true. + * - Otherwise, if |strict| is true, throw a TypeError and return false. + * - Otherwise, do nothing and return true. + */ + bool checkStrictModeError(JSContext* cx, HandleObject obj, HandleId id, + bool strict) { + if (ok() || !strict) { + return true; + } + return reportError(cx, obj, id); + } + + /* + * The same as checkStrictModeError(cx, id, strict), except the + * operation is not associated with a particular property id. This is + * used for [[PreventExtensions]] and [[SetPrototypeOf]]. failureCode() + * must not be an error that has "{0}" in the error message. + */ + bool checkStrictModeError(JSContext* cx, HandleObject obj, bool strict) { + if (ok() || !strict) { + return true; + } + return reportError(cx, obj); + } + + /* Throw a TypeError. Call this only if !ok(). */ + bool reportError(JSContext* cx, HandleObject obj, HandleId id); + + /* + * The same as reportError(cx, obj, id), except the operation is not + * associated with a particular property id. + */ + bool reportError(JSContext* cx, HandleObject obj); + + // Convenience method. Return true if ok(); otherwise throw a TypeError + // and return false. + bool checkStrict(JSContext* cx, HandleObject obj, HandleId id) { + return checkStrictModeError(cx, obj, id, true); + } + + // Convenience method. The same as checkStrict(cx, obj, id), except the + // operation is not associated with a particular property id. + bool checkStrict(JSContext* cx, HandleObject obj) { + return checkStrictModeError(cx, obj, true); + } +}; + +} // namespace JS + +// JSClass operation signatures. + +/** Add a property named by id to obj. */ +typedef bool (*JSAddPropertyOp)(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, JS::HandleValue v); + +/** + * Delete a property named by id in obj. + * + * If an error occurred, return false as per normal JSAPI error practice. + * + * If no error occurred, but the deletion attempt wasn't allowed (perhaps + * because the property was non-configurable), call result.fail() and + * return true. This will cause |delete obj[id]| to evaluate to false in + * non-strict mode code, and to throw a TypeError in strict mode code. + * + * If no error occurred and the deletion wasn't disallowed (this is *not* the + * same as saying that a deletion actually occurred -- deleting a non-existent + * property, or an inherited property, is allowed -- it's just pointless), + * call result.succeed() and return true. + */ +typedef bool (*JSDeletePropertyOp)(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, JS::ObjectOpResult& result); + +/** + * The type of ObjectOps::enumerate. This callback overrides a portion of + * SpiderMonkey's default [[Enumerate]] internal method. When an ordinary object + * is enumerated, that object and each object on its prototype chain is tested + * for an enumerate op, and those ops are called in order. The properties each + * op adds to the 'properties' vector are added to the set of values the for-in + * loop will iterate over. All of this is nonstandard. + * + * An object is "enumerated" when it's the target of a for-in loop or + * JS_Enumerate(). The callback's job is to populate 'properties' with the + * object's property keys. If `enumerableOnly` is true, the callback should only + * add enumerable properties. + */ +typedef bool (*JSNewEnumerateOp)(JSContext* cx, JS::HandleObject obj, + JS::MutableHandleIdVector properties, + bool enumerableOnly); + +/** + * The old-style JSClass.enumerate op should define all lazy properties not + * yet reflected in obj. + */ +typedef bool (*JSEnumerateOp)(JSContext* cx, JS::HandleObject obj); + +/** + * The type of ObjectOps::funToString. This callback allows an object to + * provide a custom string to use when Function.prototype.toString is invoked on + * that object. A null return value means OOM. + */ +typedef JSString* (*JSFunToStringOp)(JSContext* cx, JS::HandleObject obj, + bool isToSource); + +/** + * Resolve a lazy property named by id in obj by defining it directly in obj. + * Lazy properties are those reflected from some peer native property space + * (e.g., the DOM attributes for a given node reflected as obj) on demand. + * + * JS looks for a property in an object, and if not found, tries to resolve + * the given id. *resolvedp should be set to true iff the property was defined + * on |obj|. + */ +typedef bool (*JSResolveOp)(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, bool* resolvedp); + +/** + * A class with a resolve hook can optionally have a mayResolve hook. This hook + * must have no side effects and must return true for a given id if the resolve + * hook may resolve this id. This is useful when we're doing a "pure" lookup: if + * mayResolve returns false, we know we don't have to call the effectful resolve + * hook. + * + * maybeObj, if non-null, is the object on which we're doing the lookup. This + * can be nullptr: during JIT compilation we sometimes know the Class but not + * the object. + */ +typedef bool (*JSMayResolveOp)(const JSAtomState& names, jsid id, + JSObject* maybeObj); + +/** + * Finalize obj, which the garbage collector has determined to be unreachable + * from other live objects or from GC roots. Obviously, finalizers must never + * store a reference to obj. + */ +typedef void (*JSFinalizeOp)(JS::GCContext* gcx, JSObject* obj); + +/** + * Function type for trace operation of the class called to enumerate all + * traceable things reachable from obj's private data structure. For each such + * thing, a trace implementation must call JS::TraceEdge on the thing's + * location. + * + * JSTraceOp implementation can assume that no other threads mutates object + * state. It must not change state of the object or corresponding native + * structures. The only exception for this rule is the case when the embedding + * needs a tight integration with GC. In that case the embedding can check if + * the traversal is a part of the marking phase through calling + * JS_IsGCMarkingTracer and apply a special code like emptying caches or + * marking its native structures. + */ +typedef void (*JSTraceOp)(JSTracer* trc, JSObject* obj); + +typedef size_t (*JSObjectMovedOp)(JSObject* obj, JSObject* old); + +namespace js { + +/* Internal / friend API operation signatures. */ + +typedef bool (*LookupPropertyOp)(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, JS::MutableHandleObject objp, + PropertyResult* propp); +typedef bool (*DefinePropertyOp)(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, + JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult& result); +typedef bool (*HasPropertyOp)(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, bool* foundp); +typedef bool (*GetPropertyOp)(JSContext* cx, JS::HandleObject obj, + JS::HandleValue receiver, JS::HandleId id, + JS::MutableHandleValue vp); +typedef bool (*SetPropertyOp)(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, JS::HandleValue v, + JS::HandleValue receiver, + JS::ObjectOpResult& result); +typedef bool (*GetOwnPropertyOp)( + JSContext* cx, JS::HandleObject obj, JS::HandleId id, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); +typedef bool (*DeletePropertyOp)(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, JS::ObjectOpResult& result); + +class JS_PUBLIC_API ElementAdder { + public: + enum GetBehavior { + // Check if the element exists before performing the Get and preserve + // holes. + CheckHasElemPreserveHoles, + + // Perform a Get operation, like obj[index] in JS. + GetElement + }; + + private: + // Only one of these is used. + JS::RootedObject resObj_; + JS::Value* vp_; + + uint32_t index_; +#ifdef DEBUG + uint32_t length_; +#endif + GetBehavior getBehavior_; + + public: + ElementAdder(JSContext* cx, JSObject* obj, uint32_t length, + GetBehavior behavior) + : resObj_(cx, obj), + vp_(nullptr), + index_(0), +#ifdef DEBUG + length_(length), +#endif + getBehavior_(behavior) { + } + ElementAdder(JSContext* cx, JS::Value* vp, uint32_t length, + GetBehavior behavior) + : resObj_(cx), + vp_(vp), + index_(0), +#ifdef DEBUG + length_(length), +#endif + getBehavior_(behavior) { + } + + GetBehavior getBehavior() const { return getBehavior_; } + + bool append(JSContext* cx, JS::HandleValue v); + void appendHole(); +}; + +typedef bool (*GetElementsOp)(JSContext* cx, JS::HandleObject obj, + uint32_t begin, uint32_t end, + ElementAdder* adder); + +/** Callback for the creation of constructor and prototype objects. */ +typedef JSObject* (*ClassObjectCreationOp)(JSContext* cx, JSProtoKey key); + +/** + * Callback for custom post-processing after class initialization via + * ClassSpec. + */ +typedef bool (*FinishClassInitOp)(JSContext* cx, JS::HandleObject ctor, + JS::HandleObject proto); + +const size_t JSCLASS_CACHED_PROTO_WIDTH = 7; + +struct MOZ_STATIC_CLASS ClassSpec { + ClassObjectCreationOp createConstructor; + ClassObjectCreationOp createPrototype; + const JSFunctionSpec* constructorFunctions; + const JSPropertySpec* constructorProperties; + const JSFunctionSpec* prototypeFunctions; + const JSPropertySpec* prototypeProperties; + FinishClassInitOp finishInit; + uintptr_t flags; + + static const size_t ProtoKeyWidth = JSCLASS_CACHED_PROTO_WIDTH; + + static const uintptr_t ProtoKeyMask = (1 << ProtoKeyWidth) - 1; + static const uintptr_t DontDefineConstructor = 1 << ProtoKeyWidth; + + bool defined() const { return !!createConstructor; } + + // The ProtoKey this class inherits from. + JSProtoKey inheritanceProtoKey() const { + MOZ_ASSERT(defined()); + static_assert(JSProto_Null == 0, "zeroed key must be null"); + + // Default: Inherit from Object. + if (!(flags & ProtoKeyMask)) { + return JSProto_Object; + } + + return JSProtoKey(flags & ProtoKeyMask); + } + + bool shouldDefineConstructor() const { + MOZ_ASSERT(defined()); + return !(flags & DontDefineConstructor); + } +}; + +struct MOZ_STATIC_CLASS ClassExtension { + /** + * Optional hook called when an object is moved by generational or + * compacting GC. + * + * There may exist weak pointers to an object that are not traced through + * when the normal trace APIs are used, for example objects in the wrapper + * cache. This hook allows these pointers to be updated. + * + * Note that this hook can be called before JS_NewObject() returns if a GC + * is triggered during construction of the object. This can happen for + * global objects for example. + * + * The function should return the difference between nursery bytes used and + * tenured bytes used, which may be nonzero e.g. if some nursery-allocated + * data beyond the actual GC thing is moved into malloced memory. + * + * This is used to compute the nursery promotion rate. + */ + JSObjectMovedOp objectMovedOp; +}; + +struct MOZ_STATIC_CLASS ObjectOps { + LookupPropertyOp lookupProperty; + DefinePropertyOp defineProperty; + HasPropertyOp hasProperty; + GetPropertyOp getProperty; + SetPropertyOp setProperty; + GetOwnPropertyOp getOwnPropertyDescriptor; + DeletePropertyOp deleteProperty; + GetElementsOp getElements; + JSFunToStringOp funToString; +}; + +} // namespace js + +static constexpr const js::ClassSpec* JS_NULL_CLASS_SPEC = nullptr; +static constexpr const js::ClassExtension* JS_NULL_CLASS_EXT = nullptr; + +static constexpr const js::ObjectOps* JS_NULL_OBJECT_OPS = nullptr; + +// Classes, objects, and properties. + +// (1 << 0 is unused) + +// Class's initialization code will call `SetNewObjectMetadata` itself. +static const uint32_t JSCLASS_DELAY_METADATA_BUILDER = 1 << 1; + +// Class is an XPCWrappedNative. WeakMaps use this to override the wrapper +// disposal mechanism. +static const uint32_t JSCLASS_IS_WRAPPED_NATIVE = 1 << 2; + +// First reserved slot is `PrivateValue(nsISupports*)` or `UndefinedValue`. +static constexpr uint32_t JSCLASS_SLOT0_IS_NSISUPPORTS = 1 << 3; + +// Objects are DOM. +static const uint32_t JSCLASS_IS_DOMJSCLASS = 1 << 4; + +// If wrapped by an xray wrapper, the builtin class's constructor won't be +// unwrapped and invoked. Instead, the constructor is resolved in the caller's +// compartment and invoked with a wrapped newTarget. The constructor has to +// detect and handle this situation. See PromiseConstructor for details. +static const uint32_t JSCLASS_HAS_XRAYED_CONSTRUCTOR = 1 << 5; + +// Objects of this class act like the value undefined, in some contexts. +static const uint32_t JSCLASS_EMULATES_UNDEFINED = 1 << 6; + +// Reserved for embeddings. +static const uint32_t JSCLASS_USERBIT1 = 1 << 7; + +// To reserve slots fetched and stored via JS_Get/SetReservedSlot, bitwise-or +// JSCLASS_HAS_RESERVED_SLOTS(n) into the initializer for JSClass.flags, where n +// is a constant in [1, 255]. Reserved slots are indexed from 0 to n-1. + +// Room for 8 flags below ... +static const uintptr_t JSCLASS_RESERVED_SLOTS_SHIFT = 8; +// ... and 16 above this field. +static const uint32_t JSCLASS_RESERVED_SLOTS_WIDTH = 8; + +static const uint32_t JSCLASS_RESERVED_SLOTS_MASK = + js::BitMask(JSCLASS_RESERVED_SLOTS_WIDTH); + +static constexpr uint32_t JSCLASS_HAS_RESERVED_SLOTS(uint32_t n) { + return (n & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT; +} + +static constexpr uint32_t JSCLASS_HIGH_FLAGS_SHIFT = + JSCLASS_RESERVED_SLOTS_SHIFT + JSCLASS_RESERVED_SLOTS_WIDTH; + +static const uint32_t JSCLASS_INTERNAL_FLAG1 = + 1 << (JSCLASS_HIGH_FLAGS_SHIFT + 0); +static const uint32_t JSCLASS_IS_GLOBAL = 1 << (JSCLASS_HIGH_FLAGS_SHIFT + 1); +static const uint32_t JSCLASS_INTERNAL_FLAG2 = + 1 << (JSCLASS_HIGH_FLAGS_SHIFT + 2); +static const uint32_t JSCLASS_IS_PROXY = 1 << (JSCLASS_HIGH_FLAGS_SHIFT + 3); +static const uint32_t JSCLASS_SKIP_NURSERY_FINALIZE = + 1 << (JSCLASS_HIGH_FLAGS_SHIFT + 4); + +// Reserved for embeddings. +static const uint32_t JSCLASS_USERBIT2 = 1 << (JSCLASS_HIGH_FLAGS_SHIFT + 5); +static const uint32_t JSCLASS_USERBIT3 = 1 << (JSCLASS_HIGH_FLAGS_SHIFT + 6); + +static const uint32_t JSCLASS_BACKGROUND_FINALIZE = + 1 << (JSCLASS_HIGH_FLAGS_SHIFT + 7); +static const uint32_t JSCLASS_FOREGROUND_FINALIZE = + 1 << (JSCLASS_HIGH_FLAGS_SHIFT + 8); + +// Bits 25 through 31 are reserved for the CACHED_PROTO_KEY mechanism, see +// below. + +// ECMA-262 requires that most constructors used internally create objects +// with "the original Foo.prototype value" as their [[Prototype]] (__proto__) +// member initial value. The "original ... value" verbiage is there because +// in ECMA-262, global properties naming class objects are read/write and +// deleteable, for the most part. +// +// Implementing this efficiently requires that global objects have classes +// with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was +// previously allowed, but is now an ES5 violation and thus unsupported. +// +// JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at +// the beginning of every global object's slots for use by the +// application. +static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5; +static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT = + JSCLASS_GLOBAL_APPLICATION_SLOTS + 1; + +static constexpr uint32_t JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(uint32_t n) { + return JSCLASS_IS_GLOBAL | + JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + n); +} + +static constexpr uint32_t JSCLASS_GLOBAL_FLAGS = + JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0); + +// Fast access to the original value of each standard class's prototype. +static const uint32_t JSCLASS_CACHED_PROTO_SHIFT = JSCLASS_HIGH_FLAGS_SHIFT + 9; +static const uint32_t JSCLASS_CACHED_PROTO_MASK = + js::BitMask(js::JSCLASS_CACHED_PROTO_WIDTH); + +static_assert(JSProto_LIMIT <= (JSCLASS_CACHED_PROTO_MASK + 1), + "JSProtoKey must not exceed the maximum cacheable proto-mask"); + +static constexpr uint32_t JSCLASS_HAS_CACHED_PROTO(JSProtoKey key) { + return uint32_t(key) << JSCLASS_CACHED_PROTO_SHIFT; +} + +struct MOZ_STATIC_CLASS JSClassOps { + /* Function pointer members (may be null). */ + JSAddPropertyOp addProperty; + JSDeletePropertyOp delProperty; + JSEnumerateOp enumerate; + JSNewEnumerateOp newEnumerate; + JSResolveOp resolve; + JSMayResolveOp mayResolve; + JSFinalizeOp finalize; + JSNative call; + JSNative construct; + JSTraceOp trace; +}; + +static constexpr const JSClassOps* JS_NULL_CLASS_OPS = nullptr; + +struct alignas(js::gc::JSClassAlignBytes) JSClass { + const char* name; + uint32_t flags; + const JSClassOps* cOps; + + const js::ClassSpec* spec; + const js::ClassExtension* ext; + const js::ObjectOps* oOps; + + // Public accessors: + + JSAddPropertyOp getAddProperty() const { + return cOps ? cOps->addProperty : nullptr; + } + JSDeletePropertyOp getDelProperty() const { + return cOps ? cOps->delProperty : nullptr; + } + JSEnumerateOp getEnumerate() const { + return cOps ? cOps->enumerate : nullptr; + } + JSNewEnumerateOp getNewEnumerate() const { + return cOps ? cOps->newEnumerate : nullptr; + } + JSResolveOp getResolve() const { return cOps ? cOps->resolve : nullptr; } + JSMayResolveOp getMayResolve() const { + return cOps ? cOps->mayResolve : nullptr; + } + JSNative getCall() const { return cOps ? cOps->call : nullptr; } + JSNative getConstruct() const { return cOps ? cOps->construct : nullptr; } + + bool hasFinalize() const { return cOps && cOps->finalize; } + bool hasTrace() const { return cOps && cOps->trace; } + + bool isTrace(JSTraceOp trace) const { return cOps && cOps->trace == trace; } + + // The special treatment of |finalize| and |trace| is necessary because if we + // assign either of those hooks to a local variable and then call it -- as is + // done with the other hooks -- the GC hazard analysis gets confused. + void doFinalize(JS::GCContext* gcx, JSObject* obj) const { + MOZ_ASSERT(cOps && cOps->finalize); + cOps->finalize(gcx, obj); + } + void doTrace(JSTracer* trc, JSObject* obj) const { + MOZ_ASSERT(cOps && cOps->trace); + cOps->trace(trc, obj); + } + + /* + * Objects of this class aren't native objects. They don't have Shapes that + * describe their properties and layout. Classes using this flag must + * provide their own property behavior, either by being proxy classes (do + * this) or by overriding all the ObjectOps except getElements + * (don't do this). + */ + static const uint32_t NON_NATIVE = JSCLASS_INTERNAL_FLAG2; + + // A JSObject created from a JSClass extends from one of: + // - js::NativeObject + // - js::ProxyObject + // + // While it is possible to introduce new families of objects, it is strongly + // discouraged. The JITs would be entirely unable to optimize them and testing + // coverage is low. The existing NativeObject and ProxyObject are extremely + // flexible and are able to represent the entire Gecko embedding requirements. + // + // NOTE: Internal to SpiderMonkey, there is an experimental js::TypedObject + // object family for future WASM features. + bool isNativeObject() const { return !(flags & NON_NATIVE); } + bool isProxyObject() const { return flags & JSCLASS_IS_PROXY; } + + bool emulatesUndefined() const { return flags & JSCLASS_EMULATES_UNDEFINED; } + + bool isJSFunction() const { + return this == js::FunctionClassPtr || this == js::FunctionExtendedClassPtr; + } + + bool nonProxyCallable() const { + MOZ_ASSERT(!isProxyObject()); + return isJSFunction() || getCall(); + } + + bool isGlobal() const { return flags & JSCLASS_IS_GLOBAL; } + + bool isDOMClass() const { return flags & JSCLASS_IS_DOMJSCLASS; } + + bool shouldDelayMetadataBuilder() const { + return flags & JSCLASS_DELAY_METADATA_BUILDER; + } + + bool isWrappedNative() const { return flags & JSCLASS_IS_WRAPPED_NATIVE; } + + bool slot0IsISupports() const { return flags & JSCLASS_SLOT0_IS_NSISUPPORTS; } + + static size_t offsetOfFlags() { return offsetof(JSClass, flags); } + + // Internal / friend API accessors: + + bool specDefined() const { return spec ? spec->defined() : false; } + JSProtoKey specInheritanceProtoKey() const { + return spec ? spec->inheritanceProtoKey() : JSProto_Null; + } + bool specShouldDefineConstructor() const { + return spec ? spec->shouldDefineConstructor() : true; + } + js::ClassObjectCreationOp specCreateConstructorHook() const { + return spec ? spec->createConstructor : nullptr; + } + js::ClassObjectCreationOp specCreatePrototypeHook() const { + return spec ? spec->createPrototype : nullptr; + } + const JSFunctionSpec* specConstructorFunctions() const { + return spec ? spec->constructorFunctions : nullptr; + } + const JSPropertySpec* specConstructorProperties() const { + return spec ? spec->constructorProperties : nullptr; + } + const JSFunctionSpec* specPrototypeFunctions() const { + return spec ? spec->prototypeFunctions : nullptr; + } + const JSPropertySpec* specPrototypeProperties() const { + return spec ? spec->prototypeProperties : nullptr; + } + js::FinishClassInitOp specFinishInitHook() const { + return spec ? spec->finishInit : nullptr; + } + + JSObjectMovedOp extObjectMovedOp() const { + return ext ? ext->objectMovedOp : nullptr; + } + + js::LookupPropertyOp getOpsLookupProperty() const { + return oOps ? oOps->lookupProperty : nullptr; + } + js::DefinePropertyOp getOpsDefineProperty() const { + return oOps ? oOps->defineProperty : nullptr; + } + js::HasPropertyOp getOpsHasProperty() const { + return oOps ? oOps->hasProperty : nullptr; + } + js::GetPropertyOp getOpsGetProperty() const { + return oOps ? oOps->getProperty : nullptr; + } + js::SetPropertyOp getOpsSetProperty() const { + return oOps ? oOps->setProperty : nullptr; + } + js::GetOwnPropertyOp getOpsGetOwnPropertyDescriptor() const { + return oOps ? oOps->getOwnPropertyDescriptor : nullptr; + } + js::DeletePropertyOp getOpsDeleteProperty() const { + return oOps ? oOps->deleteProperty : nullptr; + } + js::GetElementsOp getOpsGetElements() const { + return oOps ? oOps->getElements : nullptr; + } + JSFunToStringOp getOpsFunToString() const { + return oOps ? oOps->funToString : nullptr; + } +}; + +static constexpr uint32_t JSCLASS_RESERVED_SLOTS(const JSClass* clasp) { + return (clasp->flags >> JSCLASS_RESERVED_SLOTS_SHIFT) & + JSCLASS_RESERVED_SLOTS_MASK; +} + +static constexpr bool JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(const JSClass* clasp) { + return (clasp->flags & JSCLASS_IS_GLOBAL) && + JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT; +} + +static constexpr JSProtoKey JSCLASS_CACHED_PROTO_KEY(const JSClass* clasp) { + return JSProtoKey((clasp->flags >> JSCLASS_CACHED_PROTO_SHIFT) & + JSCLASS_CACHED_PROTO_MASK); +} + +namespace js { + +/** + * Enumeration describing possible values of the [[Class]] internal property + * value of objects. + */ +enum class ESClass { + Object, + Array, + Number, + String, + Boolean, + RegExp, + ArrayBuffer, + SharedArrayBuffer, + Date, + Set, + Map, + Promise, + MapIterator, + SetIterator, + Arguments, + Error, + BigInt, + Function, // Note: Only JSFunction objects. + +#ifdef ENABLE_RECORD_TUPLE + Record, + Tuple, +#endif + + /** None of the above. */ + Other +}; + +/* Fills |vp| with the unboxed value for boxed types, or undefined otherwise. */ +bool Unbox(JSContext* cx, JS::HandleObject obj, JS::MutableHandleValue vp); + +// Classes with JSCLASS_SKIP_NURSERY_FINALIZE or Wrapper classes with +// CROSS_COMPARTMENT flags will not have their finalizer called if they are +// nursery allocated and not promoted to the tenured heap. The finalizers for +// these classes must do nothing except free data which was allocated via +// Nursery::allocateBuffer. +inline bool CanNurseryAllocateFinalizedClass(const JSClass* const clasp) { + MOZ_ASSERT(clasp->hasFinalize()); + return clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE; +} + +#ifdef DEBUG +JS_PUBLIC_API bool HasObjectMovedOp(JSObject* obj); +#endif + +} /* namespace js */ + +#endif /* js_Class_h */ diff --git a/js/public/ComparisonOperators.h b/js/public/ComparisonOperators.h new file mode 100644 index 0000000000..c7f03fa4ca --- /dev/null +++ b/js/public/ComparisonOperators.h @@ -0,0 +1,237 @@ +/* -*- 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/. */ + +/* + * Support comparison operations on wrapper types -- e.g. |JS::Rooted<T>|, + * |JS::Handle<T>|, and so on -- against raw |T| values, against pointers or + * |nullptr| if the wrapper is a pointer wrapper, and against other wrappers + * around compatible types. + */ + +#ifndef js_ComparisonOperators_h +#define js_ComparisonOperators_h + +#include <type_traits> // std::false_type, std::true_type, std::enable_if_t, std::is_pointer_v, std::remove_pointer_t + +// To define |operator==| and |operator!=| for a wrapper class |W| (which may +// or may not be a template class) that contains a |T|: +// +// * Specialize |JS::detail::DefineComparisonOps| for |W|: +// - Make it inherit from |std::true_type|. +// - Include within your specialization a |static get(const W& v)| function +// that returns the value (which may be an lvalue reference) of the |T| in +// |W|. +// * If needed, add |using JS::detail::wrapper_comparison::operator==;| and +// |using JS::detail::wrapper_comparison::operator!=;| to the namespace +// directly containing |W| at the end of this header. (If you are not in +// SpiderMonkey code and have questionably decided to define your own +// wrapper class, add these to its namespace somewhere in your code.) +// +// The first step opts the wrapper class into comparison support and defines a +// generic means of extracting a comparable |T| out of an instance. +// +// The second step ensures that symmetric |operator==| and |operator!=| are +// exposed for the wrapper, accepting two wrappers or a wrapper and a suitable +// raw value. +// +// Failure to perform *both* steps will likely result in errors like +// 'invalid operands to binary expression' or 'no match for operator==' +// when comparing an instance of your wrapper. + +namespace JS { + +namespace detail { + +// By default, comparison ops are not supported for types. +template <typename T> +struct DefineComparisonOps : std::false_type {}; + +// Define functions for the core equality operations, that the actual operators +// can all invoke. + +// Compare two wrapper types. Assumes both wrapper types support comparison +// operators. +template <typename W, typename OW> +inline bool WrapperEqualsWrapper(const W& wrapper, const OW& other) { + return JS::detail::DefineComparisonOps<W>::get(wrapper) == + JS::detail::DefineComparisonOps<OW>::get(other); +} + +// Compare a wrapper against a value of its unwrapped element type (or against a +// value that implicitly converts to that unwrapped element type). Assumes its +// wrapper argument supports comparison operators. +template <typename W> +inline bool WrapperEqualsUnwrapped(const W& wrapper, + const typename W::ElementType& value) { + return JS::detail::DefineComparisonOps<W>::get(wrapper) == value; +} + +// Compare a wrapper containing a pointer against a pointer to const element +// type. Assumes its wrapper argument supports comparison operators. +template <typename W> +inline bool WrapperEqualsPointer( + const W& wrapper, + const typename std::remove_pointer_t<typename W::ElementType>* ptr) { + return JS::detail::DefineComparisonOps<W>::get(wrapper) == ptr; +} + +// It is idiomatic C++ to define operators on user-defined types in the +// namespace of their operands' types (not at global scope, which isn't examined +// if at point of operator use another operator definition shadows the global +// definition). But our wrappers live in *multiple* namespaces (|namespace js| +// and |namespace JS| in SpiderMonkey), so we can't literally do that without +// defining ambiguous overloads. +// +// Instead, we define the operators *once* in a namespace containing nothing +// else at all. Then we |using| the operators into each namespace containing +// a wrapper type. |using| creates *aliases*, so two |using|s of the same +// operator contribute only one overload to overload resolution. +namespace wrapper_comparison { + +// Comparisons between potentially-differing wrappers. +template <typename W, typename OW> +inline typename std::enable_if_t<JS::detail::DefineComparisonOps<W>::value && + JS::detail::DefineComparisonOps<OW>::value, + bool> +operator==(const W& wrapper, const OW& other) { + return JS::detail::WrapperEqualsWrapper(wrapper, other); +} + +template <typename W, typename OW> +inline typename std::enable_if_t<JS::detail::DefineComparisonOps<W>::value && + JS::detail::DefineComparisonOps<OW>::value, + bool> +operator!=(const W& wrapper, const OW& other) { + return !JS::detail::WrapperEqualsWrapper(wrapper, other); +} + +// Comparisons between a wrapper and its unwrapped element type. +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator==(const W& wrapper, const typename W::ElementType& value) { + return WrapperEqualsUnwrapped(wrapper, value); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator!=(const W& wrapper, const typename W::ElementType& value) { + return !WrapperEqualsUnwrapped(wrapper, value); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator==(const typename W::ElementType& value, const W& wrapper) { + return WrapperEqualsUnwrapped(wrapper, value); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator!=(const typename W::ElementType& value, const W& wrapper) { + return !WrapperEqualsUnwrapped(wrapper, value); +} + +// For wrappers around a pointer type, comparisons between a wrapper object +// and a const element pointer. +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value && + std::is_pointer_v<typename W::ElementType>, + bool> +operator==(const W& wrapper, + const typename std::remove_pointer_t<typename W::ElementType>* ptr) { + return WrapperEqualsPointer(wrapper, ptr); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value && + std::is_pointer_v<typename W::ElementType>, + bool> +operator!=(const W& wrapper, + const typename std::remove_pointer_t<typename W::ElementType>* ptr) { + return !WrapperEqualsPointer(wrapper, ptr); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value && + std::is_pointer_v<typename W::ElementType>, + bool> +operator==(const typename std::remove_pointer_t<typename W::ElementType>* ptr, + const W& wrapper) { + return WrapperEqualsPointer(wrapper, ptr); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value && + std::is_pointer_v<typename W::ElementType>, + bool> +operator!=(const typename std::remove_pointer_t<typename W::ElementType>* ptr, + const W& wrapper) { + return !WrapperEqualsPointer(wrapper, ptr); +} + +// For wrappers around a pointer type, comparisons between a wrapper object +// and |nullptr|. +// +// These overloads are a workaround for gcc hazard build bugs. Per spec, +// |nullptr -> const T*| for the wrapper-pointer operators immediately above +// this is a standard conversion sequence (consisting of a single pointer +// conversion). Meanwhile, |nullptr -> T* const&| for the wrapper-element +// operators just above that, is a pointer conversion to |T*|, then an identity +// conversion of the |T* const| to a reference. The former conversion sequence +// is a proper subsequence of the latter, so it *should* be a better conversion +// sequence and thus should be the better overload. But gcc doesn't implement +// things this way, so we add overloads directly targeting |nullptr| as an exact +// match, preferred to either of those overloads. +// +// We should be able to remove these overloads when gcc hazard builds use modern +// clang. +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator==(const W& wrapper, std::nullptr_t) { + return WrapperEqualsUnwrapped(wrapper, nullptr); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator!=(const W& wrapper, std::nullptr_t) { + return !WrapperEqualsUnwrapped(wrapper, nullptr); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator==(std::nullptr_t, const W& wrapper) { + return WrapperEqualsUnwrapped(wrapper, nullptr); +} + +template <typename W> +inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> +operator!=(std::nullptr_t, const W& wrapper) { + return !WrapperEqualsUnwrapped(wrapper, nullptr); +} + +} // namespace wrapper_comparison + +} // namespace detail + +} // namespace JS + +// Expose wrapper-supporting |operator==| and |operator!=| in the namespaces of +// all SpiderMonkey's wrapper classes that support comparisons. + +namespace JS { + +using JS::detail::wrapper_comparison::operator==; +using JS::detail::wrapper_comparison::operator!=; + +} // namespace JS + +namespace js { + +using JS::detail::wrapper_comparison::operator==; +using JS::detail::wrapper_comparison::operator!=; + +} // namespace js + +#endif // js_ComparisonOperators_h diff --git a/js/public/CompilationAndEvaluation.h b/js/public/CompilationAndEvaluation.h new file mode 100644 index 0000000000..ce0090dea9 --- /dev/null +++ b/js/public/CompilationAndEvaluation.h @@ -0,0 +1,233 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* Functions for compiling and evaluating scripts. */ + +#ifndef js_CompilationAndEvaluation_h +#define js_CompilationAndEvaluation_h + +#include <stddef.h> // size_t +#include <stdio.h> // FILE + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/RootingAPI.h" // JS::Handle, JS::MutableHandle +#include "js/TypeDecls.h" + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSFunction; +class JS_PUBLIC_API JSObject; +class JS_PUBLIC_API JSScript; + +namespace mozilla { +union Utf8Unit; +} + +namespace JS { + +class JS_PUBLIC_API InstantiateOptions; +class JS_PUBLIC_API ReadOnlyCompileOptions; + +template <typename UnitT> +class SourceText; + +} // namespace JS + +/** + * Given a buffer, return false if the buffer might become a valid JavaScript + * script with the addition of more lines, or true if the validity of such a + * script is conclusively known (because it's the prefix of a valid script -- + * and possibly the entirety of such a script). + * + * The intent of this function is to enable interactive compilation: accumulate + * lines in a buffer until JS_Utf8BufferIsCompilableUnit is true, then pass it + * to the compiler. + * + * The provided buffer is interpreted as UTF-8 data. An error is reported if + * a UTF-8 encoding error is encountered. + */ +extern JS_PUBLIC_API bool JS_Utf8BufferIsCompilableUnit( + JSContext* cx, JS::Handle<JSObject*> obj, const char* utf8, size_t length); + +/* + * NB: JS_ExecuteScript and the JS::Evaluate APIs come in two flavors: either + * they use the global as the scope, or they take a HandleValueVector of + * objects to use as the scope chain. In the former case, the global is also + * used as the "this" keyword value and the variables object (ECMA parlance for + * where 'var' and 'function' bind names) of the execution context for script. + * In the latter case, the first object in the provided list is used, unless the + * list is empty, in which case the global is used. + * + * Why a runtime option? The alternative is to add APIs duplicating those + * for the other value of flags, and that doesn't seem worth the code bloat + * cost. Such new entry points would probably have less obvious names, too, so + * would not tend to be used. The ContextOptionsRef adjustment, OTOH, can be + * more easily hacked into existing code that does not depend on the bug; such + * code can continue to use the familiar JS::Evaluate, etc., entry points. + */ + +/** + * Evaluate a script in the scope of the current global of cx. + */ +extern JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx, + JS::Handle<JSScript*> script, + JS::MutableHandle<JS::Value> rval); + +extern JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx, + JS::Handle<JSScript*> script); + +/** + * As above, but providing an explicit scope chain. envChain must not include + * the global object on it; that's implicit. It needs to contain the other + * objects that should end up on the script's scope chain. + */ +extern JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx, + JS::HandleObjectVector envChain, + JS::Handle<JSScript*> script, + JS::MutableHandle<JS::Value> rval); + +extern JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx, + JS::HandleObjectVector envChain, + JS::Handle<JSScript*> script); + +namespace JS { + +/** + * Evaluate the given source buffer in the scope of the current global of cx, + * and return the completion value in |rval|. + */ +extern JS_PUBLIC_API bool Evaluate(JSContext* cx, + const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf, + MutableHandle<Value> rval); + +/** + * As above, but providing an explicit scope chain. envChain must not include + * the global object on it; that's implicit. It needs to contain the other + * objects that should end up on the script's scope chain. + */ +extern JS_PUBLIC_API bool Evaluate(JSContext* cx, HandleObjectVector envChain, + const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf, + MutableHandle<Value> rval); + +/** + * Evaluate the provided UTF-8 data in the scope of the current global of |cx|, + * and return the completion value in |rval|. If the data contains invalid + * UTF-8, an error is reported. + */ +extern JS_PUBLIC_API bool Evaluate(JSContext* cx, + const ReadOnlyCompileOptions& options, + SourceText<mozilla::Utf8Unit>& srcBuf, + MutableHandle<Value> rval); + +/** + * Evaluate the UTF-8 contents of the file at the given path, and return the + * completion value in |rval|. (The path itself is UTF-8 encoded, too.) If + * the contents contain any malformed UTF-8, an error is reported. + */ +extern JS_PUBLIC_API bool EvaluateUtf8Path( + JSContext* cx, const ReadOnlyCompileOptions& options, const char* filename, + MutableHandle<Value> rval); + +/** + * Compile the provided script using the given options. Return the script on + * success, or return null on failure (usually with an error reported). + */ +extern JS_PUBLIC_API JSScript* Compile(JSContext* cx, + const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf); + +/** + * Compile the provided script using the given options. Return the script on + * success, or return null on failure (usually with an error reported). + */ +extern JS_PUBLIC_API JSScript* Compile(JSContext* cx, + const ReadOnlyCompileOptions& options, + SourceText<mozilla::Utf8Unit>& srcBuf); + +/** + * Compile the UTF-8 contents of the given file into a script. It is an error + * if the file contains invalid UTF-8. Return the script on success, or return + * null on failure (usually with an error reported). + */ +extern JS_PUBLIC_API JSScript* CompileUtf8File( + JSContext* cx, const ReadOnlyCompileOptions& options, FILE* file); + +/** + * Compile the UTF-8 contents of the file at the given path into a script. + * (The path itself is in the system encoding, not [necessarily] UTF-8.) It + * is an error if the file's contents are invalid UTF-8. Return the script on + * success, or return null on failure (usually with an error reported). + */ +extern JS_PUBLIC_API JSScript* CompileUtf8Path( + JSContext* cx, const ReadOnlyCompileOptions& options, const char* filename); + +/** + * Compile a function with envChain plus the global as its scope chain. + * envChain must contain objects in the current compartment of cx. The actual + * scope chain used for the function will consist of With wrappers for those + * objects, followed by the current global of the compartment cx is in. This + * global must not be explicitly included in the scope chain. + */ +extern JS_PUBLIC_API JSFunction* CompileFunction( + JSContext* cx, HandleObjectVector envChain, + const ReadOnlyCompileOptions& options, const char* name, unsigned nargs, + const char* const* argnames, SourceText<char16_t>& srcBuf); + +/** + * Compile a function with envChain plus the global as its scope chain. + * envChain must contain objects in the current compartment of cx. The actual + * scope chain used for the function will consist of With wrappers for those + * objects, followed by the current global of the compartment cx is in. This + * global must not be explicitly included in the scope chain. + */ +extern JS_PUBLIC_API JSFunction* CompileFunction( + JSContext* cx, HandleObjectVector envChain, + const ReadOnlyCompileOptions& options, const char* name, unsigned nargs, + const char* const* argnames, SourceText<mozilla::Utf8Unit>& srcBuf); + +/** + * Identical to the CompileFunction overload above for UTF-8, but with + * Rust-friendly ergonomics. + */ +extern JS_PUBLIC_API JSFunction* CompileFunctionUtf8( + JSContext* cx, HandleObjectVector envChain, + const ReadOnlyCompileOptions& options, const char* name, unsigned nargs, + const char* const* argnames, const char* utf8, size_t length); + +/* + * For a script compiled with the hideScriptFromDebugger option, expose the + * script to the debugger by calling the debugger's onNewScript hook. + */ +extern JS_PUBLIC_API void ExposeScriptToDebugger(JSContext* cx, + Handle<JSScript*> script); + +/* + * JSScripts have associated with them (via their ScriptSourceObjects) some + * metadata used by the debugger. The following API functions are used to set + * that metadata on scripts, functions and modules. + * + * The metadata consists of: + * - A privateValue, which is used to keep some object value associated + * with the script. + * - The elementAttributeName is used by Gecko + * - The introductionScript is used by the debugger to identify which + * script created which. Only set for dynamicaly generated scripts. + * - scriptOrModule is used to transfer private value metadata from + * script to script + * + * Callers using UpdateDebugMetaData need to have set deferDebugMetadata + * in the compile options; this hides the script from the debugger until + * the debug metadata is provided by the UpdateDebugMetadata call. + */ +extern JS_PUBLIC_API bool UpdateDebugMetadata( + JSContext* cx, Handle<JSScript*> script, const InstantiateOptions& options, + HandleValue privateValue, HandleString elementAttributeName, + HandleScript introScript, HandleScript scriptOrModule); + +} /* namespace JS */ + +#endif /* js_CompilationAndEvaluation_h */ diff --git a/js/public/CompileOptions.h b/js/public/CompileOptions.h new file mode 100644 index 0000000000..99fc5235e5 --- /dev/null +++ b/js/public/CompileOptions.h @@ -0,0 +1,700 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * Options for JavaScript compilation. + * + * In the most common use case, a CompileOptions instance is allocated on the + * stack, and holds non-owning references to non-POD option values: strings, + * principals, objects, and so on. The code declaring the instance guarantees + * that such option values will outlive the CompileOptions itself: objects are + * otherwise rooted, principals have had their reference counts bumped, and + * strings won't be freed until the CompileOptions goes out of scope. In this + * situation, CompileOptions only refers to things others own, so it can be + * lightweight. + * + * In some cases, however, we need to hold compilation options with a + * non-stack-like lifetime. For example, JS::CompileOffThread needs to save + * compilation options where a worker thread can find them, then return + * immediately. The worker thread will come along at some later point, and use + * the options. + * + * The compiler itself just needs to be able to access a collection of options; + * it doesn't care who owns them, or what's keeping them alive. It does its + * own addrefs/copies/tracing/etc. + * + * Furthermore, in some cases compile options are propagated from one entity to + * another (e.g. from a script to a function defined in that script). This + * involves copying over some, but not all, of the options. + * + * So we have a class hierarchy that reflects these four use cases: + * + * - TransitiveCompileOptions is the common base class, representing options + * that should get propagated from a script to functions defined in that + * script. This class is abstract and is only ever used as a subclass. + * + * - ReadOnlyCompileOptions is the only subclass of TransitiveCompileOptions, + * representing a full set of compile options. It can be used by code that + * simply needs to access options set elsewhere, like the compiler. This + * class too is abstract and is only ever used as a subclass. + * + * - The usual CompileOptions class must be stack-allocated, and holds + * non-owning references to the filename, element, and so on. It's derived + * from ReadOnlyCompileOptions, so the compiler can use it. + * + * - OwningCompileOptions roots / copies / reference counts of all its values, + * and unroots / frees / releases them when it is destructed. It too is + * derived from ReadOnlyCompileOptions, so the compiler accepts it. + */ + +#ifndef js_CompileOptions_h +#define js_CompileOptions_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf + +#include <stddef.h> // size_t +#include <stdint.h> // uint8_t, uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/TypeDecls.h" // JS::MutableHandle (fwd) + +namespace JS { + +enum class AsmJSOption : uint8_t { + Enabled, + DisabledByAsmJSPref, + DisabledByLinker, + DisabledByNoWasmCompiler, + DisabledByDebugger, +}; + +#define FOREACH_DELAZIFICATION_STRATEGY(_) \ + /* Do not delazify anything eagerly. */ \ + _(OnDemandOnly) \ + \ + /* \ + * Compare the stencil produced by concurrent depth first delazification and \ + * on-demand delazification. Any differences would crash SpiderMonkey with \ + * an assertion. \ + */ \ + _(CheckConcurrentWithOnDemand) \ + \ + /* \ + * Delazifiy functions in a depth first traversal of the functions. \ + */ \ + _(ConcurrentDepthFirst) \ + \ + /* \ + * Delazifiy functions strating with the largest function first. \ + */ \ + _(ConcurrentLargeFirst) \ + \ + /* \ + * Parse everything eagerly, from the first parse. \ + * \ + * NOTE: Either the Realm configuration or specialized VM operating modes \ + * may disallow syntax-parse altogether. These conditions are checked in the \ + * CompileOptions constructor. \ + */ \ + _(ParseEverythingEagerly) + +enum class DelazificationOption : uint8_t { +#define _ENUM_ENTRY(Name) Name, + FOREACH_DELAZIFICATION_STRATEGY(_ENUM_ENTRY) +#undef _ENUM_ENTRY +}; + +class JS_PUBLIC_API InstantiateOptions; +class JS_PUBLIC_API DecodeOptions; + +/** + * The common base class for the CompileOptions hierarchy. + * + * Use this in code that needs to propagate compile options from one + * compilation unit to another. + */ +class JS_PUBLIC_API TransitiveCompileOptions { + friend class JS_PUBLIC_API DecodeOptions; + + protected: + // non-POD options: + + // UTF-8 encoded file name. + const char* filename_ = nullptr; + + // UTF-8 encoded introducer file name. + const char* introducerFilename_ = nullptr; + + const char16_t* sourceMapURL_ = nullptr; + + // POD options: + // WARNING: When adding new fields, don't forget to add them to + // copyPODTransitiveOptions. + + /** + * The Web Platform allows scripts to be loaded from arbitrary cross-origin + * sources. This allows an attack by which a malicious website loads a + * sensitive file (say, a bank statement) cross-origin (using the user's + * cookies), and sniffs the generated syntax errors (via a window.onerror + * handler) for juicy morsels of its contents. + * + * To counter this attack, HTML5 specifies that script errors should be + * sanitized ("muted") when the script is not same-origin with the global + * for which it is loaded. Callers should set this flag for cross-origin + * scripts, and it will be propagated appropriately to child scripts and + * passed back in JSErrorReports. + */ + bool mutedErrors_ = false; + + // Either the Realm configuration or the compile request may force + // strict-mode. + bool forceStrictMode_ = false; + + // The Realm of this script is configured to resist fingerprinting. + bool shouldResistFingerprinting_ = false; + + // The context has specified that source pragmas should be parsed. + bool sourcePragmas_ = true; + + // Flag used to bypass the filename validation callback. + // See also SetFilenameValidationCallback. + bool skipFilenameValidation_ = false; + + bool hideScriptFromDebugger_ = false; + + // If set, this script will be hidden from the debugger. The requirement + // is that once compilation is finished, a call to UpdateDebugMetadata will + // be made, which will update the SSO with the appropiate debug metadata, + // and expose the script to the debugger (if hideScriptFromDebugger_ isn't + // set) + bool deferDebugMetadata_ = false; + + // Off-thread delazification strategy is used to tell off-thread tasks how the + // delazification should be performed. Multiple strategies are available in + // order to test different approaches to the concurrent delazification. + DelazificationOption eagerDelazificationStrategy_ = + DelazificationOption::OnDemandOnly; + + friend class JS_PUBLIC_API InstantiateOptions; + + public: + bool selfHostingMode = false; + AsmJSOption asmJSOption = AsmJSOption::DisabledByAsmJSPref; + bool throwOnAsmJSValidationFailureOption = false; + bool forceAsync = false; + bool discardSource = false; + bool sourceIsLazy = false; + bool allowHTMLComments = true; + bool nonSyntacticScope = false; + + // Top-level await is enabled by default but is not supported for chrome + // modules loaded with ChromeUtils.importModule. + bool topLevelAwait = true; + + bool importAssertions = false; + + // When decoding from XDR into a Stencil, directly reference data in the + // buffer (where possible) instead of copying it. This is an optional + // performance optimization, and may also reduce memory if the buffer is going + // remain alive anyways. + // + // NOTE: The XDR buffer must remain alive as long as the Stencil does. Special + // care must be taken that there are no addition shared references to + // the Stencil. + // + // NOTE: Instantiated GC things may still outlive the buffer as long as the + // Stencil was cleaned up. This is covers a typical case where a decoded + // Stencil is instantiated once and then thrown away. + bool borrowBuffer = false; + + // Similar to `borrowBuffer`, but additionally the JSRuntime may directly + // reference data in the buffer for JS bytecode. The `borrowBuffer` flag must + // be set if this is set. This can be a memory optimization in multi-process + // architectures where a (read-only) XDR buffer is mapped into multiple + // processes. + // + // NOTE: When using this mode, the XDR buffer must live until JS_Shutdown is + // called. There is currently no mechanism to release the data sooner. + bool usePinnedBytecode = false; + + // When performing off-thread task that generates JS::Stencil as output, + // allocate JS::InstantiationStorage off main thread to reduce the + // main thread allocation. + bool allocateInstantiationStorage = false; + + // De-optimize ES module's top-level `var`s, in order to define all of them + // on the ModuleEnvironmentObject, instead of local slot. + // + // This is used for providing all global variables in Cu.import return value + // (see bug 1766761 for more details), and this is temporary solution until + // ESM-ification finishes. + // + // WARNING: This option will eventually be removed. + bool deoptimizeModuleGlobalVars = false; + + /** + * |introductionType| is a statically allocated C string. See JSScript.h + * for more information. + */ + const char* introductionType = nullptr; + + unsigned introductionLineno = 0; + uint32_t introductionOffset = 0; + bool hasIntroductionInfo = false; + + // WARNING: When adding new fields, don't forget to add them to + // copyPODTransitiveOptions. + + protected: + TransitiveCompileOptions() = default; + + // Set all POD options (those not requiring reference counts, copies, + // rooting, or other hand-holding) to their values in |rhs|. + void copyPODTransitiveOptions(const TransitiveCompileOptions& rhs); + + bool isEagerDelazificationEqualTo(DelazificationOption val) const { + return eagerDelazificationStrategy() == val; + } + + template <DelazificationOption... Values> + bool eagerDelazificationIsOneOf() const { + return (isEagerDelazificationEqualTo(Values) || ...); + } + + public: + // Read-only accessors for non-POD options. The proper way to set these + // depends on the derived type. + bool mutedErrors() const { return mutedErrors_; } + bool shouldResistFingerprinting() const { + return shouldResistFingerprinting_; + } + bool forceFullParse() const { + return eagerDelazificationIsOneOf< + DelazificationOption::ParseEverythingEagerly>(); + } + bool forceStrictMode() const { return forceStrictMode_; } + bool consumeDelazificationCache() const { + return eagerDelazificationIsOneOf< + DelazificationOption::ConcurrentDepthFirst, + DelazificationOption::ConcurrentLargeFirst>(); + } + bool populateDelazificationCache() const { + return eagerDelazificationIsOneOf< + DelazificationOption::CheckConcurrentWithOnDemand, + DelazificationOption::ConcurrentDepthFirst, + DelazificationOption::ConcurrentLargeFirst>(); + } + bool waitForDelazificationCache() const { + return eagerDelazificationIsOneOf< + DelazificationOption::CheckConcurrentWithOnDemand>(); + } + bool checkDelazificationCache() const { + return eagerDelazificationIsOneOf< + DelazificationOption::CheckConcurrentWithOnDemand>(); + } + DelazificationOption eagerDelazificationStrategy() const { + return eagerDelazificationStrategy_; + } + bool sourcePragmas() const { return sourcePragmas_; } + const char* filename() const { return filename_; } + const char* introducerFilename() const { return introducerFilename_; } + const char16_t* sourceMapURL() const { return sourceMapURL_; } + + TransitiveCompileOptions(const TransitiveCompileOptions&) = delete; + TransitiveCompileOptions& operator=(const TransitiveCompileOptions&) = delete; + +#if defined(DEBUG) || defined(JS_JITSPEW) + template <typename Printer> + void dumpWith(Printer& print) const { +# define PrintFields_(Name) print(#Name, Name) + PrintFields_(filename_); + PrintFields_(introducerFilename_); + PrintFields_(sourceMapURL_); + PrintFields_(mutedErrors_); + PrintFields_(forceStrictMode_); + PrintFields_(shouldResistFingerprinting_); + PrintFields_(sourcePragmas_); + PrintFields_(skipFilenameValidation_); + PrintFields_(hideScriptFromDebugger_); + PrintFields_(deferDebugMetadata_); + PrintFields_(eagerDelazificationStrategy_); + PrintFields_(selfHostingMode); + PrintFields_(asmJSOption); + PrintFields_(throwOnAsmJSValidationFailureOption); + PrintFields_(forceAsync); + PrintFields_(discardSource); + PrintFields_(sourceIsLazy); + PrintFields_(allowHTMLComments); + PrintFields_(nonSyntacticScope); + PrintFields_(topLevelAwait); + PrintFields_(importAssertions); + PrintFields_(borrowBuffer); + PrintFields_(usePinnedBytecode); + PrintFields_(allocateInstantiationStorage); + PrintFields_(deoptimizeModuleGlobalVars); + PrintFields_(introductionType); + PrintFields_(introductionLineno); + PrintFields_(introductionOffset); + PrintFields_(hasIntroductionInfo); +# undef PrintFields_ + } +#endif // defined(DEBUG) || defined(JS_JITSPEW) +}; + +/** + * The class representing a full set of compile options. + * + * Use this in code that only needs to access compilation options created + * elsewhere, like the compiler. Don't instantiate this class (the constructor + * is protected anyway); instead, create instances only of the derived classes: + * CompileOptions and OwningCompileOptions. + */ +class JS_PUBLIC_API ReadOnlyCompileOptions : public TransitiveCompileOptions { + public: + // POD options. + unsigned lineno = 1; + unsigned column = 0; + + // The offset within the ScriptSource's full uncompressed text of the first + // character we're presenting for compilation with this CompileOptions. + // + // When we compile a lazy script, we pass the compiler only the substring of + // the source the lazy function occupies. With chunked decompression, we may + // not even have the complete uncompressed source present in memory. But parse + // node positions are offsets within the ScriptSource's full text, and + // BaseScript indicate their substring of the full source by its starting and + // ending offsets within the full text. This scriptSourceOffset field lets the + // frontend convert between these offsets and offsets within the substring + // presented for compilation. + unsigned scriptSourceOffset = 0; + + // These only apply to non-function scripts. + bool isRunOnce = false; + bool noScriptRval = false; + + protected: + ReadOnlyCompileOptions() = default; + + void copyPODNonTransitiveOptions(const ReadOnlyCompileOptions& rhs); + + ReadOnlyCompileOptions(const ReadOnlyCompileOptions&) = delete; + ReadOnlyCompileOptions& operator=(const ReadOnlyCompileOptions&) = delete; + + public: +#if defined(DEBUG) || defined(JS_JITSPEW) + template <typename Printer> + void dumpWith(Printer& print) const { + this->TransitiveCompileOptions::dumpWith(print); +# define PrintFields_(Name) print(#Name, Name) + PrintFields_(lineno); + PrintFields_(column); + PrintFields_(scriptSourceOffset); + PrintFields_(isRunOnce); + PrintFields_(noScriptRval); +# undef PrintFields_ + } +#endif // defined(DEBUG) || defined(JS_JITSPEW) +}; + +/** + * Compilation options, with dynamic lifetime. An instance of this type + * makes a copy of / holds / roots all dynamically allocated resources + * (principals; elements; strings) that it refers to. Its destructor frees + * / drops / unroots them. This is heavier than CompileOptions, below, but + * unlike CompileOptions, it can outlive any given stack frame. + * + * Note that this *roots* any JS values it refers to - they're live + * unconditionally. Thus, instances of this type can't be owned, directly + * or indirectly, by a JavaScript object: if any value that this roots ever + * comes to refer to the object that owns this, then the whole cycle, and + * anything else it entrains, will never be freed. + */ +class JS_PUBLIC_API OwningCompileOptions final : public ReadOnlyCompileOptions { + public: + // A minimal constructor, for use with OwningCompileOptions::copy. + explicit OwningCompileOptions(JSContext* cx); + ~OwningCompileOptions(); + + /** Set this to a copy of |rhs|. Return false on OOM. */ + bool copy(JSContext* cx, const ReadOnlyCompileOptions& rhs); + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + OwningCompileOptions& setIsRunOnce(bool once) { + isRunOnce = once; + return *this; + } + + OwningCompileOptions& setForceStrictMode() { + forceStrictMode_ = true; + return *this; + } + + OwningCompileOptions& setModule() { + // ES6 10.2.1 Module code is always strict mode code. + setForceStrictMode(); + setIsRunOnce(true); + allowHTMLComments = false; + return *this; + } + + private: + void release(); + + OwningCompileOptions(const OwningCompileOptions&) = delete; + OwningCompileOptions& operator=(const OwningCompileOptions&) = delete; +}; + +/** + * Compilation options stored on the stack. An instance of this type + * simply holds references to dynamically allocated resources (element; + * filename; source map URL) that are owned by something else. If you + * create an instance of this type, it's up to you to guarantee that + * everything you store in it will outlive it. + */ +class MOZ_STACK_CLASS JS_PUBLIC_API CompileOptions final + : public ReadOnlyCompileOptions { + public: + // Default options determined using the JSContext. + explicit CompileOptions(JSContext* cx); + + // Copy both the transitive and the non-transitive options from another + // options object. + CompileOptions(JSContext* cx, const ReadOnlyCompileOptions& rhs) + : ReadOnlyCompileOptions() { + copyPODNonTransitiveOptions(rhs); + copyPODTransitiveOptions(rhs); + + filename_ = rhs.filename(); + introducerFilename_ = rhs.introducerFilename(); + sourceMapURL_ = rhs.sourceMapURL(); + } + + // Construct CompileOptions for FrontendContext-APIs. + struct ForFrontendContext {}; + explicit CompileOptions(const ForFrontendContext&) + : ReadOnlyCompileOptions() {} + + CompileOptions& setFile(const char* f) { + filename_ = f; + return *this; + } + + CompileOptions& setLine(unsigned l) { + lineno = l; + return *this; + } + + CompileOptions& setFileAndLine(const char* f, unsigned l) { + filename_ = f; + lineno = l; + return *this; + } + + CompileOptions& setSourceMapURL(const char16_t* s) { + sourceMapURL_ = s; + return *this; + } + + CompileOptions& setMutedErrors(bool mute) { + mutedErrors_ = mute; + return *this; + } + + CompileOptions& setColumn(unsigned c) { + column = c; + return *this; + } + + CompileOptions& setScriptSourceOffset(unsigned o) { + scriptSourceOffset = o; + return *this; + } + + CompileOptions& setIsRunOnce(bool once) { + isRunOnce = once; + return *this; + } + + CompileOptions& setNoScriptRval(bool nsr) { + noScriptRval = nsr; + return *this; + } + + CompileOptions& setSkipFilenameValidation(bool b) { + skipFilenameValidation_ = b; + return *this; + } + + CompileOptions& setSelfHostingMode(bool shm) { + selfHostingMode = shm; + return *this; + } + + CompileOptions& setSourceIsLazy(bool l) { + sourceIsLazy = l; + return *this; + } + + CompileOptions& setNonSyntacticScope(bool n) { + nonSyntacticScope = n; + return *this; + } + + CompileOptions& setIntroductionType(const char* t) { + introductionType = t; + return *this; + } + + CompileOptions& setDeferDebugMetadata(bool v = true) { + deferDebugMetadata_ = v; + return *this; + } + + CompileOptions& setHideScriptFromDebugger(bool v = true) { + hideScriptFromDebugger_ = v; + return *this; + } + + CompileOptions& setIntroductionInfo(const char* introducerFn, + const char* intro, unsigned line, + uint32_t offset) { + introducerFilename_ = introducerFn; + introductionType = intro; + introductionLineno = line; + introductionOffset = offset; + hasIntroductionInfo = true; + return *this; + } + + // Set introduction information according to any currently executing script. + CompileOptions& setIntroductionInfoToCaller( + JSContext* cx, const char* introductionType, + JS::MutableHandle<JSScript*> introductionScript); + + CompileOptions& setDiscardSource() { + discardSource = true; + return *this; + } + + CompileOptions& setForceFullParse() { + eagerDelazificationStrategy_ = DelazificationOption::ParseEverythingEagerly; + return *this; + } + + CompileOptions& setEagerDelazificationStrategy( + DelazificationOption strategy) { + // forceFullParse is at the moment considered as a non-overridable strategy. + MOZ_RELEASE_ASSERT(eagerDelazificationStrategy_ != + DelazificationOption::ParseEverythingEagerly || + strategy == + DelazificationOption::ParseEverythingEagerly); + eagerDelazificationStrategy_ = strategy; + return *this; + } + + CompileOptions& setForceStrictMode() { + forceStrictMode_ = true; + return *this; + } + + CompileOptions& setModule() { + // ES6 10.2.1 Module code is always strict mode code. + setForceStrictMode(); + setIsRunOnce(true); + allowHTMLComments = false; + return *this; + } + + CompileOptions(const CompileOptions& rhs) = delete; + CompileOptions& operator=(const CompileOptions& rhs) = delete; +}; + +/** + * Subset of CompileOptions fields used while instantiating Stencils. + */ +class JS_PUBLIC_API InstantiateOptions { + public: + bool skipFilenameValidation = false; + bool hideScriptFromDebugger = false; + bool deferDebugMetadata = false; + + InstantiateOptions() = default; + + explicit InstantiateOptions(const ReadOnlyCompileOptions& options) + : skipFilenameValidation(options.skipFilenameValidation_), + hideScriptFromDebugger(options.hideScriptFromDebugger_), + deferDebugMetadata(options.deferDebugMetadata_) {} + + void copyTo(CompileOptions& options) const { + options.skipFilenameValidation_ = skipFilenameValidation; + options.hideScriptFromDebugger_ = hideScriptFromDebugger; + options.deferDebugMetadata_ = deferDebugMetadata; + } + + bool hideFromNewScriptInitial() const { + return deferDebugMetadata || hideScriptFromDebugger; + } + +#ifdef DEBUG + // Assert that all fields have default value. + // + // This can be used when instantiation is performed as separate step than + // compile-to-stencil, and CompileOptions isn't available there. + void assertDefault() const { + MOZ_ASSERT(skipFilenameValidation == false); + MOZ_ASSERT(hideScriptFromDebugger == false); + MOZ_ASSERT(deferDebugMetadata == false); + } +#endif +}; + +/** + * Subset of CompileOptions fields used while decoding Stencils. + */ +class JS_PUBLIC_API DecodeOptions { + public: + bool borrowBuffer = false; + bool usePinnedBytecode = false; + bool allocateInstantiationStorage = false; + bool forceAsync = false; + + const char* introducerFilename = nullptr; + + // See `TransitiveCompileOptions::introductionType` field for details. + const char* introductionType = nullptr; + + unsigned introductionLineno = 0; + uint32_t introductionOffset = 0; + + DecodeOptions() = default; + + explicit DecodeOptions(const ReadOnlyCompileOptions& options) + : borrowBuffer(options.borrowBuffer), + usePinnedBytecode(options.usePinnedBytecode), + allocateInstantiationStorage(options.allocateInstantiationStorage), + forceAsync(options.forceAsync), + introducerFilename(options.introducerFilename()), + introductionType(options.introductionType), + introductionLineno(options.introductionLineno), + introductionOffset(options.introductionOffset) {} + + void copyTo(CompileOptions& options) const { + options.borrowBuffer = borrowBuffer; + options.usePinnedBytecode = usePinnedBytecode; + options.allocateInstantiationStorage = allocateInstantiationStorage; + options.forceAsync = forceAsync; + options.introducerFilename_ = introducerFilename; + options.introductionType = introductionType; + options.introductionLineno = introductionLineno; + options.introductionOffset = introductionOffset; + } +}; + +} // namespace JS + +#endif /* js_CompileOptions_h */ diff --git a/js/public/Context.h b/js/public/Context.h new file mode 100644 index 0000000000..ad0b725b3a --- /dev/null +++ b/js/public/Context.h @@ -0,0 +1,111 @@ +/* -*- 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/. */ + +/* JavaScript API. */ + +#ifndef js_Context_h +#define js_Context_h + +#include "jspubtd.h" +// [SMDOC] Nested Thread Data Structures (JSContext, JSRuntime) +// +// Spidermonkey has two nested data structures for representing threads, +// JSContext and JSRuntime. All JS threads are represented by a context. +// Contexts can contain runtimes. A runtime however is not present for +// all threads. Threads also interact with the GC. See "Nested GC +// DataStructures" for more info. +// +// Context +// ------- +// JSContext represents a thread: there must be exactly one JSContext for each +// thread running JS/Wasm. +// +// Internally, helper threads can also have a JSContext. They do not always have +// an active context, but one may be requested by AutoSetHelperThreadContext, +// which activates a pre-allocated JSContext for the duration of its lifetime. +// +// Runtime +// ------- +// JSRuntime is very similar to JSContext: each runtime belongs to one context +// (thread), but helper threads don't have their own runtimes (they're shared by +// all runtimes in the process and use the runtime of the task they're +// executing). +// +// Note: +// Locking, contexts, and memory allocation. +// +// It is important that SpiderMonkey be initialized, and the first context +// be created, in a single-threaded fashion. Otherwise the behavior of the +// library is undefined. +// See: +// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference + +// Create a new context (and runtime) for this thread. +extern JS_PUBLIC_API JSContext* JS_NewContext( + uint32_t maxbytes, JSRuntime* parentRuntime = nullptr); + +// Destroy a context allocated with JS_NewContext. Must be called on the thread +// that called JS_NewContext. +extern JS_PUBLIC_API void JS_DestroyContext(JSContext* cx); + +JS_PUBLIC_API void* JS_GetContextPrivate(JSContext* cx); + +JS_PUBLIC_API void JS_SetContextPrivate(JSContext* cx, void* data); + +extern JS_PUBLIC_API JSRuntime* JS_GetParentRuntime(JSContext* cx); + +extern JS_PUBLIC_API JSRuntime* JS_GetRuntime(JSContext* cx); + +extern JS_PUBLIC_API void JS_SetFutexCanWait(JSContext* cx); + +namespace js { + +void AssertHeapIsIdle(); + +} /* namespace js */ + +namespace JS { + +/** + * Asserts (in debug and release builds) that `obj` belongs to the current + * thread's context. + */ +JS_PUBLIC_API void AssertObjectBelongsToCurrentThread(JSObject* obj); + +/** + * Install a process-wide callback to validate script filenames. The JS engine + * will invoke this callback for each JS script it parses or XDR decodes. + * + * If the callback returns |false|, an exception is thrown and parsing/decoding + * will be aborted. + * + * See also CompileOptions::setSkipFilenameValidation to opt-out of the callback + * for specific parse jobs. + */ +using FilenameValidationCallback = bool (*)(JSContext* cx, + const char* filename); +JS_PUBLIC_API void SetFilenameValidationCallback(FilenameValidationCallback cb); + +/** + * Install an context wide callback that implements the ECMA262 specification + * host hook `HostEnsureCanAddPrivateElement`. + * + * This hook, which should only be overriden for Web Browsers, examines the + * provided object to determine if the addition of a private field is allowed, + * throwing an exception and returning false if not. + * + * The default implementation of this hook, which will be used unless overriden, + * examines only proxy objects, and throws if the proxy handler returns true + * from the handler method `throwOnPrivateField()`. + */ +using EnsureCanAddPrivateElementOp = bool (*)(JSContext* cx, HandleValue val); + +JS_PUBLIC_API void SetHostEnsureCanAddPrivateElementHook( + JSContext* cx, EnsureCanAddPrivateElementOp op); + +} /* namespace JS */ + +#endif // js_Context_h diff --git a/js/public/ContextOptions.h b/js/public/ContextOptions.h new file mode 100644 index 0000000000..2077a5ceea --- /dev/null +++ b/js/public/ContextOptions.h @@ -0,0 +1,252 @@ +/* -*- 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/. */ + +/* JavaScript API. */ + +#ifndef js_ContextOptions_h +#define js_ContextOptions_h + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/WasmFeatures.h" + +struct JS_PUBLIC_API JSContext; + +namespace JS { + +class JS_PUBLIC_API ContextOptions { + public: + // clang-format off + ContextOptions() + : asmJS_(true), + wasm_(true), + wasmForTrustedPrinciples_(true), + wasmVerbose_(false), + wasmBaseline_(true), + wasmIon_(true), +#define WASM_DEFAULT_FEATURE(NAME, ...) wasm##NAME##_(true), +#define WASM_EXPERIMENTAL_FEATURE(NAME, ...) wasm##NAME##_(false), + JS_FOR_WASM_FEATURES(WASM_DEFAULT_FEATURE, WASM_DEFAULT_FEATURE, WASM_EXPERIMENTAL_FEATURE) +#undef WASM_DEFAULT_FEATURE +#undef WASM_EXPERIMENTAL_FEATURE + testWasmAwaitTier2_(false), + throwOnAsmJSValidationFailure_(false), + disableIon_(false), + disableEvalSecurityChecks_(false), + asyncStack_(true), + asyncStackCaptureDebuggeeOnly_(false), + sourcePragmas_(true), + throwOnDebuggeeWouldRun_(true), + dumpStackOnDebuggeeWouldRun_(false), + strictMode_(false), +#ifdef JS_ENABLE_SMOOSH + trackNotImplemented_(false), + trySmoosh_(false), +#endif + fuzzing_(false), + importAssertions_(false) { + } + // clang-format on + + bool asmJS() const { return asmJS_; } + ContextOptions& setAsmJS(bool flag) { + asmJS_ = flag; + return *this; + } + ContextOptions& toggleAsmJS() { + asmJS_ = !asmJS_; + return *this; + } + + bool wasm() const { return wasm_; } + ContextOptions& setWasm(bool flag) { + wasm_ = flag; + return *this; + } + ContextOptions& toggleWasm() { + wasm_ = !wasm_; + return *this; + } + + bool wasmForTrustedPrinciples() const { return wasmForTrustedPrinciples_; } + ContextOptions& setWasmForTrustedPrinciples(bool flag) { + wasmForTrustedPrinciples_ = flag; + return *this; + } + + bool wasmVerbose() const { return wasmVerbose_; } + ContextOptions& setWasmVerbose(bool flag) { + wasmVerbose_ = flag; + return *this; + } + + bool wasmBaseline() const { return wasmBaseline_; } + ContextOptions& setWasmBaseline(bool flag) { + wasmBaseline_ = flag; + return *this; + } + + bool wasmIon() const { return wasmIon_; } + ContextOptions& setWasmIon(bool flag) { + wasmIon_ = flag; + return *this; + } + + bool testWasmAwaitTier2() const { return testWasmAwaitTier2_; } + ContextOptions& setTestWasmAwaitTier2(bool flag) { + testWasmAwaitTier2_ = flag; + return *this; + } + +#define WASM_FEATURE(NAME, ...) \ + bool wasm##NAME() const { return wasm##NAME##_; } \ + ContextOptions& setWasm##NAME(bool flag) { \ + wasm##NAME##_ = flag; \ + return *this; \ + } + JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE, WASM_FEATURE) +#undef WASM_FEATURE + + bool throwOnAsmJSValidationFailure() const { + return throwOnAsmJSValidationFailure_; + } + ContextOptions& setThrowOnAsmJSValidationFailure(bool flag) { + throwOnAsmJSValidationFailure_ = flag; + return *this; + } + ContextOptions& toggleThrowOnAsmJSValidationFailure() { + throwOnAsmJSValidationFailure_ = !throwOnAsmJSValidationFailure_; + return *this; + } + + // Override to allow disabling Ion for this context irrespective of the + // process-wide Ion-enabled setting. This must be set right after creating + // the context. + bool disableIon() const { return disableIon_; } + ContextOptions& setDisableIon() { + disableIon_ = true; + return *this; + } + + bool importAssertions() const { return importAssertions_; } + ContextOptions& setImportAssertions(bool enabled) { + importAssertions_ = enabled; + return *this; + } + + // Override to allow disabling the eval restriction security checks for + // this context. + bool disableEvalSecurityChecks() const { return disableEvalSecurityChecks_; } + ContextOptions& setDisableEvalSecurityChecks() { + disableEvalSecurityChecks_ = true; + return *this; + } + + bool asyncStack() const { return asyncStack_; } + ContextOptions& setAsyncStack(bool flag) { + asyncStack_ = flag; + return *this; + } + + bool asyncStackCaptureDebuggeeOnly() const { + return asyncStackCaptureDebuggeeOnly_; + } + ContextOptions& setAsyncStackCaptureDebuggeeOnly(bool flag) { + asyncStackCaptureDebuggeeOnly_ = flag; + return *this; + } + + // Enable/disable support for parsing '//(#@) source(Mapping)?URL=' pragmas. + bool sourcePragmas() const { return sourcePragmas_; } + ContextOptions& setSourcePragmas(bool flag) { + sourcePragmas_ = flag; + return *this; + } + + bool throwOnDebuggeeWouldRun() const { return throwOnDebuggeeWouldRun_; } + ContextOptions& setThrowOnDebuggeeWouldRun(bool flag) { + throwOnDebuggeeWouldRun_ = flag; + return *this; + } + + bool dumpStackOnDebuggeeWouldRun() const { + return dumpStackOnDebuggeeWouldRun_; + } + ContextOptions& setDumpStackOnDebuggeeWouldRun(bool flag) { + dumpStackOnDebuggeeWouldRun_ = flag; + return *this; + } + + bool strictMode() const { return strictMode_; } + ContextOptions& setStrictMode(bool flag) { + strictMode_ = flag; + return *this; + } + ContextOptions& toggleStrictMode() { + strictMode_ = !strictMode_; + return *this; + } + +#ifdef JS_ENABLE_SMOOSH + // Track Number of Not Implemented Calls by writing to a file + bool trackNotImplemented() const { return trackNotImplemented_; } + ContextOptions& setTrackNotImplemented(bool flag) { + trackNotImplemented_ = flag; + return *this; + } + + // Try compiling SmooshMonkey frontend first, and fallback to C++ + // implementation when it fails. + bool trySmoosh() const { return trySmoosh_; } + ContextOptions& setTrySmoosh(bool flag) { + trySmoosh_ = flag; + return *this; + } + +#endif // JS_ENABLE_SMOOSH + + bool fuzzing() const { return fuzzing_; } + // Defined out-of-line because it depends on a compile-time option + ContextOptions& setFuzzing(bool flag); + + void disableOptionsForSafeMode() { + setAsmJS(false); + setWasmBaseline(false); + } + + private: + bool asmJS_ : 1; + bool wasm_ : 1; + bool wasmForTrustedPrinciples_ : 1; + bool wasmVerbose_ : 1; + bool wasmBaseline_ : 1; + bool wasmIon_ : 1; +#define WASM_FEATURE(NAME, ...) bool wasm##NAME##_ : 1; + JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE, WASM_FEATURE) +#undef WASM_FEATURE + bool testWasmAwaitTier2_ : 1; + bool throwOnAsmJSValidationFailure_ : 1; + bool disableIon_ : 1; + bool disableEvalSecurityChecks_ : 1; + bool asyncStack_ : 1; + bool asyncStackCaptureDebuggeeOnly_ : 1; + bool sourcePragmas_ : 1; + bool throwOnDebuggeeWouldRun_ : 1; + bool dumpStackOnDebuggeeWouldRun_ : 1; + bool strictMode_ : 1; +#ifdef JS_ENABLE_SMOOSH + bool trackNotImplemented_ : 1; + bool trySmoosh_ : 1; +#endif + bool fuzzing_ : 1; + bool importAssertions_ : 1; +}; + +JS_PUBLIC_API ContextOptions& ContextOptionsRef(JSContext* cx); + +} // namespace JS + +#endif // js_ContextOptions_h diff --git a/js/public/Conversions.h b/js/public/Conversions.h new file mode 100644 index 0000000000..63a9c5111e --- /dev/null +++ b/js/public/Conversions.h @@ -0,0 +1,601 @@ +/* -*- 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/. */ + +/* ECMAScript conversion operations. */ + +#ifndef js_Conversions_h +#define js_Conversions_h + +#include "mozilla/Casting.h" +#include "mozilla/Compiler.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/WrappingOperations.h" + +#include <cmath> +#include <stddef.h> // size_t +#include <stdint.h> // {u,}int{8,16,32,64}_t +#include <type_traits> + +#include "jspubtd.h" +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/RootingAPI.h" +#include "js/Value.h" + +namespace js { + +/* DO NOT CALL THIS. Use JS::ToBoolean. */ +extern JS_PUBLIC_API bool ToBooleanSlow(JS::HandleValue v); + +/* DO NOT CALL THIS. Use JS::ToNumber. */ +extern JS_PUBLIC_API bool ToNumberSlow(JSContext* cx, JS::HandleValue v, + double* dp); + +/* DO NOT CALL THIS. Use JS::ToInt8. */ +extern JS_PUBLIC_API bool ToInt8Slow(JSContext* cx, JS::HandleValue v, + int8_t* out); + +/* DO NOT CALL THIS. Use JS::ToUint8. */ +extern JS_PUBLIC_API bool ToUint8Slow(JSContext* cx, JS::HandleValue v, + uint8_t* out); + +/* DO NOT CALL THIS. Use JS::ToInt16. */ +extern JS_PUBLIC_API bool ToInt16Slow(JSContext* cx, JS::HandleValue v, + int16_t* out); + +/* DO NOT CALL THIS. Use JS::ToInt32. */ +extern JS_PUBLIC_API bool ToInt32Slow(JSContext* cx, JS::HandleValue v, + int32_t* out); + +/* DO NOT CALL THIS. Use JS::ToUint32. */ +extern JS_PUBLIC_API bool ToUint32Slow(JSContext* cx, JS::HandleValue v, + uint32_t* out); + +/* DO NOT CALL THIS. Use JS::ToUint16. */ +extern JS_PUBLIC_API bool ToUint16Slow(JSContext* cx, JS::HandleValue v, + uint16_t* out); + +/* DO NOT CALL THIS. Use JS::ToInt64. */ +extern JS_PUBLIC_API bool ToInt64Slow(JSContext* cx, JS::HandleValue v, + int64_t* out); + +/* DO NOT CALL THIS. Use JS::ToUint64. */ +extern JS_PUBLIC_API bool ToUint64Slow(JSContext* cx, JS::HandleValue v, + uint64_t* out); + +/* DO NOT CALL THIS. Use JS::ToString. */ +extern JS_PUBLIC_API JSString* ToStringSlow(JSContext* cx, JS::HandleValue v); + +/* DO NOT CALL THIS. Use JS::ToObject. */ +extern JS_PUBLIC_API JSObject* ToObjectSlow(JSContext* cx, JS::HandleValue v, + bool reportScanStack); + +} // namespace js + +namespace JS { + +namespace detail { + +#ifdef JS_DEBUG +/** + * Assert that we're not doing GC on cx, that we're in a request as + * needed, and that the compartments for cx and v are correct. + * Also check that GC would be safe at this point. + */ +extern JS_PUBLIC_API void AssertArgumentsAreSane(JSContext* cx, HandleValue v); +#else +inline void AssertArgumentsAreSane(JSContext* cx, HandleValue v) {} +#endif /* JS_DEBUG */ + +} // namespace detail + +/** + * ES6 draft 20141224, 7.1.1, second algorithm. + * + * Most users shouldn't call this -- use JS::ToBoolean, ToNumber, or ToString + * instead. This will typically only be called from custom convert hooks that + * wish to fall back to the ES6 default conversion behavior shared by most + * objects in JS, codified as OrdinaryToPrimitive. + */ +extern JS_PUBLIC_API bool OrdinaryToPrimitive(JSContext* cx, HandleObject obj, + JSType type, + MutableHandleValue vp); + +/* ES6 draft 20141224, 7.1.2. */ +MOZ_ALWAYS_INLINE bool ToBoolean(HandleValue v) { + if (v.isBoolean()) { + return v.toBoolean(); + } + if (v.isInt32()) { + return v.toInt32() != 0; + } + if (v.isNullOrUndefined()) { + return false; + } + if (v.isDouble()) { + double d = v.toDouble(); + return !std::isnan(d) && d != 0; + } + if (v.isSymbol()) { + return true; + } + + /* The slow path handles strings, BigInts and objects. */ + return js::ToBooleanSlow(v); +} + +/* ES6 draft 20141224, 7.1.3. */ +MOZ_ALWAYS_INLINE bool ToNumber(JSContext* cx, HandleValue v, double* out) { + detail::AssertArgumentsAreSane(cx, v); + + if (v.isNumber()) { + *out = v.toNumber(); + return true; + } + return js::ToNumberSlow(cx, v, out); +} + +// ES2020 draft rev 6b05bc56ba4e3c7a2b9922c4282d9eb844426d9b +// 7.1.5 ToInteger ( argument ) +// +// Specialized for double values. +inline double ToInteger(double d) { + if (d == 0) { + return 0; + } + + if (!std::isfinite(d)) { + if (std::isnan(d)) { + return 0; + } + return d; + } + + return std::trunc(d) + (+0.0); // Add zero to convert -0 to +0. +} + +/* ES6 draft 20141224, 7.1.5. */ +MOZ_ALWAYS_INLINE bool ToInt32(JSContext* cx, JS::HandleValue v, int32_t* out) { + detail::AssertArgumentsAreSane(cx, v); + + if (v.isInt32()) { + *out = v.toInt32(); + return true; + } + return js::ToInt32Slow(cx, v, out); +} + +/* ES6 draft 20141224, 7.1.6. */ +MOZ_ALWAYS_INLINE bool ToUint32(JSContext* cx, HandleValue v, uint32_t* out) { + detail::AssertArgumentsAreSane(cx, v); + + if (v.isInt32()) { + *out = uint32_t(v.toInt32()); + return true; + } + return js::ToUint32Slow(cx, v, out); +} + +/* ES6 draft 20141224, 7.1.7. */ +MOZ_ALWAYS_INLINE bool ToInt16(JSContext* cx, JS::HandleValue v, int16_t* out) { + detail::AssertArgumentsAreSane(cx, v); + + if (v.isInt32()) { + *out = int16_t(v.toInt32()); + return true; + } + return js::ToInt16Slow(cx, v, out); +} + +/* ES6 draft 20141224, 7.1.8. */ +MOZ_ALWAYS_INLINE bool ToUint16(JSContext* cx, HandleValue v, uint16_t* out) { + detail::AssertArgumentsAreSane(cx, v); + + if (v.isInt32()) { + *out = uint16_t(v.toInt32()); + return true; + } + return js::ToUint16Slow(cx, v, out); +} + +/* ES6 draft 20141224, 7.1.9 */ +MOZ_ALWAYS_INLINE bool ToInt8(JSContext* cx, JS::HandleValue v, int8_t* out) { + detail::AssertArgumentsAreSane(cx, v); + + if (v.isInt32()) { + *out = int8_t(v.toInt32()); + return true; + } + return js::ToInt8Slow(cx, v, out); +} + +/* ES6 ECMA-262, 7.1.10 */ +MOZ_ALWAYS_INLINE bool ToUint8(JSContext* cx, JS::HandleValue v, uint8_t* out) { + detail::AssertArgumentsAreSane(cx, v); + + if (v.isInt32()) { + *out = uint8_t(v.toInt32()); + return true; + } + return js::ToUint8Slow(cx, v, out); +} + +/* + * Non-standard, with behavior similar to that of ToInt32, except in its + * producing an int64_t. + */ +MOZ_ALWAYS_INLINE bool ToInt64(JSContext* cx, HandleValue v, int64_t* out) { + detail::AssertArgumentsAreSane(cx, v); + + if (v.isInt32()) { + *out = int64_t(v.toInt32()); + return true; + } + return js::ToInt64Slow(cx, v, out); +} + +/* + * Non-standard, with behavior similar to that of ToUint32, except in its + * producing a uint64_t. + */ +MOZ_ALWAYS_INLINE bool ToUint64(JSContext* cx, HandleValue v, uint64_t* out) { + detail::AssertArgumentsAreSane(cx, v); + + if (v.isInt32()) { + *out = uint64_t(v.toInt32()); + return true; + } + return js::ToUint64Slow(cx, v, out); +} + +/* ES6 draft 20141224, 7.1.12. */ +MOZ_ALWAYS_INLINE JSString* ToString(JSContext* cx, HandleValue v) { + detail::AssertArgumentsAreSane(cx, v); + + if (v.isString()) { + return v.toString(); + } + return js::ToStringSlow(cx, v); +} + +/* ES6 draft 20141224, 7.1.13. */ +inline JSObject* ToObject(JSContext* cx, HandleValue v) { + detail::AssertArgumentsAreSane(cx, v); + + if (v.isObject()) { + return &v.toObject(); + } + return js::ToObjectSlow(cx, v, false); +} + +#ifdef ENABLE_RECORD_TUPLE +inline JSObject* ToObjectOrGetObjectPayload(JSContext* cx, HandleValue v) { + detail::AssertArgumentsAreSane(cx, v); + + if (v.hasObjectPayload()) { + return &v.getObjectPayload(); + } + return js::ToObjectSlow(cx, v, false); +} +#endif + +/** + * Convert a double value to UnsignedInteger (an unsigned integral type) using + * ECMAScript-style semantics (that is, in like manner to how ECMAScript's + * ToInt32 converts to int32_t). + * + * If d is infinite or NaN, return 0. + * Otherwise compute d2 = sign(d) * floor(abs(d)), and return the + * UnsignedInteger value congruent to d2 % 2**(bit width of UnsignedInteger). + * + * The algorithm below is inspired by that found in + * <https://trac.webkit.org/changeset/67825/webkit/trunk/JavaScriptCore/runtime/JSValue.cpp> + * but has been generalized to all integer widths. + */ +template <typename UnsignedInteger> +inline UnsignedInteger ToUnsignedInteger(double d) { + static_assert(std::is_unsigned_v<UnsignedInteger>, + "UnsignedInteger must be an unsigned type"); + + uint64_t bits = mozilla::BitwiseCast<uint64_t>(d); + unsigned DoubleExponentShift = mozilla::FloatingPoint<double>::kExponentShift; + + // Extract the exponent component. (Be careful here! It's not technically + // the exponent in NaN, infinities, and subnormals.) + int_fast16_t exp = + int_fast16_t((bits & mozilla::FloatingPoint<double>::kExponentBits) >> + DoubleExponentShift) - + int_fast16_t(mozilla::FloatingPoint<double>::kExponentBias); + + // If the exponent's less than zero, abs(d) < 1, so the result is 0. (This + // also handles subnormals.) + if (exp < 0) { + return 0; + } + + uint_fast16_t exponent = mozilla::AssertedCast<uint_fast16_t>(exp); + + // If the exponent is greater than or equal to the bits of precision of a + // double plus UnsignedInteger's width, the number is either infinite, NaN, + // or too large to have lower-order bits in the congruent value. (Example: + // 2**84 is exactly representable as a double. The next exact double is + // 2**84 + 2**32. Thus if UnsignedInteger is uint32_t, an exponent >= 84 + // implies floor(abs(d)) == 0 mod 2**32.) Return 0 in all these cases. + constexpr size_t ResultWidth = CHAR_BIT * sizeof(UnsignedInteger); + if (exponent >= DoubleExponentShift + ResultWidth) { + return 0; + } + + // The significand contains the bits that will determine the final result. + // Shift those bits left or right, according to the exponent, to their + // locations in the unsigned binary representation of floor(abs(d)). + static_assert(sizeof(UnsignedInteger) <= sizeof(uint64_t), + "left-shifting below would lose upper bits"); + UnsignedInteger result = + (exponent > DoubleExponentShift) + ? UnsignedInteger(bits << (exponent - DoubleExponentShift)) + : UnsignedInteger(bits >> (DoubleExponentShift - exponent)); + + // Two further complications remain. First, |result| may contain bogus + // sign/exponent bits. Second, IEEE-754 numbers' significands (excluding + // subnormals, but we already handled those) have an implicit leading 1 + // which may affect the final result. + // + // It may appear that there's complexity here depending on how ResultWidth + // and DoubleExponentShift relate, but it turns out there's not. + // + // Assume ResultWidth < DoubleExponentShift: + // Only right-shifts leave bogus bits in |result|. For this to happen, + // we must right-shift by > |DoubleExponentShift - ResultWidth|, implying + // |exponent < ResultWidth|. + // The implicit leading bit only matters if it appears in the final + // result -- if |2**exponent mod 2**ResultWidth != 0|. This implies + // |exponent < ResultWidth|. + // Otherwise assume ResultWidth >= DoubleExponentShift: + // Any left-shift less than |ResultWidth - DoubleExponentShift| leaves + // bogus bits in |result|. This implies |exponent < ResultWidth|. Any + // right-shift less than |ResultWidth| does too, which implies + // |DoubleExponentShift - ResultWidth < exponent|. By assumption, then, + // |exponent| is negative, but we excluded that above. So bogus bits + // need only |exponent < ResultWidth|. + // The implicit leading bit matters identically to the other case, so + // again, |exponent < ResultWidth|. + if (exponent < ResultWidth) { + const auto implicitOne = + static_cast<UnsignedInteger>(UnsignedInteger{1} << exponent); + result &= implicitOne - 1; // remove bogus bits + result += implicitOne; // add the implicit bit + } + + // Compute the congruent value in the signed range. + return (bits & mozilla::FloatingPoint<double>::kSignBit) ? ~result + 1 + : result; +} + +template <typename SignedInteger> +inline SignedInteger ToSignedInteger(double d) { + static_assert(std::is_signed_v<SignedInteger>, + "SignedInteger must be a signed type"); + + using UnsignedInteger = std::make_unsigned_t<SignedInteger>; + UnsignedInteger u = ToUnsignedInteger<UnsignedInteger>(d); + + return mozilla::WrapToSigned(u); +} + +// clang crashes compiling this when targeting arm: +// https://llvm.org/bugs/show_bug.cgi?id=22974 +#if defined(__arm__) && MOZ_IS_GCC + +template <> +inline int32_t ToSignedInteger<int32_t>(double d) { + int32_t i; + uint32_t tmp0; + uint32_t tmp1; + uint32_t tmp2; + asm( + // We use a pure integer solution here. In the 'softfp' ABI, the argument + // will start in r0 and r1, and VFP can't do all of the necessary ECMA + // conversions by itself so some integer code will be required anyway. A + // hybrid solution is faster on A9, but this pure integer solution is + // notably faster for A8. + + // %0 is the result register, and may alias either of the %[QR]1 + // registers. + // %Q4 holds the lower part of the mantissa. + // %R4 holds the sign, exponent, and the upper part of the mantissa. + // %1, %2 and %3 are used as temporary values. + + // Extract the exponent. + " mov %1, %R4, LSR #20\n" + " bic %1, %1, #(1 << 11)\n" // Clear the sign. + + // Set the implicit top bit of the mantissa. This clobbers a bit of the + // exponent, but we have already extracted that. + " orr %R4, %R4, #(1 << 20)\n" + + // Special Cases + // We should return zero in the following special cases: + // - Exponent is 0x000 - 1023: +/-0 or subnormal. + // - Exponent is 0x7ff - 1023: +/-INFINITY or NaN + // - This case is implicitly handled by the standard code path + // anyway, as shifting the mantissa up by the exponent will + // result in '0'. + // + // The result is composed of the mantissa, prepended with '1' and + // bit-shifted left by the (decoded) exponent. Note that because the + // r1[20] is the bit with value '1', r1 is effectively already shifted + // (left) by 20 bits, and r0 is already shifted by 52 bits. + + // Adjust the exponent to remove the encoding offset. If the decoded + // exponent is negative, quickly bail out with '0' as such values round to + // zero anyway. This also catches +/-0 and subnormals. + " sub %1, %1, #0xff\n" + " subs %1, %1, #0x300\n" + " bmi 8f\n" + + // %1 = (decoded) exponent >= 0 + // %R4 = upper mantissa and sign + + // ---- Lower Mantissa ---- + " subs %3, %1, #52\n" // Calculate exp-52 + " bmi 1f\n" + + // Shift r0 left by exp-52. + // Ensure that we don't overflow ARM's 8-bit shift operand range. + // We need to handle anything up to an 11-bit value here as we know that + // 52 <= exp <= 1024 (0x400). Any shift beyond 31 bits results in zero + // anyway, so as long as we don't touch the bottom 5 bits, we can use + // a logical OR to push long shifts into the 32 <= (exp&0xff) <= 255 + // range. + " bic %2, %3, #0xff\n" + " orr %3, %3, %2, LSR #3\n" + // We can now perform a straight shift, avoiding the need for any + // conditional instructions or extra branches. + " mov %Q4, %Q4, LSL %3\n" + " b 2f\n" + "1:\n" // Shift r0 right by 52-exp. + // We know that 0 <= exp < 52, and we can shift up to 255 bits so + // 52-exp will always be a valid shift and we can sk%3 the range + // check for this case. + " rsb %3, %1, #52\n" + " mov %Q4, %Q4, LSR %3\n" + + // %1 = (decoded) exponent + // %R4 = upper mantissa and sign + // %Q4 = partially-converted integer + + "2:\n" + // ---- Upper Mantissa ---- + // This is much the same as the lower mantissa, with a few different + // boundary checks and some masking to hide the exponent & sign bit in the + // upper word. + // Note that the upper mantissa is pre-shifted by 20 in %R4, but we shift + // it left more to remove the sign and exponent so it is effectively + // pre-shifted by 31 bits. + " subs %3, %1, #31\n" // Calculate exp-31 + " mov %1, %R4, LSL #11\n" // Re-use %1 as a temporary register. + " bmi 3f\n" + + // Shift %R4 left by exp-31. + // Avoid overflowing the 8-bit shift range, as before. + " bic %2, %3, #0xff\n" + " orr %3, %3, %2, LSR #3\n" + // Perform the shift. + " mov %2, %1, LSL %3\n" + " b 4f\n" + "3:\n" // Shift r1 right by 31-exp. + // We know that 0 <= exp < 31, and we can shift up to 255 bits so + // 31-exp will always be a valid shift and we can skip the range + // check for this case. + " rsb %3, %3, #0\n" // Calculate 31-exp from -(exp-31) + " mov %2, %1, LSR %3\n" // Thumb-2 can't do "LSR %3" in "orr". + + // %Q4 = partially-converted integer (lower) + // %R4 = upper mantissa and sign + // %2 = partially-converted integer (upper) + + "4:\n" + // Combine the converted parts. + " orr %Q4, %Q4, %2\n" + // Negate the result if we have to, and move it to %0 in the process. To + // avoid conditionals, we can do this by inverting on %R4[31], then adding + // %R4[31]>>31. + " eor %Q4, %Q4, %R4, ASR #31\n" + " add %0, %Q4, %R4, LSR #31\n" + " b 9f\n" + "8:\n" + // +/-INFINITY, +/-0, subnormals, NaNs, and anything else out-of-range + // that will result in a conversion of '0'. + " mov %0, #0\n" + "9:\n" + : "=r"(i), "=&r"(tmp0), "=&r"(tmp1), "=&r"(tmp2), "=&r"(d) + : "4"(d) + : "cc"); + return i; +} + +#endif // defined (__arm__) && MOZ_IS_GCC + +namespace detail { + +template <typename IntegerType, + bool IsUnsigned = std::is_unsigned_v<IntegerType>> +struct ToSignedOrUnsignedInteger; + +template <typename IntegerType> +struct ToSignedOrUnsignedInteger<IntegerType, true> { + static IntegerType compute(double d) { + return ToUnsignedInteger<IntegerType>(d); + } +}; + +template <typename IntegerType> +struct ToSignedOrUnsignedInteger<IntegerType, false> { + static IntegerType compute(double d) { + return ToSignedInteger<IntegerType>(d); + } +}; + +} // namespace detail + +template <typename IntegerType> +inline IntegerType ToSignedOrUnsignedInteger(double d) { + return detail::ToSignedOrUnsignedInteger<IntegerType>::compute(d); +} + +/* WEBIDL 4.2.4 */ +inline int8_t ToInt8(double d) { return ToSignedInteger<int8_t>(d); } + +/* ECMA-262 7.1.10 ToUInt8() specialized for doubles. */ +inline int8_t ToUint8(double d) { return ToUnsignedInteger<uint8_t>(d); } + +/* WEBIDL 4.2.6 */ +inline int16_t ToInt16(double d) { return ToSignedInteger<int16_t>(d); } + +inline uint16_t ToUint16(double d) { return ToUnsignedInteger<uint16_t>(d); } + +/* ES5 9.5 ToInt32 (specialized for doubles). */ +inline int32_t ToInt32(double d) { return ToSignedInteger<int32_t>(d); } + +/* ES5 9.6 (specialized for doubles). */ +inline uint32_t ToUint32(double d) { return ToUnsignedInteger<uint32_t>(d); } + +/* WEBIDL 4.2.10 */ +inline int64_t ToInt64(double d) { return ToSignedInteger<int64_t>(d); } + +/* WEBIDL 4.2.11 */ +inline uint64_t ToUint64(double d) { return ToUnsignedInteger<uint64_t>(d); } + +/** + * An amount of space large enough to store the null-terminated result of + * |ToString| on any Number. + * + * The <https://tc39.es/ecma262/#sec-tostring-applied-to-the-number-type> + * |NumberToString| algorithm is specified in terms of results, not an + * algorithm. It is extremely unclear from the algorithm's definition what its + * longest output can be. |-(2**-19 - 2**-72)| requires 25 + 1 characters and + * is believed to be at least *very close* to the upper bound, so we round that + * *very generously* upward to a 64-bit pointer-size boundary (to be extra + * cautious) and assume that's adequate. + * + * If you can supply better reasoning for a tighter bound, file a bug to improve + * this! + */ +static constexpr size_t MaximumNumberToStringLength = 31 + 1; + +/** + * Store in |out| the null-terminated, base-10 result of |ToString| applied to + * |d| per <https://tc39.es/ecma262/#sec-tostring-applied-to-the-number-type>. + * (This will produce "NaN", "-Infinity", or "Infinity" for non-finite |d|.) + */ +extern JS_PUBLIC_API void NumberToString( + double d, char (&out)[MaximumNumberToStringLength]); + +} // namespace JS + +#endif /* js_Conversions_h */ diff --git a/js/public/Date.h b/js/public/Date.h new file mode 100644 index 0000000000..45c6a88602 --- /dev/null +++ b/js/public/Date.h @@ -0,0 +1,208 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* JavaScript date/time computation and creation functions. */ + +#ifndef js_Date_h +#define js_Date_h + +/* + * Dates in JavaScript are defined by IEEE-754 double precision numbers from + * the set: + * + * { t ∈ ℕ : -8.64e15 ≤ t ≤ +8.64e15 } ∪ { NaN } + * + * The single NaN value represents any invalid-date value. All other values + * represent idealized durations in milliseconds since the UTC epoch. (Leap + * seconds are ignored; leap days are not.) +0 is the only zero in this set. + * The limit represented by 8.64e15 milliseconds is 100 million days either + * side of 00:00 January 1, 1970 UTC. + * + * Dates in the above set are represented by the |ClippedTime| class. The + * double type is a superset of the above set, so it *may* (but need not) + * represent a date. Use ECMAScript's |TimeClip| method to produce a date from + * a double. + * + * Date *objects* are simply wrappers around |TimeClip|'d numbers, with a bunch + * of accessor methods to the various aspects of the represented date. + */ + +#include "mozilla/FloatingPoint.h" // mozilla::{IsFinite,}, mozilla::UnspecifiedNaN +#include "mozilla/MathAlgorithms.h" // mozilla::Abs + +#include "js/Conversions.h" // JS::ToInteger +#include "js/TypeDecls.h" +#include "js/Value.h" // JS::CanonicalizeNaN, JS::DoubleValue, JS::Value + +namespace JS { + +/** + * Re-query the system to determine the current time zone adjustment from UTC, + * including any component due to DST. If the time zone has changed, this will + * cause all Date object non-UTC methods and formatting functions to produce + * appropriately adjusted results. + * + * Left to its own devices, SpiderMonkey itself may occasionally try to detect + * system time changes. However, no particular frequency of checking is + * guaranteed. Embedders unable to accept occasional inaccuracies should call + * this method in response to system time changes, or immediately before + * operations requiring instantaneous correctness, to guarantee correct + * behavior. + */ +extern JS_PUBLIC_API void ResetTimeZone(); + +class ClippedTime; +inline ClippedTime TimeClip(double time); + +/* + * |ClippedTime| represents the limited subset of dates/times described above. + * + * An invalid date/time may be created through the |ClippedTime::invalid| + * method. Otherwise, a |ClippedTime| may be created using the |TimeClip| + * method. + * + * In typical use, the user might wish to manipulate a timestamp. The user + * performs a series of operations on it, but the final value might not be a + * date as defined above -- it could have overflowed, acquired a fractional + * component, &c. So as a *final* step, the user passes that value through + * |TimeClip| to produce a number restricted to JavaScript's date range. + * + * APIs that accept a JavaScript date value thus accept a |ClippedTime|, not a + * double. This ensures that date/time APIs will only ever receive acceptable + * JavaScript dates. This also forces users to perform any desired clipping, + * as only the user knows what behavior is desired when clipping occurs. + */ +class ClippedTime { + double t = mozilla::UnspecifiedNaN<double>(); + + explicit ClippedTime(double time) : t(time) {} + friend ClippedTime TimeClip(double time); + + public: + // Create an invalid date. + ClippedTime() = default; + + // Create an invalid date/time, more explicitly; prefer this to the default + // constructor. + static ClippedTime invalid() { return ClippedTime(); } + + double toDouble() const { return t; } + + bool isValid() const { return !std::isnan(t); } +}; + +// ES6 20.3.1.15. +// +// Clip a double to JavaScript's date range (or to an invalid date) using the +// ECMAScript TimeClip algorithm. +inline ClippedTime TimeClip(double time) { + // Steps 1-2. + const double MaxTimeMagnitude = 8.64e15; + if (!std::isfinite(time) || mozilla::Abs(time) > MaxTimeMagnitude) { + return ClippedTime(mozilla::UnspecifiedNaN<double>()); + } + + // Step 3. + return ClippedTime(ToInteger(time)); +} + +// Produce a double Value from the given time. Because times may be NaN, +// prefer using this to manual canonicalization. +inline Value TimeValue(ClippedTime time) { + return CanonicalizedDoubleValue(time.toDouble()); +} + +// Create a new Date object whose [[DateValue]] internal slot contains the +// clipped |time|. (Users who must represent times outside that range must use +// another representation.) +extern JS_PUBLIC_API JSObject* NewDateObject(JSContext* cx, ClippedTime time); + +/** + * Create a new Date object for a year/month/day-of-month/hour/minute/second. + * + * The created date is initialized with the time value + * + * TimeClip(UTC(MakeDate(MakeDay(year, mon, mday), + * MakeTime(hour, min, sec, 0.0)))) + * + * where each function/operation is as specified in ECMAScript. + */ +extern JS_PUBLIC_API JSObject* NewDateObject(JSContext* cx, int year, int mon, + int mday, int hour, int min, + int sec); + +/** + * On success, returns true, setting |*isDate| to true if |obj| is a Date + * object or a wrapper around one, or to false if not. Returns false on + * failure. + * + * This method returns true with |*isDate == false| when passed an ES6 proxy + * whose target is a Date, or when passed a revoked proxy. + */ +extern JS_PUBLIC_API bool ObjectIsDate(JSContext* cx, Handle<JSObject*> obj, + bool* isDate); + +// Year is a year, month is 0-11, day is 1-based. The return value is a number +// of milliseconds since the epoch. +// +// Consistent with the MakeDate algorithm defined in ECMAScript, this value is +// *not* clipped! Use JS::TimeClip if you need a clipped date. +JS_PUBLIC_API double MakeDate(double year, unsigned month, unsigned day); + +// Year is a year, month is 0-11, day is 1-based, and time is in milliseconds. +// The return value is a number of milliseconds since the epoch. +// +// Consistent with the MakeDate algorithm defined in ECMAScript, this value is +// *not* clipped! Use JS::TimeClip if you need a clipped date. +JS_PUBLIC_API double MakeDate(double year, unsigned month, unsigned day, + double time); + +// Takes an integer number of milliseconds since the epoch and returns the +// year. Can return NaN, and will do so if NaN is passed in. +JS_PUBLIC_API double YearFromTime(double time); + +// Takes an integer number of milliseconds since the epoch and returns the +// month (0-11). Can return NaN, and will do so if NaN is passed in. +JS_PUBLIC_API double MonthFromTime(double time); + +// Takes an integer number of milliseconds since the epoch and returns the +// day (1-based). Can return NaN, and will do so if NaN is passed in. +JS_PUBLIC_API double DayFromTime(double time); + +// Takes an integer year and returns the number of days from epoch to the given +// year. +// NOTE: The calculation performed by this function is literally that given in +// the ECMAScript specification. Nonfinite years, years containing fractional +// components, and years outside ECMAScript's date range are not handled with +// any particular intelligence. Garbage in, garbage out. +JS_PUBLIC_API double DayFromYear(double year); + +// Takes an integer number of milliseconds since the epoch and an integer year, +// returns the number of days in that year. If |time| is nonfinite, returns NaN. +// Otherwise |time| *must* correspond to a time within the valid year |year|. +// This should usually be ensured by computing |year| as +// |JS::DayFromYear(time)|. +JS_PUBLIC_API double DayWithinYear(double time, double year); + +// The callback will be a wrapper function that accepts a double (the time +// to clamp and jitter) as well as a bool indicating if we should be resisting +// fingerprinting. Inside the JS Engine, other parameters that may be +// needed are all constant, so they are handled inside the wrapper function +using ReduceMicrosecondTimePrecisionCallback = double (*)(double, bool, + JSContext*); + +// Set a callback into the toolkit/components/resistfingerprinting function that +// will centralize time resolution and jitter into one place. +JS_PUBLIC_API void SetReduceMicrosecondTimePrecisionCallback( + ReduceMicrosecondTimePrecisionCallback callback); + +// Sets the time resolution for fingerprinting protection, and whether jitter +// should occur. If resolution is set to zero, then no rounding or jitter will +// occur. This is used if the callback above is not specified. +JS_PUBLIC_API void SetTimeResolutionUsec(uint32_t resolution, bool jitter); + +} // namespace JS + +#endif /* js_Date_h */ diff --git a/js/public/Debug.h b/js/public/Debug.h new file mode 100644 index 0000000000..6d7fd8a4be --- /dev/null +++ b/js/public/Debug.h @@ -0,0 +1,354 @@ +/* -*- 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/. */ + +// Interfaces by which the embedding can interact with the Debugger API. + +#ifndef js_Debug_h +#define js_Debug_h + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" + +#include <utility> + +#include "jstypes.h" + +#include "js/GCAPI.h" +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" + +namespace js { +class Debugger; +} // namespace js + +/* Defined in vm/Debugger.cpp. */ +extern JS_PUBLIC_API bool JS_DefineDebuggerObject(JSContext* cx, + JS::HandleObject obj); + +namespace JS { +namespace dbg { + +// [SMDOC] Debugger builder API +// +// Helping embedding code build objects for Debugger +// ------------------------------------------------- +// +// Some Debugger API features lean on the embedding application to construct +// their result values. For example, Debugger.Frame.prototype.scriptEntryReason +// calls hooks provided by the embedding to construct values explaining why it +// invoked JavaScript; if F is a frame called from a mouse click event handler, +// F.scriptEntryReason would return an object of the form: +// +// { eventType: "mousedown", event: <object> } +// +// where <object> is a Debugger.Object whose referent is the event being +// dispatched. +// +// However, Debugger implements a trust boundary. Debuggee code may be +// considered untrusted; debugger code needs to be protected from debuggee +// getters, setters, proxies, Object.watch watchpoints, and any other feature +// that might accidentally cause debugger code to set the debuggee running. The +// Debugger API tries to make it easy to write safe debugger code by only +// offering access to debuggee objects via Debugger.Object instances, which +// ensure that only those operations whose explicit purpose is to invoke +// debuggee code do so. But this protective membrane is only helpful if we +// interpose Debugger.Object instances in all the necessary spots. +// +// SpiderMonkey's compartment system also implements a trust boundary. The +// debuggee and debugger are always in different compartments. Inter-compartment +// work requires carefully tracking which compartment each JSObject or JS::Value +// belongs to, and ensuring that is is correctly wrapped for each operation. +// +// It seems precarious to expect the embedding's hooks to implement these trust +// boundaries. Instead, the JS::dbg::Builder API segregates the code which +// constructs trusted objects from that which deals with untrusted objects. +// Trusted objects have an entirely different C++ type, so code that improperly +// mixes trusted and untrusted objects is caught at compile time. +// +// In the structure shown above, there are two trusted objects, and one +// untrusted object: +// +// - The overall object, with the 'eventType' and 'event' properties, is a +// trusted object. We're going to return it to D.F.p.scriptEntryReason's +// caller, which will handle it directly. +// +// - The Debugger.Object instance appearing as the value of the 'event' property +// is a trusted object. It belongs to the same Debugger instance as the +// Debugger.Frame instance whose scriptEntryReason accessor was called, and +// presents a safe reflection-oriented API for inspecting its referent, which +// is: +// +// - The actual event object, an untrusted object, and the referent of the +// Debugger.Object above. (Content can do things like replacing accessors on +// Event.prototype.) +// +// Using JS::dbg::Builder, all objects and values the embedding deals with +// directly are considered untrusted, and are assumed to be debuggee values. The +// only way to construct trusted objects is to use Builder's own methods, which +// return a separate Object type. The only way to set a property on a trusted +// object is through that Object type. The actual trusted object is never +// exposed to the embedding. +// +// So, for example, the embedding might use code like the following to construct +// the object shown above, given a Builder passed to it by Debugger: +// +// bool +// MyScriptEntryReason::explain(JSContext* cx, +// Builder& builder, +// Builder::Object& result) +// { +// JSObject* eventObject = ... obtain debuggee event object somehow ...; +// if (!eventObject) { +// return false; +// } +// result = builder.newObject(cx); +// return result && +// result.defineProperty(cx, "eventType", +// SafelyFetchType(eventObject)) && +// result.defineProperty(cx, "event", eventObject); +// } +// +// +// Object::defineProperty also accepts an Object as the value to store on the +// property. By its type, we know that the value is trusted, so we set it +// directly as the property's value, without interposing a Debugger.Object +// wrapper. This allows the embedding to builted nested structures of trusted +// objects. +// +// The Builder and Builder::Object methods take care of doing whatever +// compartment switching and wrapping are necessary to construct the trusted +// values in the Debugger's compartment. +// +// The Object type is self-rooting. Construction, assignment, and destruction +// all properly root the referent object. + +class BuilderOrigin; + +class Builder { + // The Debugger instance whose client we are building a value for. We build + // objects in this object's compartment. + PersistentRootedObject debuggerObject; + + // debuggerObject's Debugger structure, for convenience. + js::Debugger* debugger; + + // Check that |thing| is in the same compartment as our debuggerObject. Used + // for assertions when constructing BuiltThings. We can overload this as we + // add more instantiations of BuiltThing. +#ifdef DEBUG + void assertBuilt(JSObject* obj); +#else + void assertBuilt(JSObject* obj) {} +#endif + + protected: + // A reference to a trusted object or value. At the moment, we only use it + // with JSObject*. + template <typename T> + class BuiltThing { + friend class BuilderOrigin; + + protected: + // The Builder to which this trusted thing belongs. + Builder& owner; + + // A rooted reference to our value. + PersistentRooted<T> value; + + BuiltThing(JSContext* cx, Builder& owner_, + T value_ = SafelyInitialized<T>::create()) + : owner(owner_), value(cx, value_) { + owner.assertBuilt(value_); + } + + // Forward some things from our owner, for convenience. + js::Debugger* debugger() const { return owner.debugger; } + JSObject* debuggerObject() const { return owner.debuggerObject; } + + public: + BuiltThing(const BuiltThing& rhs) : owner(rhs.owner), value(rhs.value) {} + BuiltThing& operator=(const BuiltThing& rhs) { + MOZ_ASSERT(&owner == &rhs.owner); + owner.assertBuilt(rhs.value); + value = rhs.value; + return *this; + } + + explicit operator bool() const { + // If we ever instantiate BuiltThing<Value>, this might not suffice. + return value; + } + + private: + BuiltThing() = delete; + }; + + public: + // A reference to a trusted object, possibly null. Instances of Object are + // always properly rooted. They can be copied and assigned, as if they were + // pointers. + class Object : private BuiltThing<JSObject*> { + friend class Builder; // for construction + friend class BuilderOrigin; // for unwrapping + + typedef BuiltThing<JSObject*> Base; + + // This is private, because only Builders can create Objects that + // actually point to something (hence the 'friend' declaration). + Object(JSContext* cx, Builder& owner_, HandleObject obj) + : Base(cx, owner_, obj.get()) {} + + bool definePropertyToTrusted(JSContext* cx, const char* name, + JS::MutableHandleValue value); + + public: + Object(JSContext* cx, Builder& owner_) : Base(cx, owner_, nullptr) {} + Object(const Object& rhs) = default; + + // Our automatically-generated assignment operator can see our base + // class's assignment operator, so we don't need to write one out here. + + // Set the property named |name| on this object to |value|. + // + // If |value| is a string or primitive, re-wrap it for the debugger's + // compartment. + // + // If |value| is an object, assume it is a debuggee object and make a + // Debugger.Object instance referring to it. Set that as the propery's + // value. + // + // If |value| is another trusted object, store it directly as the + // property's value. + // + // On error, report the problem on cx and return false. + bool defineProperty(JSContext* cx, const char* name, JS::HandleValue value); + bool defineProperty(JSContext* cx, const char* name, + JS::HandleObject value); + bool defineProperty(JSContext* cx, const char* name, Object& value); + + using Base::operator bool; + }; + + // Build an empty object for direct use by debugger code, owned by this + // Builder. If an error occurs, report it on cx and return a false Object. + Object newObject(JSContext* cx); + + protected: + Builder(JSContext* cx, js::Debugger* debugger); +}; + +// Debugger itself instantiates this subclass of Builder, which can unwrap +// BuiltThings that belong to it. +class BuilderOrigin : public Builder { + template <typename T> + T unwrapAny(const BuiltThing<T>& thing) { + MOZ_ASSERT(&thing.owner == this); + return thing.value.get(); + } + + public: + BuilderOrigin(JSContext* cx, js::Debugger* debugger_) + : Builder(cx, debugger_) {} + + JSObject* unwrap(Object& object) { return unwrapAny(object); } +}; + +// Finding the size of blocks allocated with malloc +// ------------------------------------------------ +// +// Debugger.Memory wants to be able to report how many bytes items in memory are +// consuming. To do this, it needs a function that accepts a pointer to a block, +// and returns the number of bytes allocated to that block. SpiderMonkey itself +// doesn't know which function is appropriate to use, but the embedding does. + +// Tell Debuggers in |cx| to use |mallocSizeOf| to find the size of +// malloc'd blocks. +JS_PUBLIC_API void SetDebuggerMallocSizeOf(JSContext* cx, + mozilla::MallocSizeOf mallocSizeOf); + +// Get the MallocSizeOf function that the given context is using to find the +// size of malloc'd blocks. +JS_PUBLIC_API mozilla::MallocSizeOf GetDebuggerMallocSizeOf(JSContext* cx); + +// Debugger and Garbage Collection Events +// -------------------------------------- +// +// The Debugger wants to report about its debuggees' GC cycles, however entering +// JS after a GC is troublesome since SpiderMonkey will often do something like +// force a GC and then rely on the nursery being empty. If we call into some +// Debugger's hook after the GC, then JS runs and the nursery won't be +// empty. Instead, we rely on embedders to call back into SpiderMonkey after a +// GC and notify Debuggers to call their onGarbageCollection hook. + +// Determine whether it's necessary to call FireOnGarbageCollectionHook() after +// a GC. This is only required if there are debuggers with an +// onGarbageCollection hook observing a global in the set of collected zones. +JS_PUBLIC_API bool FireOnGarbageCollectionHookRequired(JSContext* cx); + +// For each Debugger that observed a debuggee involved in the given GC event, +// call its `onGarbageCollection` hook. +JS_PUBLIC_API bool FireOnGarbageCollectionHook( + JSContext* cx, GarbageCollectionEvent::Ptr&& data); + +// Return true if the given value is a Debugger object, false otherwise. +JS_PUBLIC_API bool IsDebugger(JSObject& obj); + +// Append each of the debuggee global objects observed by the Debugger object +// |dbgObj| to |vector|. Returns true on success, false on failure. +JS_PUBLIC_API bool GetDebuggeeGlobals(JSContext* cx, JSObject& dbgObj, + MutableHandleObjectVector vector); + +// Hooks for reporting where JavaScript execution began. +// +// Our performance tools would like to be able to label blocks of JavaScript +// execution with the function name and source location where execution began: +// the event handler, the callback, etc. +// +// Construct an instance of this class on the stack, providing a JSContext +// belonging to the runtime in which execution will occur. Each time we enter +// JavaScript --- specifically, each time we push a JavaScript stack frame that +// has no older JS frames younger than this AutoEntryMonitor --- we will +// call the appropriate |Entry| member function to indicate where we've begun +// execution. + +class MOZ_STACK_CLASS JS_PUBLIC_API AutoEntryMonitor { + JSContext* cx_; + AutoEntryMonitor* savedMonitor_; + + public: + explicit AutoEntryMonitor(JSContext* cx); + ~AutoEntryMonitor(); + + // SpiderMonkey reports the JavaScript entry points occuring within this + // AutoEntryMonitor's scope to the following member functions, which the + // embedding is expected to override. + // + // It is important to note that |asyncCause| is owned by the caller and its + // lifetime must outlive the lifetime of the AutoEntryMonitor object. It is + // strongly encouraged that |asyncCause| be a string constant or similar + // statically allocated string. + + // We have begun executing |function|. Note that |function| may not be the + // actual closure we are running, but only the canonical function object to + // which the script refers. + virtual void Entry(JSContext* cx, JSFunction* function, + HandleValue asyncStack, const char* asyncCause) = 0; + + // Execution has begun at the entry point of |script|, which is not a + // function body. (This is probably being executed by 'eval' or some + // JSAPI equivalent.) + virtual void Entry(JSContext* cx, JSScript* script, HandleValue asyncStack, + const char* asyncCause) = 0; + + // Execution of the function or script has ended. + virtual void Exit(JSContext* cx) {} +}; + +} // namespace dbg +} // namespace JS + +#endif /* js_Debug_h */ diff --git a/js/public/Equality.h b/js/public/Equality.h new file mode 100644 index 0000000000..7460aebe16 --- /dev/null +++ b/js/public/Equality.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* Equality operations. */ + +#ifndef js_Equality_h +#define js_Equality_h + +#include "mozilla/FloatingPoint.h" + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/TypeDecls.h" + +struct JS_PUBLIC_API JSContext; + +namespace JS { + +/** + * Store |v1 === v2| to |*equal| -- strict equality, which performs no + * conversions on |v1| or |v2| before comparing. + * + * This operation can fail only if an internal error occurs (e.g. OOM while + * linearizing a string value). + */ +extern JS_PUBLIC_API bool StrictlyEqual(JSContext* cx, JS::Handle<JS::Value> v1, + JS::Handle<JS::Value> v2, bool* equal); + +/** + * Store |v1 == v2| to |*equal| -- loose equality, which may perform + * user-modifiable conversions on |v1| or |v2|. + * + * This operation can fail if a user-modifiable conversion fails *or* if an + * internal error occurs. (e.g. OOM while linearizing a string value). + */ +extern JS_PUBLIC_API bool LooselyEqual(JSContext* cx, JS::Handle<JS::Value> v1, + JS::Handle<JS::Value> v2, bool* equal); + +/** + * Stores |SameValue(v1, v2)| to |*equal| -- using the SameValue operation + * defined in ECMAScript, initially exposed to script as |Object.is|. SameValue + * behaves identically to strict equality, except that it equates two NaN values + * and does not equate differently-signed zeroes. It performs no conversions on + * |v1| or |v2| before comparing. + * + * This operation can fail only if an internal error occurs (e.g. OOM while + * linearizing a string value). + */ +extern JS_PUBLIC_API bool SameValue(JSContext* cx, JS::Handle<JS::Value> v1, + JS::Handle<JS::Value> v2, bool* same); + +/** + * Implements |SameValueZero(v1, v2)| for Number values |v1| and |v2|. + * SameValueZero equates NaNs, equal nonzero values, and zeroes without respect + * to their signs. + */ +static inline bool SameValueZero(double v1, double v2) { + return mozilla::EqualOrBothNaN(v1, v2); +} + +} // namespace JS + +#endif /* js_Equality_h */ diff --git a/js/public/ErrorInterceptor.h b/js/public/ErrorInterceptor.h new file mode 100644 index 0000000000..7481c48fcb --- /dev/null +++ b/js/public/ErrorInterceptor.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_ErrorInterceptor_h +#define js_ErrorInterceptor_h + +#include "jstypes.h" + +#include "js/TypeDecls.h" + +/** + * Callback used to intercept JavaScript errors. + */ +struct JSErrorInterceptor { + /** + * This method is called whenever an error has been raised from JS code. + * + * This method MUST be infallible. + */ + virtual void interceptError(JSContext* cx, JS::HandleValue error) = 0; +}; + +// Set a callback that will be called whenever an error +// is thrown in this runtime. This is designed as a mechanism +// for logging errors. Note that the VM makes no attempt to sanitize +// the contents of the error (so it may contain private data) +// or to sort out among errors (so it may not be the error you +// are interested in or for the component in which you are +// interested). +// +// If the callback sets a new error, this new error +// will replace the original error. +// +// May be `nullptr`. +// This is a no-op if built without NIGHTLY_BUILD. +extern JS_PUBLIC_API void JS_SetErrorInterceptorCallback( + JSRuntime*, JSErrorInterceptor* callback); + +// This returns nullptr if built without NIGHTLY_BUILD. +extern JS_PUBLIC_API JSErrorInterceptor* JS_GetErrorInterceptorCallback( + JSRuntime*); + +#endif // js_ErrorInterceptor_h diff --git a/js/public/ErrorReport.h b/js/public/ErrorReport.h new file mode 100644 index 0000000000..de443e205b --- /dev/null +++ b/js/public/ErrorReport.h @@ -0,0 +1,541 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * Error-reporting APIs. + * + * Despite the types and structures defined here existing in js/public, + * significant parts of their heritage date back to distant SpiderMonkey past, + * and they are not all universally well-thought-out as ideal, + * intended-to-be-permanent API. We may eventually replace this with something + * more consistent with ECMAScript the language and less consistent with + * '90s-era JSAPI inventions, but it's doubtful this will happen any time soon. + */ + +#ifndef js_ErrorReport_h +#define js_ErrorReport_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include <cstdarg> +#include <iterator> // std::input_iterator_tag, std::iterator +#include <stdarg.h> +#include <stddef.h> // size_t +#include <stdint.h> // int16_t, uint16_t +#include <string.h> // strlen + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/AllocPolicy.h" +#include "js/CharacterEncoding.h" // JS::ConstUTF8CharsZ +#include "js/RootingAPI.h" // JS::HandleObject, JS::RootedObject +#include "js/UniquePtr.h" // js::UniquePtr +#include "js/Value.h" // JS::Value +#include "js/Vector.h" // js::Vector + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSString; + +namespace JS { +class ExceptionStack; +} +namespace js { +class SystemAllocPolicy; + +enum ErrorArgumentsType { + ArgumentsAreUnicode, + ArgumentsAreASCII, + ArgumentsAreLatin1, + ArgumentsAreUTF8 +}; +} // namespace js + +/** + * Possible exception types. These types are part of a JSErrorFormatString + * structure. They define which error to throw in case of a runtime error. + * + * JSEXN_WARN is used for warnings, that are not strictly errors but are handled + * using the generalized error reporting mechanism. (One side effect of this + * type is to not prepend 'Error:' to warning messages.) This value can go away + * if we ever decide to use an entirely separate mechanism for warnings. + */ +enum JSExnType { + JSEXN_ERR, + JSEXN_FIRST = JSEXN_ERR, + JSEXN_INTERNALERR, + JSEXN_AGGREGATEERR, + JSEXN_EVALERR, + JSEXN_RANGEERR, + JSEXN_REFERENCEERR, + JSEXN_SYNTAXERR, + JSEXN_TYPEERR, + JSEXN_URIERR, + JSEXN_DEBUGGEEWOULDRUN, + JSEXN_WASMCOMPILEERROR, + JSEXN_WASMLINKERROR, + JSEXN_WASMRUNTIMEERROR, + JSEXN_ERROR_LIMIT, + JSEXN_WARN = JSEXN_ERROR_LIMIT, + JSEXN_NOTE, + JSEXN_LIMIT +}; + +struct JSErrorFormatString { + /** The error message name in ASCII. */ + const char* name; + + /** The error format string in ASCII. */ + const char* format; + + /** The number of arguments to expand in the formatted error message. */ + uint16_t argCount; + + /** One of the JSExnType constants above. */ + int16_t exnType; +}; + +using JSErrorCallback = + const JSErrorFormatString* (*)(void* userRef, const unsigned errorNumber); + +/** + * Base class that implements parts shared by JSErrorReport and + * JSErrorNotes::Note. + */ +class JSErrorBase { + private: + // The (default) error message. + // If ownsMessage_ is true, the it is freed in destructor. + JS::ConstUTF8CharsZ message_; + + public: + // The UTF-8 encoded source file name, URL, etc., or null. + const char* filename; + + // Unique identifier for the script source. + unsigned sourceId; + + // Source line number. + unsigned lineno; + + // Zero-based column index in line. + unsigned column; + + // the error number, e.g. see js/public/friend/ErrorNumbers.msg. + unsigned errorNumber; + + // Points to JSErrorFormatString::name. + // This string must never be freed. + const char* errorMessageName; + + private: + bool ownsMessage_ : 1; + + public: + JSErrorBase() + : filename(nullptr), + sourceId(0), + lineno(0), + column(0), + errorNumber(0), + errorMessageName(nullptr), + ownsMessage_(false) {} + JSErrorBase(JSErrorBase&& other) noexcept + : message_(other.message_), + filename(other.filename), + sourceId(other.sourceId), + lineno(other.lineno), + column(other.column), + errorNumber(other.errorNumber), + errorMessageName(other.errorMessageName), + ownsMessage_(other.ownsMessage_) { + if (ownsMessage_) { + other.ownsMessage_ = false; + } + } + + ~JSErrorBase() { freeMessage(); } + + public: + const JS::ConstUTF8CharsZ message() const { return message_; } + + void initOwnedMessage(const char* messageArg) { + initBorrowedMessage(messageArg); + ownsMessage_ = true; + } + void initBorrowedMessage(const char* messageArg) { + MOZ_ASSERT(!message_); + message_ = JS::ConstUTF8CharsZ(messageArg, strlen(messageArg)); + } + + JSString* newMessageString(JSContext* cx); + + private: + void freeMessage(); +}; + +/** + * Notes associated with JSErrorReport. + */ +class JSErrorNotes { + public: + class Note final : public JSErrorBase {}; + + private: + // Stores pointers to each note. + js::Vector<js::UniquePtr<Note>, 1, js::SystemAllocPolicy> notes_; + + bool addNoteVA(js::FrontendContext* fc, const char* filename, + unsigned sourceId, unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, + js::ErrorArgumentsType argumentsType, va_list ap); + + public: + JSErrorNotes(); + ~JSErrorNotes(); + + // Add an note to the given position. + bool addNoteASCII(JSContext* cx, const char* filename, unsigned sourceId, + unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...); + bool addNoteASCII(js::FrontendContext* fc, const char* filename, + unsigned sourceId, unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...); + bool addNoteLatin1(JSContext* cx, const char* filename, unsigned sourceId, + unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...); + bool addNoteLatin1(js::FrontendContext* fc, const char* filename, + unsigned sourceId, unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...); + bool addNoteUTF8(JSContext* cx, const char* filename, unsigned sourceId, + unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...); + bool addNoteUTF8(js::FrontendContext* fc, const char* filename, + unsigned sourceId, unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...); + + JS_PUBLIC_API size_t length(); + + // Create a deep copy of notes. + js::UniquePtr<JSErrorNotes> copy(JSContext* cx); + + class iterator final { + private: + js::UniquePtr<Note>* note_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = js::UniquePtr<Note>; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + explicit iterator(js::UniquePtr<Note>* note = nullptr) : note_(note) {} + + bool operator==(iterator other) const { return note_ == other.note_; } + bool operator!=(iterator other) const { return !(*this == other); } + iterator& operator++() { + note_++; + return *this; + } + reference operator*() { return *note_; } + }; + + JS_PUBLIC_API iterator begin(); + JS_PUBLIC_API iterator end(); +}; + +/** + * Describes a single error or warning that occurs in the execution of script. + */ +class JSErrorReport : public JSErrorBase { + private: + // Offending source line without final '\n'. + // If ownsLinebuf_ is true, the buffer is freed in destructor. + const char16_t* linebuf_; + + // Number of chars in linebuf_. Does not include trailing '\0'. + size_t linebufLength_; + + // The 0-based offset of error token in linebuf_. + size_t tokenOffset_; + + public: + // Associated notes, or nullptr if there's no note. + js::UniquePtr<JSErrorNotes> notes; + + // One of the JSExnType constants. + int16_t exnType; + + // See the comment in TransitiveCompileOptions. + bool isMuted : 1; + + // This error report is actually a warning. + bool isWarning_ : 1; + + private: + bool ownsLinebuf_ : 1; + + public: + JSErrorReport() + : linebuf_(nullptr), + linebufLength_(0), + tokenOffset_(0), + notes(nullptr), + exnType(0), + isMuted(false), + isWarning_(false), + ownsLinebuf_(false) {} + JSErrorReport(JSErrorReport&& other) noexcept + : JSErrorBase(std::move(other)), + linebuf_(other.linebuf_), + linebufLength_(other.linebufLength_), + tokenOffset_(other.tokenOffset_), + notes(std::move(other.notes)), + exnType(other.exnType), + isMuted(other.isMuted), + isWarning_(other.isWarning_), + ownsLinebuf_(other.ownsLinebuf_) { + if (ownsLinebuf_) { + other.ownsLinebuf_ = false; + } + } + + ~JSErrorReport() { freeLinebuf(); } + + public: + const char16_t* linebuf() const { return linebuf_; } + size_t linebufLength() const { return linebufLength_; } + size_t tokenOffset() const { return tokenOffset_; } + void initOwnedLinebuf(const char16_t* linebufArg, size_t linebufLengthArg, + size_t tokenOffsetArg) { + initBorrowedLinebuf(linebufArg, linebufLengthArg, tokenOffsetArg); + ownsLinebuf_ = true; + } + void initBorrowedLinebuf(const char16_t* linebufArg, size_t linebufLengthArg, + size_t tokenOffsetArg); + + bool isWarning() const { return isWarning_; } + + private: + void freeLinebuf(); +}; + +namespace JS { + +struct MOZ_STACK_CLASS JS_PUBLIC_API ErrorReportBuilder { + explicit ErrorReportBuilder(JSContext* cx); + ~ErrorReportBuilder(); + + enum SniffingBehavior { WithSideEffects, NoSideEffects }; + + /** + * Generate a JSErrorReport from the provided thrown value. + * + * If the value is a (possibly wrapped) Error object, the JSErrorReport will + * be exactly initialized from the Error object's information, without + * observable side effects. (The Error object's JSErrorReport is reused, if + * it has one.) + * + * Otherwise various attempts are made to derive JSErrorReport information + * from |exnStack| and from the current execution state. This process is + * *definitely* inconsistent with any standard, and particulars of the + * behavior implemented here generally shouldn't be relied upon. + * + * If the value of |sniffingBehavior| is |WithSideEffects|, some of these + * attempts *may* invoke user-configurable behavior when the exception is an + * object: converting it to a string, detecting and getting its properties, + * accessing its prototype chain, and others are possible. Users *must* + * tolerate |ErrorReportBuilder::init| potentially having arbitrary effects. + * Any exceptions thrown by these operations will be caught and silently + * ignored, and "default" values will be substituted into the JSErrorReport. + * + * But if the value of |sniffingBehavior| is |NoSideEffects|, these attempts + * *will not* invoke any observable side effects. The JSErrorReport will + * simply contain fewer, less precise details. + * + * Unlike some functions involved in error handling, this function adheres + * to the usual JSAPI return value error behavior. + */ + bool init(JSContext* cx, const JS::ExceptionStack& exnStack, + SniffingBehavior sniffingBehavior); + + JSErrorReport* report() const { return reportp; } + + const JS::ConstUTF8CharsZ toStringResult() const { return toStringResult_; } + + private: + // More or less an equivalent of JS_ReportErrorNumber/js::ReportErrorNumberVA + // but fills in an ErrorReport instead of reporting it. Uses varargs to + // make it simpler to call js::ExpandErrorArgumentsVA. + // + // Returns false if we fail to actually populate the ErrorReport + // for some reason (probably out of memory). + bool populateUncaughtExceptionReportUTF8(JSContext* cx, + JS::HandleObject stack, ...); + bool populateUncaughtExceptionReportUTF8VA(JSContext* cx, + JS::HandleObject stack, + va_list ap); + + // Reports exceptions from add-on scopes to telemetry. + void ReportAddonExceptionToTelemetry(JSContext* cx); + + // We may have a provided JSErrorReport, so need a way to represent that. + JSErrorReport* reportp; + + // Or we may need to synthesize a JSErrorReport one of our own. + JSErrorReport ownedReport; + + // Root our exception value to keep a possibly borrowed |reportp| alive. + JS::RootedObject exnObject; + + // And for our filename. + JS::UniqueChars filename; + + // We may have a result of error.toString(). + // FIXME: We should not call error.toString(), since it could have side + // effect (see bug 633623). + JS::ConstUTF8CharsZ toStringResult_; + JS::UniqueChars toStringResultBytesStorage; +}; + +// Writes a full report to a file descriptor. Does nothing for JSErrorReports +// which are warnings, unless reportWarnings is set. +extern JS_PUBLIC_API void PrintError(FILE* file, JSErrorReport* report, + bool reportWarnings); + +extern JS_PUBLIC_API void PrintError(FILE* file, + const JS::ErrorReportBuilder& builder, + bool reportWarnings); + +} // namespace JS + +/* + * There are four encoding variants for the error reporting API: + * UTF-8 + * JSAPI's default encoding for error handling. Use this when the encoding + * of the error message, format string, and arguments is UTF-8. + * ASCII + * Equivalent to UTF-8, but also asserts that the error message, format + * string, and arguments are all ASCII. Because ASCII is a subset of UTF-8, + * any use of this encoding variant *could* be replaced with use of the + * UTF-8 variant. This variant exists solely to double-check the + * developer's assumption that all these strings truly are ASCII, given that + * UTF-8 and ASCII strings regrettably have the same C++ type. + * UC = UTF-16 + * Use this when arguments are UTF-16. The format string must be UTF-8. + * Latin1 (planned to be removed) + * In this variant, all strings are interpreted byte-for-byte as the + * corresponding Unicode codepoint. This encoding may *safely* be used on + * any null-terminated string, regardless of its encoding. (You shouldn't + * *actually* be uncertain, but in the real world, a string's encoding -- if + * promised at all -- may be more...aspirational...than reality.) This + * encoding variant will eventually be removed -- work to convert your uses + * to UTF-8 as you're able. + */ + +namespace JS { +const uint16_t MaxNumErrorArguments = 10; +}; + +/** + * Report an exception represented by the sprintf-like conversion of format + * and its arguments. + */ +extern JS_PUBLIC_API void JS_ReportErrorASCII(JSContext* cx, const char* format, + ...) MOZ_FORMAT_PRINTF(2, 3); + +extern JS_PUBLIC_API void JS_ReportErrorLatin1(JSContext* cx, + const char* format, ...) + MOZ_FORMAT_PRINTF(2, 3); + +extern JS_PUBLIC_API void JS_ReportErrorUTF8(JSContext* cx, const char* format, + ...) MOZ_FORMAT_PRINTF(2, 3); + +/* + * Use an errorNumber to retrieve the format string, args are char* + */ +extern JS_PUBLIC_API void JS_ReportErrorNumberASCII( + JSContext* cx, JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...); + +extern JS_PUBLIC_API void JS_ReportErrorNumberASCIIVA( + JSContext* cx, JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, va_list ap); + +extern JS_PUBLIC_API void JS_ReportErrorNumberLatin1( + JSContext* cx, JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...); + +#ifdef va_start +extern JS_PUBLIC_API void JS_ReportErrorNumberLatin1VA( + JSContext* cx, JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, va_list ap); +#endif + +extern JS_PUBLIC_API void JS_ReportErrorNumberUTF8( + JSContext* cx, JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...); + +#ifdef va_start +extern JS_PUBLIC_API void JS_ReportErrorNumberUTF8VA( + JSContext* cx, JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, va_list ap); +#endif + +/* + * args is null-terminated. That is, a null char* means there are no + * more args. The number of args must match the number expected for + * errorNumber for the given JSErrorCallback. + */ +extern JS_PUBLIC_API void JS_ReportErrorNumberUTF8Array( + JSContext* cx, JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, const char** args); + +/* + * Use an errorNumber to retrieve the format string, args are char16_t* + */ +extern JS_PUBLIC_API void JS_ReportErrorNumberUC(JSContext* cx, + JSErrorCallback errorCallback, + void* userRef, + const unsigned errorNumber, + ...); + +extern JS_PUBLIC_API void JS_ReportErrorNumberUCArray( + JSContext* cx, JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, const char16_t** args); + +/** + * Complain when out of memory. + */ +extern MOZ_COLD JS_PUBLIC_API void JS_ReportOutOfMemory(JSContext* cx); + +extern JS_PUBLIC_API bool JS_ExpandErrorArgumentsASCII( + JSContext* cx, JSErrorCallback errorCallback, const unsigned errorNumber, + JSErrorReport* reportp, ...); + +/** + * Complain when an allocation size overflows the maximum supported limit. + */ +extern JS_PUBLIC_API void JS_ReportAllocationOverflow(JSContext* cx); + +namespace JS { + +extern JS_PUBLIC_API bool CreateError( + JSContext* cx, JSExnType type, HandleObject stack, HandleString fileName, + uint32_t lineNumber, uint32_t columnNumber, JSErrorReport* report, + HandleString message, Handle<mozilla::Maybe<Value>> cause, + MutableHandleValue rval); + +} /* namespace JS */ + +#endif /* js_ErrorReport_h */ diff --git a/js/public/Exception.h b/js/public/Exception.h new file mode 100644 index 0000000000..649c40ac47 --- /dev/null +++ b/js/public/Exception.h @@ -0,0 +1,196 @@ +/* -*- 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 js_Exception_h +#define js_Exception_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include "jstypes.h" + +#include "js/RootingAPI.h" // JS::{Handle,Rooted} +#include "js/TypeDecls.h" +#include "js/Value.h" // JS::Value, JS::Handle<JS::Value> + +class JSErrorReport; + +namespace JS { +enum class ExceptionStackBehavior : bool { + // Do not capture any stack. + DoNotCapture, + + // Capture the current JS stack when setting the exception. It may be + // retrieved by JS::GetPendingExceptionStack. + Capture +}; +} // namespace JS + +extern JS_PUBLIC_API bool JS_IsExceptionPending(JSContext* cx); + +extern JS_PUBLIC_API bool JS_IsThrowingOutOfMemory(JSContext* cx); + +extern JS_PUBLIC_API bool JS_GetPendingException(JSContext* cx, + JS::MutableHandleValue vp); + +extern JS_PUBLIC_API void JS_SetPendingException( + JSContext* cx, JS::HandleValue v, + JS::ExceptionStackBehavior behavior = JS::ExceptionStackBehavior::Capture); + +extern JS_PUBLIC_API void JS_ClearPendingException(JSContext* cx); + +/** + * If the given object is an exception object, the exception will have (or be + * able to lazily create) an error report struct, and this function will return + * the address of that struct. Otherwise, it returns nullptr. The lifetime + * of the error report struct that might be returned is the same as the + * lifetime of the exception object. + */ +extern JS_PUBLIC_API JSErrorReport* JS_ErrorFromException(JSContext* cx, + JS::HandleObject obj); + +namespace JS { + +// When propagating an exception up the call stack, we store the underlying +// reason on the JSContext as one of the following enum values. +// +// TODO: Track uncatchable exceptions explicitly. +enum class ExceptionStatus { + // No exception status. + None, + + // Used by debugger when forcing an early return from a frame. This uses + // exception machinery, but at the right time is turned back into a normal + // non-error completion. + ForcedReturn, + + // Throwing a (catchable) exception. Certain well-known exceptions are + // explicitly tracked for convenience. + Throwing, + OutOfMemory, + OverRecursed, +}; + +// Returns true if the status is a catchable exception. Formerly this was +// indicated by the `JSContext::throwing` flag. +static MOZ_ALWAYS_INLINE bool IsCatchableExceptionStatus( + ExceptionStatus status) { + return status >= ExceptionStatus::Throwing; +} + +// This class encapsulates a (pending) exception and the corresponding optional +// SavedFrame stack object captured when the pending exception was set +// on the JSContext. This fuzzily correlates with a `throw` statement in JS, +// although arbitrary JSAPI consumers or VM code may also set pending exceptions +// via `JS_SetPendingException`. +// +// This is not the same stack as `e.stack` when `e` is an `Error` object. +// (That would be JS::ExceptionStackOrNull). +class MOZ_STACK_CLASS ExceptionStack { + Rooted<Value> exception_; + Rooted<JSObject*> stack_; + + friend JS_PUBLIC_API bool GetPendingExceptionStack( + JSContext* cx, JS::ExceptionStack* exceptionStack); + + void init(HandleValue exception, HandleObject stack) { + exception_ = exception; + stack_ = stack; + } + + public: + explicit ExceptionStack(JSContext* cx) : exception_(cx), stack_(cx) {} + + ExceptionStack(JSContext* cx, HandleValue exception, HandleObject stack) + : exception_(cx, exception), stack_(cx, stack) {} + + HandleValue exception() const { return exception_; } + + // |stack| can be null. + HandleObject stack() const { return stack_; } +}; + +/** + * Save and later restore the current exception state of a given JSContext. + * This is useful for implementing behavior in C++ that's like try/catch + * or try/finally in JS. + * + * Typical usage: + * + * bool ok = JS::Evaluate(cx, ...); + * AutoSaveExceptionState savedExc(cx); + * ... cleanup that might re-enter JS ... + * return ok; + */ +class JS_PUBLIC_API AutoSaveExceptionState { + private: + JSContext* context; + ExceptionStatus status; + RootedValue exceptionValue; + RootedObject exceptionStack; + + public: + /* + * Take a snapshot of cx's current exception state. Then clear any current + * pending exception in cx. + */ + explicit AutoSaveExceptionState(JSContext* cx); + + /* + * If neither drop() nor restore() was called, restore the exception + * state only if no exception is currently pending on cx. + */ + ~AutoSaveExceptionState(); + + /* + * Discard any stored exception state. + * If this is called, the destructor is a no-op. + */ + void drop(); + + /* + * Replace cx's exception state with the stored exception state. Then + * discard the stored exception state. If this is called, the + * destructor is a no-op. + */ + void restore(); +}; + +// Get the current pending exception value and stack. +// This function asserts that there is a pending exception. +// If this function returns false, then retrieving the current pending exception +// failed and might have been overwritten by a new exception. +extern JS_PUBLIC_API bool GetPendingExceptionStack( + JSContext* cx, JS::ExceptionStack* exceptionStack); + +// Similar to GetPendingExceptionStack, but also clears the current +// pending exception. +extern JS_PUBLIC_API bool StealPendingExceptionStack( + JSContext* cx, JS::ExceptionStack* exceptionStack); + +// Set both the exception value and its associated stack on the context as +// the current pending exception. +extern JS_PUBLIC_API void SetPendingExceptionStack( + JSContext* cx, const JS::ExceptionStack& exceptionStack); + +/** + * If the given object is an exception object (or an unwrappable + * cross-compartment wrapper for one), return the stack for that exception, if + * any. Will return null if the given object is not an exception object + * (including if it's null or a security wrapper that can't be unwrapped) or if + * the exception has no stack. + */ +extern JS_PUBLIC_API JSObject* ExceptionStackOrNull(JS::HandleObject obj); + +/** + * If the given object is an exception object, return the error cause for that + * exception, if any, or mozilla::Nothing. + */ +extern JS_PUBLIC_API mozilla::Maybe<JS::Value> GetExceptionCause(JSObject* exc); + +} // namespace JS + +#endif // js_Exception_h diff --git a/js/public/ForOfIterator.h b/js/public/ForOfIterator.h new file mode 100644 index 0000000000..db0a099e5e --- /dev/null +++ b/js/public/ForOfIterator.h @@ -0,0 +1,118 @@ +/* -*- 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/. */ + +/* + * A convenience class that makes it easy to perform the operations of a for-of + * loop. + */ + +#ifndef js_ForOfIterator_h +#define js_ForOfIterator_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS + +#include <stdint.h> // UINT32_MAX, uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/RootingAPI.h" // JS::{Handle,Rooted} +#include "js/Value.h" // JS::Value, JS::{,Mutable}Handle<JS::Value> + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; + +namespace JS { + +/** + * A convenience class for imitating a JS for-of loop. Typical usage: + * + * JS::ForOfIterator it(cx); + * if (!it.init(iterable)) { + * return false; + * } + * JS::Rooted<JS::Value> val(cx); + * while (true) { + * bool done; + * if (!it.next(&val, &done)) { + * return false; + * } + * if (done) { + * break; + * } + * if (!DoStuff(cx, val)) { + * return false; + * } + * } + */ +class MOZ_STACK_CLASS JS_PUBLIC_API ForOfIterator { + protected: + JSContext* cx_; + + // Use the ForOfPIC on the global object (see vm/GlobalObject.h) to try to + // optimize iteration across arrays. + // + // Case 1: Regular Iteration + // iterator - pointer to the iterator object. + // nextMethod - value of |iterator|.next. + // index - fixed to NOT_ARRAY (== UINT32_MAX) + // + // Case 2: Optimized Array Iteration + // iterator - pointer to the array object. + // nextMethod - the undefined value. + // index - current position in array. + // + // The cases are distinguished by whether |index == NOT_ARRAY|. + Rooted<JSObject*> iterator; + Rooted<Value> nextMethod; + + static constexpr uint32_t NOT_ARRAY = UINT32_MAX; + + uint32_t index = NOT_ARRAY; + + ForOfIterator(const ForOfIterator&) = delete; + ForOfIterator& operator=(const ForOfIterator&) = delete; + + public: + explicit ForOfIterator(JSContext* cx) + : cx_(cx), iterator(cx), nextMethod(cx) {} + + enum NonIterableBehavior { ThrowOnNonIterable, AllowNonIterable }; + + /** + * Initialize the iterator. If AllowNonIterable is passed then if getting + * the @@iterator property from iterable returns undefined init() will just + * return true instead of throwing. Callers must then check + * valueIsIterable() before continuing with the iteration. + */ + [[nodiscard]] bool init( + Handle<Value> iterable, + NonIterableBehavior nonIterableBehavior = ThrowOnNonIterable); + + /** + * Get the next value from the iterator. If false *done is true + * after this call, do not examine val. + */ + [[nodiscard]] bool next(MutableHandle<Value> val, bool* done); + + /** + * Close the iterator. + * For the case that completion type is throw. + */ + void closeThrow(); + + /** + * If initialized with throwOnNonCallable = false, check whether + * the value is iterable. + */ + bool valueIsIterable() const { return iterator; } + + private: + inline bool nextFromOptimizedArray(MutableHandle<Value> val, bool* done); +}; + +} // namespace JS + +#endif // js_ForOfIterator_h diff --git a/js/public/GCAPI.h b/js/public/GCAPI.h new file mode 100644 index 0000000000..fa441117d3 --- /dev/null +++ b/js/public/GCAPI.h @@ -0,0 +1,1347 @@ +/* -*- 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/. */ + +/* + * High-level interface to the JS garbage collector. + */ + +#ifndef js_GCAPI_h +#define js_GCAPI_h + +#include "mozilla/TimeStamp.h" +#include "mozilla/Vector.h" + +#include "js/GCAnnotations.h" +#include "js/shadow/Zone.h" +#include "js/SliceBudget.h" +#include "js/TypeDecls.h" +#include "js/UniquePtr.h" +#include "js/Utility.h" + +class JS_PUBLIC_API JSTracer; + +namespace js { +namespace gc { +class GCRuntime; +} // namespace gc +class JS_PUBLIC_API SliceBudget; +namespace gcstats { +struct Statistics; +} // namespace gcstats +} // namespace js + +namespace JS { + +// Options used when starting a GC. +enum class GCOptions : uint32_t { + // Normal GC invocation. + // + // Some objects that are unreachable from the program may still be alive after + // collection because of internal references + Normal = 0, + + // A shrinking GC. + // + // Try to release as much memory as possible by clearing internal caches, + // aggressively discarding JIT code and decommitting unused chunks. This + // ensures all unreferenced objects are removed from the system. + // + // Finally, compact the GC heap. + Shrink = 1, + + // A shutdown GC. + // + // This does more drastic cleanup as part of system shutdown, including: + // - clearing WeakRef kept object sets + // - not marking FinalizationRegistry roots + // - repeating collection if JS::NotifyGCRootsRemoved was called + // - skipping scheduling of various future work that won't be needed + // + // Note that this assumes that no JS will run after this point! + Shutdown = 2 +}; + +} // namespace JS + +typedef enum JSGCParamKey { + /** + * Maximum nominal heap before last ditch GC. + * + * Soft limit on the number of bytes we are allowed to allocate in the GC + * heap. Attempts to allocate gcthings over this limit will return null and + * subsequently invoke the standard OOM machinery, independent of available + * physical memory. + * + * Pref: javascript.options.mem.max + * Default: 0xffffffff + */ + JSGC_MAX_BYTES = 0, + + /** + * Maximum size of the generational GC nurseries. + * + * This will be rounded to the nearest gc::ChunkSize. + * + * Pref: javascript.options.mem.nursery.max_kb + * Default: JS::DefaultNurseryMaxBytes + */ + JSGC_MAX_NURSERY_BYTES = 2, + + /** Amount of bytes allocated by the GC. */ + JSGC_BYTES = 3, + + /** Number of times GC has been invoked. Includes both major and minor GC. */ + JSGC_NUMBER = 4, + + /** + * Whether incremental GC is enabled. If not, GC will always run to + * completion. + * + * prefs: javascript.options.mem.gc_incremental. + * Default: false + */ + JSGC_INCREMENTAL_GC_ENABLED = 5, + + /** + * Whether per-zone GC is enabled. If not, all zones are collected every time. + * + * prefs: javascript.options.mem.gc_per_zone + * Default: false + */ + JSGC_PER_ZONE_GC_ENABLED = 6, + + /** Number of cached empty GC chunks. */ + JSGC_UNUSED_CHUNKS = 7, + + /** Total number of allocated GC chunks. */ + JSGC_TOTAL_CHUNKS = 8, + + /** + * Max milliseconds to spend in an incremental GC slice. + * + * A value of zero means there is no maximum. + * + * Pref: javascript.options.mem.gc_incremental_slice_ms + * Default: DefaultTimeBudgetMS. + */ + JSGC_SLICE_TIME_BUDGET_MS = 9, + + /** + * The "do we collect?" decision depends on various parameters and can be + * summarised as: + * + * ZoneSize > Max(ThresholdBase, LastSize) * GrowthFactor * ThresholdFactor + * + * Where + * ZoneSize: Current size of this zone. + * LastSize: Heap size immediately after the most recent collection. + * ThresholdBase: The JSGC_ALLOCATION_THRESHOLD parameter + * GrowthFactor: A number above 1, calculated based on some of the + * following parameters. + * See computeZoneHeapGrowthFactorForHeapSize() in GC.cpp + * ThresholdFactor: 1.0 to trigger an incremental collections or between + * JSGC_SMALL_HEAP_INCREMENTAL_LIMIT and + * JSGC_LARGE_HEAP_INCREMENTAL_LIMIT to trigger a + * non-incremental collection. + * + * The RHS of the equation above is calculated and sets + * zone->gcHeapThreshold.bytes(). When gcHeapSize.bytes() exeeds + * gcHeapThreshold.bytes() for a zone, the zone may be scheduled for a GC. + */ + + /** + * GCs less than this far apart in milliseconds will be considered + * 'high-frequency GCs'. + * + * Pref: javascript.options.mem.gc_high_frequency_time_limit_ms + * Default: HighFrequencyThreshold + */ + JSGC_HIGH_FREQUENCY_TIME_LIMIT = 11, + + /** + * Upper limit for classifying a heap as small (MB). + * + * Dynamic heap growth thresholds are based on whether the heap is small, + * medium or large. Heaps smaller than this size are classified as small; + * larger heaps are classified as medium or large. + * + * Pref: javascript.options.mem.gc_small_heap_size_max_mb + * Default: SmallHeapSizeMaxBytes + */ + JSGC_SMALL_HEAP_SIZE_MAX = 12, + + /** + * Lower limit for classifying a heap as large (MB). + * + * Dynamic heap growth thresholds are based on whether the heap is small, + * medium or large. Heaps larger than this size are classified as large; + * smaller heaps are classified as small or medium. + * + * Pref: javascript.options.mem.gc_large_heap_size_min_mb + * Default: LargeHeapSizeMinBytes + */ + JSGC_LARGE_HEAP_SIZE_MIN = 13, + + /** + * Heap growth factor for small heaps in the high-frequency GC state. + * + * Pref: javascript.options.mem.gc_high_frequency_small_heap_growth + * Default: HighFrequencySmallHeapGrowth + */ + JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH = 14, + + /** + * Heap growth factor for large heaps in the high-frequency GC state. + * + * Pref: javascript.options.mem.gc_high_frequency_large_heap_growth + * Default: HighFrequencyLargeHeapGrowth + */ + JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH = 15, + + /** + * Heap growth factor for low frequency GCs. + * + * This factor is applied regardless of the size of the heap when not in the + * high-frequency GC state. + * + * Pref: javascript.options.mem.gc_low_frequency_heap_growth + * Default: LowFrequencyHeapGrowth + */ + JSGC_LOW_FREQUENCY_HEAP_GROWTH = 16, + + /** + * Whether balanced heap limits are enabled. + * + * If this is set to true then heap limits are calculated in a way designed to + * balance memory usage optimally between many heaps. + * + * Otherwise, heap limits are set based on a linear multiple of the retained + * size after the last collection. + * + * Pref: javascript.options.mem.gc_balanced_heap_limits + * Default: BalancedHeapLimitsEnabled + */ + JSGC_BALANCED_HEAP_LIMITS_ENABLED = 17, + + /** + * Heap growth parameter for balanced heap limit calculation. + * + * This parameter trades off GC time for memory usage. Smaller values result + * in lower memory use and larger values result in less time spent collecting. + * + * Heap limits are set to the heap's retained size plus some extra space. The + * extra space is calculated based on several factors but is scaled + * proportionally to this parameter. + * + * Pref: javascript.options.mem.gc_heap_growth_factor + * Default: HeapGrowthFactor + */ + JSGC_HEAP_GROWTH_FACTOR = 18, + + /** + * Lower limit for collecting a zone (MB). + * + * Zones smaller than this size will not normally be collected. + * + * Pref: javascript.options.mem.gc_allocation_threshold_mb + * Default GCZoneAllocThresholdBase + */ + JSGC_ALLOCATION_THRESHOLD = 19, + + /** + * We try to keep at least this many unused chunks in the free chunk pool at + * all times, even after a shrinking GC. + * + * Pref: javascript.options.mem.gc_min_empty_chunk_count + * Default: MinEmptyChunkCount + */ + JSGC_MIN_EMPTY_CHUNK_COUNT = 21, + + /** + * We never keep more than this many unused chunks in the free chunk pool. + * + * Pref: javascript.options.mem.gc_max_empty_chunk_count + * Default: MaxEmptyChunkCount + */ + JSGC_MAX_EMPTY_CHUNK_COUNT = 22, + + /** + * Whether compacting GC is enabled. + * + * Pref: javascript.options.mem.gc_compacting + * Default: CompactingEnabled + */ + JSGC_COMPACTING_ENABLED = 23, + + /** + * Whether parallel marking is enabled. + * + * Pref: javascript.options.mem.gc_parallel_marking + * Default: ParallelMarkingEnabled + */ + JSGC_PARALLEL_MARKING_ENABLED = 24, + + /** + * Limit of how far over the incremental trigger threshold we allow the heap + * to grow before finishing a collection non-incrementally, for small heaps. + * + * We trigger an incremental GC when a trigger threshold is reached but the + * collection may not be fast enough to keep up with the mutator. At some + * point we finish the collection non-incrementally. + * + * Default: SmallHeapIncrementalLimit + * Pref: javascript.options.mem.gc_small_heap_incremental_limit + */ + JSGC_SMALL_HEAP_INCREMENTAL_LIMIT = 25, + + /** + * Limit of how far over the incremental trigger threshold we allow the heap + * to grow before finishing a collection non-incrementally, for large heaps. + * + * Default: LargeHeapIncrementalLimit + * Pref: javascript.options.mem.gc_large_heap_incremental_limit + */ + JSGC_LARGE_HEAP_INCREMENTAL_LIMIT = 26, + + /** + * Attempt to run a minor GC in the idle time if the free space falls + * below this number of bytes. + * + * Default: NurseryChunkUsableSize / 4 + * Pref: None + */ + JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION = 27, + + /** + * If this percentage of the nursery is tenured and the nursery is at least + * 4MB, then proceed to examine which groups we should pretenure. + * + * Default: PretenureThreshold + * Pref: None + */ + JSGC_PRETENURE_THRESHOLD = 28, + + /** + * Attempt to run a minor GC in the idle time if the free space falls + * below this percentage (from 0 to 99). + * + * Default: 25 + * Pref: None + */ + JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION_PERCENT = 30, + + /** + * Minimum size of the generational GC nurseries. + * + * This value will be rounded to the nearest Nursery::SubChunkStep if below + * gc::ChunkSize, otherwise it'll be rounded to the nearest gc::ChunkSize. + * + * Default: Nursery::SubChunkLimit + * Pref: javascript.options.mem.nursery.min_kb + */ + JSGC_MIN_NURSERY_BYTES = 31, + + /** + * The minimum time to allow between triggering last ditch GCs in seconds. + * + * Default: 60 seconds + * Pref: None + */ + JSGC_MIN_LAST_DITCH_GC_PERIOD = 32, + + /** + * The delay (in heapsize kilobytes) between slices of an incremental GC. + * + * Default: ZoneAllocDelayBytes + */ + JSGC_ZONE_ALLOC_DELAY_KB = 33, + + /* + * The current size of the nursery. + * + * This parameter is read-only. + */ + JSGC_NURSERY_BYTES = 34, + + /** + * Retained size base value for calculating malloc heap threshold. + * + * Default: MallocThresholdBase + */ + JSGC_MALLOC_THRESHOLD_BASE = 35, + + /** + * Whether incremental weakmap marking is enabled. + * + * Pref: javascript.options.mem.incremental_weakmap + * Default: IncrementalWeakMarkEnabled + */ + JSGC_INCREMENTAL_WEAKMAP_ENABLED = 37, + + /** + * The chunk size in bytes for this system. + * + * This parameter is read-only. + */ + JSGC_CHUNK_BYTES = 38, + + /** + * The number of background threads to use for parallel GC work for each CPU + * core, expressed as an integer percentage. + * + * Pref: javascript.options.mem.gc_helper_thread_ratio + */ + JSGC_HELPER_THREAD_RATIO = 39, + + /** + * The maximum number of background threads to use for parallel GC work. + * + * Pref: javascript.options.mem.gc_max_helper_threads + */ + JSGC_MAX_HELPER_THREADS = 40, + + /** + * The number of background threads to use for parallel GC work. + * + * This parameter is read-only and is set based on the + * JSGC_HELPER_THREAD_RATIO and JSGC_MAX_HELPER_THREADS parameters. + */ + JSGC_HELPER_THREAD_COUNT = 41, + + /** + * If the percentage of the tenured strings exceeds this threshold, string + * will be allocated in tenured heap instead. (Default is allocated in + * nursery.) + */ + JSGC_PRETENURE_STRING_THRESHOLD = 42, + + /** + * If the finalization rate of the tenured strings exceeds this threshold, + * string will be allocated in nursery. + */ + JSGC_STOP_PRETENURE_STRING_THRESHOLD = 43, + + /** + * A number that is incremented on every major GC slice. + */ + JSGC_MAJOR_GC_NUMBER = 44, + + /** + * A number that is incremented on every minor GC. + */ + JSGC_MINOR_GC_NUMBER = 45, + + /** + * JS::MaybeRunNurseryCollection will collect the nursery if it hasn't been + * collected in this many milliseconds. + * + * Default: 5000 + * Pref: None + */ + JSGC_NURSERY_TIMEOUT_FOR_IDLE_COLLECTION_MS = 46, + + /** + * The system page size in KB. + * + * This parameter is read-only. + */ + JSGC_SYSTEM_PAGE_SIZE_KB = 47, + + /** + * In an incremental GC, this determines the point at which to start + * increasing the slice budget and frequency of allocation triggered slices to + * try to avoid reaching the incremental limit and finishing the collection + * synchronously. + * + * The threshold is calculated by subtracting this value from the heap's + * incremental limit. + */ + JSGC_URGENT_THRESHOLD_MB = 48, + + /** + * Set the number of threads to use for parallel marking, or zero to use the + * default. + * + * The actual number used is capped to the number of available helper threads. + * + * This is provided for testing purposes. + * + * Pref: None. + * Default: 0 (no effect). + */ + JSGC_MARKING_THREAD_COUNT = 49, + + /** + * The heap size above which to use parallel marking. + * + * Default: ParallelMarkingThresholdKB + */ + JSGC_PARALLEL_MARKING_THRESHOLD_KB = 50, +} JSGCParamKey; + +/* + * Generic trace operation that calls JS::TraceEdge on each traceable thing's + * location reachable from data. + */ +typedef void (*JSTraceDataOp)(JSTracer* trc, void* data); + +/* + * Trace hook used to trace gray roots incrementally. + * + * This should return whether tracing is finished. It will be called repeatedly + * in subsequent GC slices until it returns true. + * + * While tracing this should check the budget and return false if it has been + * exceeded. When passed an unlimited budget it should always return true. + */ +typedef bool (*JSGrayRootsTracer)(JSTracer* trc, js::SliceBudget& budget, + void* data); + +typedef enum JSGCStatus { JSGC_BEGIN, JSGC_END } JSGCStatus; + +typedef void (*JSObjectsTenuredCallback)(JSContext* cx, void* data); + +typedef enum JSFinalizeStatus { + /** + * Called when preparing to sweep a group of zones, before anything has been + * swept. The collector will not yield to the mutator before calling the + * callback with JSFINALIZE_GROUP_START status. + */ + JSFINALIZE_GROUP_PREPARE, + + /** + * Called after preparing to sweep a group of zones. Weak references to + * unmarked things have been removed at this point, but no GC things have + * been swept. The collector may yield to the mutator after this point. + */ + JSFINALIZE_GROUP_START, + + /** + * Called after sweeping a group of zones. All dead GC things have been + * swept at this point. + */ + JSFINALIZE_GROUP_END, + + /** + * Called at the end of collection when everything has been swept. + */ + JSFINALIZE_COLLECTION_END +} JSFinalizeStatus; + +typedef void (*JSFinalizeCallback)(JS::GCContext* gcx, JSFinalizeStatus status, + void* data); + +typedef void (*JSWeakPointerZonesCallback)(JSTracer* trc, void* data); + +typedef void (*JSWeakPointerCompartmentCallback)(JSTracer* trc, + JS::Compartment* comp, + void* data); + +/* + * This is called to tell the embedding that a FinalizationRegistry object has + * cleanup work, and that the engine should be called back at an appropriate + * later time to perform this cleanup, by calling the function |doCleanup|. + * + * This callback must not do anything that could cause GC. + */ +using JSHostCleanupFinalizationRegistryCallback = + void (*)(JSFunction* doCleanup, JSObject* incumbentGlobal, void* data); + +/** + * Each external string has a pointer to JSExternalStringCallbacks. Embedders + * can use this to implement custom finalization or memory reporting behavior. + */ +struct JSExternalStringCallbacks { + /** + * Finalizes external strings created by JS_NewExternalString. The finalizer + * can be called off the main thread. + */ + virtual void finalize(char16_t* chars) const = 0; + + /** + * Callback used by memory reporting to ask the embedder how much memory an + * external string is keeping alive. The embedder is expected to return a + * value that corresponds to the size of the allocation that will be released + * by the finalizer callback above. + * + * Implementations of this callback MUST NOT do anything that can cause GC. + */ + virtual size_t sizeOfBuffer(const char16_t* chars, + mozilla::MallocSizeOf mallocSizeOf) const = 0; +}; + +namespace JS { + +#define GCREASONS(D) \ + /* Reasons internal to the JS engine. */ \ + D(API, 0) \ + D(EAGER_ALLOC_TRIGGER, 1) \ + D(DESTROY_RUNTIME, 2) \ + D(ROOTS_REMOVED, 3) \ + D(LAST_DITCH, 4) \ + D(TOO_MUCH_MALLOC, 5) \ + D(ALLOC_TRIGGER, 6) \ + D(DEBUG_GC, 7) \ + D(COMPARTMENT_REVIVED, 8) \ + D(RESET, 9) \ + D(OUT_OF_NURSERY, 10) \ + D(EVICT_NURSERY, 11) \ + D(SHARED_MEMORY_LIMIT, 13) \ + D(EAGER_NURSERY_COLLECTION, 14) \ + D(BG_TASK_FINISHED, 15) \ + D(ABORT_GC, 16) \ + D(FULL_WHOLE_CELL_BUFFER, 17) \ + D(FULL_GENERIC_BUFFER, 18) \ + D(FULL_VALUE_BUFFER, 19) \ + D(FULL_CELL_PTR_OBJ_BUFFER, 20) \ + D(FULL_SLOT_BUFFER, 21) \ + D(FULL_SHAPE_BUFFER, 22) \ + D(TOO_MUCH_WASM_MEMORY, 23) \ + D(DISABLE_GENERATIONAL_GC, 24) \ + D(FINISH_GC, 25) \ + D(PREPARE_FOR_TRACING, 26) \ + D(UNUSED4, 27) \ + D(FULL_CELL_PTR_STR_BUFFER, 28) \ + D(TOO_MUCH_JIT_CODE, 29) \ + D(FULL_CELL_PTR_BIGINT_BUFFER, 30) \ + D(NURSERY_TRAILERS, 31) \ + D(NURSERY_MALLOC_BUFFERS, 32) \ + \ + /* \ + * Reasons from Firefox. \ + * \ + * The JS engine attaches special meanings to some of these reasons. \ + */ \ + D(DOM_WINDOW_UTILS, FIRST_FIREFOX_REASON) \ + D(COMPONENT_UTILS, 34) \ + D(MEM_PRESSURE, 35) \ + D(CC_FINISHED, 36) \ + D(CC_FORCED, 37) \ + D(LOAD_END, 38) \ + D(UNUSED3, 39) \ + D(PAGE_HIDE, 40) \ + D(NSJSCONTEXT_DESTROY, 41) \ + D(WORKER_SHUTDOWN, 42) \ + D(SET_DOC_SHELL, 43) \ + D(DOM_UTILS, 44) \ + D(DOM_IPC, 45) \ + D(DOM_WORKER, 46) \ + D(INTER_SLICE_GC, 47) \ + D(UNUSED1, 48) \ + D(FULL_GC_TIMER, 49) \ + D(SHUTDOWN_CC, 50) \ + D(UNUSED2, 51) \ + D(USER_INACTIVE, 52) \ + D(XPCONNECT_SHUTDOWN, 53) \ + D(DOCSHELL, 54) \ + D(HTML_PARSER, 55) \ + D(DOM_TESTUTILS, 56) \ + \ + /* Reasons reserved for embeddings. */ \ + D(RESERVED1, FIRST_RESERVED_REASON) \ + D(RESERVED2, 91) \ + D(RESERVED3, 92) \ + D(RESERVED4, 93) \ + D(RESERVED5, 94) \ + D(RESERVED6, 95) \ + D(RESERVED7, 96) \ + D(RESERVED8, 97) \ + D(RESERVED9, 98) + +enum class GCReason { + FIRST_FIREFOX_REASON = 33, + FIRST_RESERVED_REASON = 90, + +#define MAKE_REASON(name, val) name = val, + GCREASONS(MAKE_REASON) +#undef MAKE_REASON + NO_REASON, + NUM_REASONS, + + /* + * For telemetry, we want to keep a fixed max bucket size over time so we + * don't have to switch histograms. 100 is conservative; but the cost of extra + * buckets seems to be low while the cost of switching histograms is high. + */ + NUM_TELEMETRY_REASONS = 100 +}; + +/** + * Get a statically allocated C string explaining the given GC reason. + */ +extern JS_PUBLIC_API const char* ExplainGCReason(JS::GCReason reason); + +/** + * Return true if the GC reason is internal to the JS engine. + */ +extern JS_PUBLIC_API bool InternalGCReason(JS::GCReason reason); + +/* + * Zone GC: + * + * SpiderMonkey's GC is capable of performing a collection on an arbitrary + * subset of the zones in the system. This allows an embedding to minimize + * collection time by only collecting zones that have run code recently, + * ignoring the parts of the heap that are unlikely to have changed. + * + * When triggering a GC using one of the functions below, it is first necessary + * to select the zones to be collected. To do this, you can call + * PrepareZoneForGC on each zone, or you can call PrepareForFullGC to select + * all zones. Failing to select any zone is an error. + */ + +/** + * Schedule the given zone to be collected as part of the next GC. + */ +extern JS_PUBLIC_API void PrepareZoneForGC(JSContext* cx, Zone* zone); + +/** + * Schedule all zones to be collected in the next GC. + */ +extern JS_PUBLIC_API void PrepareForFullGC(JSContext* cx); + +/** + * When performing an incremental GC, the zones that were selected for the + * previous incremental slice must be selected in subsequent slices as well. + * This function selects those slices automatically. + */ +extern JS_PUBLIC_API void PrepareForIncrementalGC(JSContext* cx); + +/** + * Returns true if any zone in the system has been scheduled for GC with one of + * the functions above or by the JS engine. + */ +extern JS_PUBLIC_API bool IsGCScheduled(JSContext* cx); + +/** + * Undoes the effect of the Prepare methods above. The given zone will not be + * collected in the next GC. + */ +extern JS_PUBLIC_API void SkipZoneForGC(JSContext* cx, Zone* zone); + +/* + * Non-Incremental GC: + * + * The following functions perform a non-incremental GC. + */ + +/** + * Performs a non-incremental collection of all selected zones. + */ +extern JS_PUBLIC_API void NonIncrementalGC(JSContext* cx, JS::GCOptions options, + GCReason reason); + +/* + * Incremental GC: + * + * Incremental GC divides the full mark-and-sweep collection into multiple + * slices, allowing client JavaScript code to run between each slice. This + * allows interactive apps to avoid long collection pauses. Incremental GC does + * not make collection take less time, it merely spreads that time out so that + * the pauses are less noticable. + * + * For a collection to be carried out incrementally the following conditions + * must be met: + * - The collection must be run by calling JS::IncrementalGC() rather than + * JS_GC(). + * - The GC parameter JSGC_INCREMENTAL_GC_ENABLED must be true. + * + * Note: Even if incremental GC is enabled and working correctly, + * non-incremental collections can still happen when low on memory. + */ + +/** + * Begin an incremental collection and perform one slice worth of work. When + * this function returns, the collection may not be complete. + * IncrementalGCSlice() must be called repeatedly until + * !IsIncrementalGCInProgress(cx). + * + * Note: SpiderMonkey's GC is not realtime. Slices in practice may be longer or + * shorter than the requested interval. + */ +extern JS_PUBLIC_API void StartIncrementalGC(JSContext* cx, + JS::GCOptions options, + GCReason reason, + const js::SliceBudget& budget); + +/** + * Perform a slice of an ongoing incremental collection. When this function + * returns, the collection may not be complete. It must be called repeatedly + * until !IsIncrementalGCInProgress(cx). + * + * Note: SpiderMonkey's GC is not realtime. Slices in practice may be longer or + * shorter than the requested interval. + */ +extern JS_PUBLIC_API void IncrementalGCSlice(JSContext* cx, GCReason reason, + const js::SliceBudget& budget); + +/** + * Return whether an incremental GC has work to do on the foreground thread and + * would make progress if a slice was run now. If this returns false then the GC + * is waiting for background threads to finish their work and a slice started + * now would return immediately. + */ +extern JS_PUBLIC_API bool IncrementalGCHasForegroundWork(JSContext* cx); + +/** + * If IsIncrementalGCInProgress(cx), this call finishes the ongoing collection + * by performing an arbitrarily long slice. If !IsIncrementalGCInProgress(cx), + * this is equivalent to NonIncrementalGC. When this function returns, + * IsIncrementalGCInProgress(cx) will always be false. + */ +extern JS_PUBLIC_API void FinishIncrementalGC(JSContext* cx, GCReason reason); + +/** + * If IsIncrementalGCInProgress(cx), this call aborts the ongoing collection and + * performs whatever work needs to be done to return the collector to its idle + * state. This may take an arbitrarily long time. When this function returns, + * IsIncrementalGCInProgress(cx) will always be false. + */ +extern JS_PUBLIC_API void AbortIncrementalGC(JSContext* cx); + +namespace dbg { + +// The `JS::dbg::GarbageCollectionEvent` class is essentially a view of the +// `js::gcstats::Statistics` data without the uber implementation-specific bits. +// It should generally be palatable for web developers. +class GarbageCollectionEvent { + // The major GC number of the GC cycle this data pertains to. + uint64_t majorGCNumber_; + + // Reference to a non-owned, statically allocated C string. This is a very + // short reason explaining why a GC was triggered. + const char* reason; + + // Reference to a nullable, non-owned, statically allocated C string. If the + // collection was forced to be non-incremental, this is a short reason of + // why the GC could not perform an incremental collection. + const char* nonincrementalReason; + + // Represents a single slice of a possibly multi-slice incremental garbage + // collection. + struct Collection { + mozilla::TimeStamp startTimestamp; + mozilla::TimeStamp endTimestamp; + }; + + // The set of garbage collection slices that made up this GC cycle. + mozilla::Vector<Collection> collections; + + GarbageCollectionEvent(const GarbageCollectionEvent& rhs) = delete; + GarbageCollectionEvent& operator=(const GarbageCollectionEvent& rhs) = delete; + + public: + explicit GarbageCollectionEvent(uint64_t majorGCNum) + : majorGCNumber_(majorGCNum), + reason(nullptr), + nonincrementalReason(nullptr), + collections() {} + + using Ptr = js::UniquePtr<GarbageCollectionEvent>; + static Ptr Create(JSRuntime* rt, ::js::gcstats::Statistics& stats, + uint64_t majorGCNumber); + + JSObject* toJSObject(JSContext* cx) const; + + uint64_t majorGCNumber() const { return majorGCNumber_; } +}; + +} // namespace dbg + +enum GCProgress { + /* + * During GC, the GC is bracketed by GC_CYCLE_BEGIN/END callbacks. Each + * slice between those (whether an incremental or the sole non-incremental + * slice) is bracketed by GC_SLICE_BEGIN/GC_SLICE_END. + */ + + GC_CYCLE_BEGIN, + GC_SLICE_BEGIN, + GC_SLICE_END, + GC_CYCLE_END +}; + +struct JS_PUBLIC_API GCDescription { + bool isZone_; + bool isComplete_; + JS::GCOptions options_; + GCReason reason_; + + GCDescription(bool isZone, bool isComplete, JS::GCOptions options, + GCReason reason) + : isZone_(isZone), + isComplete_(isComplete), + options_(options), + reason_(reason) {} + + char16_t* formatSliceMessage(JSContext* cx) const; + char16_t* formatSummaryMessage(JSContext* cx) const; + + mozilla::TimeStamp startTime(JSContext* cx) const; + mozilla::TimeStamp endTime(JSContext* cx) const; + mozilla::TimeStamp lastSliceStart(JSContext* cx) const; + mozilla::TimeStamp lastSliceEnd(JSContext* cx) const; + + JS::UniqueChars sliceToJSONProfiler(JSContext* cx) const; + JS::UniqueChars formatJSONProfiler(JSContext* cx) const; + + JS::dbg::GarbageCollectionEvent::Ptr toGCEvent(JSContext* cx) const; +}; + +extern JS_PUBLIC_API UniqueChars MinorGcToJSON(JSContext* cx); + +typedef void (*GCSliceCallback)(JSContext* cx, GCProgress progress, + const GCDescription& desc); + +/** + * The GC slice callback is called at the beginning and end of each slice. This + * callback may be used for GC notifications as well as to perform additional + * marking. + */ +extern JS_PUBLIC_API GCSliceCallback +SetGCSliceCallback(JSContext* cx, GCSliceCallback callback); + +/** + * Describes the progress of an observed nursery collection. + */ +enum class GCNurseryProgress { + /** + * The nursery collection is starting. + */ + GC_NURSERY_COLLECTION_START, + /** + * The nursery collection is ending. + */ + GC_NURSERY_COLLECTION_END +}; + +/** + * A nursery collection callback receives the progress of the nursery collection + * and the reason for the collection. + */ +using GCNurseryCollectionCallback = void (*)(JSContext* cx, + GCNurseryProgress progress, + GCReason reason); + +/** + * Set the nursery collection callback for the given runtime. When set, it will + * be called at the start and end of every nursery collection. + */ +extern JS_PUBLIC_API GCNurseryCollectionCallback SetGCNurseryCollectionCallback( + JSContext* cx, GCNurseryCollectionCallback callback); + +typedef void (*DoCycleCollectionCallback)(JSContext* cx); + +/** + * The purge gray callback is called after any COMPARTMENT_REVIVED GC in which + * the majority of compartments have been marked gray. + */ +extern JS_PUBLIC_API DoCycleCollectionCallback +SetDoCycleCollectionCallback(JSContext* cx, DoCycleCollectionCallback callback); + +using CreateSliceBudgetCallback = js::SliceBudget (*)(JS::GCReason reason, + int64_t millis); + +/** + * Called when generating a GC slice budget. It allows the embedding to control + * the duration of slices and potentially check an interrupt flag as well. For + * internally triggered GCs, the given millis parameter is the JS engine's + * internal scheduling decision, which the embedding can choose to ignore. + * (Otherwise, it will be the value that was passed to eg + * JS::IncrementalGCSlice()). + */ +extern JS_PUBLIC_API void SetCreateGCSliceBudgetCallback( + JSContext* cx, CreateSliceBudgetCallback cb); + +/** + * Incremental GC defaults to enabled, but may be disabled for testing or in + * embeddings that have not yet implemented barriers on their native classes. + * There is not currently a way to re-enable incremental GC once it has been + * disabled on the runtime. + */ +extern JS_PUBLIC_API void DisableIncrementalGC(JSContext* cx); + +/** + * Returns true if incremental GC is enabled. Simply having incremental GC + * enabled is not sufficient to ensure incremental collections are happening. + * See the comment "Incremental GC" above for reasons why incremental GC may be + * suppressed. Inspection of the "nonincremental reason" field of the + * GCDescription returned by GCSliceCallback may help narrow down the cause if + * collections are not happening incrementally when expected. + */ +extern JS_PUBLIC_API bool IsIncrementalGCEnabled(JSContext* cx); + +/** + * Returns true while an incremental GC is ongoing, both when actively + * collecting and between slices. + */ +extern JS_PUBLIC_API bool IsIncrementalGCInProgress(JSContext* cx); + +/** + * Returns true while an incremental GC is ongoing, both when actively + * collecting and between slices. + */ +extern JS_PUBLIC_API bool IsIncrementalGCInProgress(JSRuntime* rt); + +/** + * Returns true if the most recent GC ran incrementally. + */ +extern JS_PUBLIC_API bool WasIncrementalGC(JSRuntime* rt); + +/* + * Generational GC: + */ + +/** + * Ensure that generational GC is disabled within some scope. + * + * This evicts the nursery and discards JIT code so it is not a lightweight + * operation. + */ +class JS_PUBLIC_API AutoDisableGenerationalGC { + JSContext* cx; + + public: + explicit AutoDisableGenerationalGC(JSContext* cx); + ~AutoDisableGenerationalGC(); +}; + +/** + * Returns true if generational allocation and collection is currently enabled + * on the given runtime. + */ +extern JS_PUBLIC_API bool IsGenerationalGCEnabled(JSRuntime* rt); + +/** + * Enable or disable support for pretenuring allocations based on their + * allocation site. + */ +extern JS_PUBLIC_API void SetSiteBasedPretenuringEnabled(bool enable); + +/** + * Pass a subclass of this "abstract" class to callees to require that they + * never GC. Subclasses can use assertions or the hazard analysis to ensure no + * GC happens. + */ +class JS_PUBLIC_API AutoRequireNoGC { + protected: + AutoRequireNoGC() = default; + ~AutoRequireNoGC() = default; +}; + +/** + * Diagnostic assert (see MOZ_DIAGNOSTIC_ASSERT) that GC cannot occur while this + * class is live. This class does not disable the static rooting hazard + * analysis. + * + * This works by entering a GC unsafe region, which is checked on allocation and + * on GC. + */ +class JS_PUBLIC_API AutoAssertNoGC : public AutoRequireNoGC { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + protected: + JSContext* cx_; + + public: + // This gets the context from TLS if it is not passed in. + explicit AutoAssertNoGC(JSContext* cx = nullptr); + ~AutoAssertNoGC(); +#else + public: + explicit AutoAssertNoGC(JSContext* cx = nullptr) {} + ~AutoAssertNoGC() {} +#endif +}; + +/** + * Disable the static rooting hazard analysis in the live region and assert in + * debug builds if any allocation that could potentially trigger a GC occurs + * while this guard object is live. This is most useful to help the exact + * rooting hazard analysis in complex regions, since it cannot understand + * dataflow. + * + * Note: GC behavior is unpredictable even when deterministic and is generally + * non-deterministic in practice. The fact that this guard has not + * asserted is not a guarantee that a GC cannot happen in the guarded + * region. As a rule, anyone performing a GC unsafe action should + * understand the GC properties of all code in that region and ensure + * that the hazard analysis is correct for that code, rather than relying + * on this class. + */ +#ifdef DEBUG +class JS_PUBLIC_API AutoSuppressGCAnalysis : public AutoAssertNoGC { + public: + explicit AutoSuppressGCAnalysis(JSContext* cx = nullptr) + : AutoAssertNoGC(cx) {} +} JS_HAZ_GC_SUPPRESSED; +#else +class JS_PUBLIC_API AutoSuppressGCAnalysis : public AutoRequireNoGC { + public: + explicit AutoSuppressGCAnalysis(JSContext* cx = nullptr) {} +} JS_HAZ_GC_SUPPRESSED; +#endif + +/** + * Assert that code is only ever called from a GC callback, disable the static + * rooting hazard analysis and assert if any allocation that could potentially + * trigger a GC occurs while this guard object is live. + * + * This is useful to make the static analysis ignore code that runs in GC + * callbacks. + */ +class JS_PUBLIC_API AutoAssertGCCallback : public AutoSuppressGCAnalysis { + public: +#ifdef DEBUG + AutoAssertGCCallback(); +#else + AutoAssertGCCallback() {} +#endif +}; + +/** + * Place AutoCheckCannotGC in scopes that you believe can never GC. These + * annotations will be verified both dynamically via AutoAssertNoGC, and + * statically with the rooting hazard analysis (implemented by making the + * analysis consider AutoCheckCannotGC to be a GC pointer, and therefore + * complain if it is live across a GC call.) It is useful when dealing with + * internal pointers to GC things where the GC thing itself may not be present + * for the static analysis: e.g. acquiring inline chars from a JSString* on the + * heap. + * + * We only do the assertion checking in DEBUG builds. + */ +#ifdef DEBUG +class JS_PUBLIC_API AutoCheckCannotGC : public AutoAssertNoGC { + public: + explicit AutoCheckCannotGC(JSContext* cx = nullptr) : AutoAssertNoGC(cx) {} +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + AutoCheckCannotGC(const AutoCheckCannotGC& other) + : AutoCheckCannotGC(other.cx_) {} +# else + AutoCheckCannotGC(const AutoCheckCannotGC& other) : AutoCheckCannotGC() {} +# endif +#else +class JS_PUBLIC_API AutoCheckCannotGC : public AutoRequireNoGC { + public: + explicit AutoCheckCannotGC(JSContext* cx = nullptr) {} + AutoCheckCannotGC(const AutoCheckCannotGC& other) : AutoCheckCannotGC() {} +#endif +} JS_HAZ_GC_INVALIDATED JS_HAZ_GC_REF; + +extern JS_PUBLIC_API void SetLowMemoryState(JSContext* cx, bool newState); + +/* + * Internal to Firefox. + */ +extern JS_PUBLIC_API void NotifyGCRootsRemoved(JSContext* cx); + +} /* namespace JS */ + +typedef void (*JSGCCallback)(JSContext* cx, JSGCStatus status, + JS::GCReason reason, void* data); + +/** + * Register externally maintained GC roots. + * + * traceOp: the trace operation. For each root the implementation should call + * JS::TraceEdge whenever the root contains a traceable thing. + * data: the data argument to pass to each invocation of traceOp. + */ +extern JS_PUBLIC_API bool JS_AddExtraGCRootsTracer(JSContext* cx, + JSTraceDataOp traceOp, + void* data); + +/** Undo a call to JS_AddExtraGCRootsTracer. */ +extern JS_PUBLIC_API void JS_RemoveExtraGCRootsTracer(JSContext* cx, + JSTraceDataOp traceOp, + void* data); + +extern JS_PUBLIC_API void JS_GC(JSContext* cx, + JS::GCReason reason = JS::GCReason::API); + +extern JS_PUBLIC_API void JS_MaybeGC(JSContext* cx); + +extern JS_PUBLIC_API void JS_SetGCCallback(JSContext* cx, JSGCCallback cb, + void* data); + +extern JS_PUBLIC_API void JS_SetObjectsTenuredCallback( + JSContext* cx, JSObjectsTenuredCallback cb, void* data); + +extern JS_PUBLIC_API bool JS_AddFinalizeCallback(JSContext* cx, + JSFinalizeCallback cb, + void* data); + +extern JS_PUBLIC_API void JS_RemoveFinalizeCallback(JSContext* cx, + JSFinalizeCallback cb); + +/* + * Weak pointers and garbage collection + * + * Weak pointers are by their nature not marked as part of garbage collection, + * but they may need to be updated in two cases after a GC: + * + * 1) Their referent was found not to be live and is about to be finalized + * 2) Their referent has been moved by a compacting GC + * + * To handle this, any part of the system that maintain weak pointers to + * JavaScript GC things must register a callback with + * JS_(Add,Remove)WeakPointer{ZoneGroup,Compartment}Callback(). This callback + * must then call JS_UpdateWeakPointerAfterGC() on all weak pointers it knows + * about. + * + * Since sweeping is incremental, we have several callbacks to avoid repeatedly + * having to visit all embedder structures. The WeakPointerZonesCallback is + * called once for each strongly connected group of zones, whereas the + * WeakPointerCompartmentCallback is called once for each compartment that is + * visited while sweeping. Structures that cannot contain references in more + * than one compartment should sweep the relevant per-compartment structures + * using the latter callback to minimizer per-slice overhead. + * + * The argument to JS_UpdateWeakPointerAfterGC() is an in-out param. If the + * referent is about to be finalized the pointer will be set to null. If the + * referent has been moved then the pointer will be updated to point to the new + * location. + * + * The return value of JS_UpdateWeakPointerAfterGC() indicates whether the + * referent is still alive. If the referent is is about to be finalized, this + * will return false. + * + * Callers of this method are responsible for updating any state that is + * dependent on the object's address. For example, if the object's address is + * used as a key in a hashtable, then the object must be removed and + * re-inserted with the correct hash. + */ + +extern JS_PUBLIC_API bool JS_AddWeakPointerZonesCallback( + JSContext* cx, JSWeakPointerZonesCallback cb, void* data); + +extern JS_PUBLIC_API void JS_RemoveWeakPointerZonesCallback( + JSContext* cx, JSWeakPointerZonesCallback cb); + +extern JS_PUBLIC_API bool JS_AddWeakPointerCompartmentCallback( + JSContext* cx, JSWeakPointerCompartmentCallback cb, void* data); + +extern JS_PUBLIC_API void JS_RemoveWeakPointerCompartmentCallback( + JSContext* cx, JSWeakPointerCompartmentCallback cb); + +namespace JS { +template <typename T> +class Heap; +} + +extern JS_PUBLIC_API bool JS_UpdateWeakPointerAfterGC( + JSTracer* trc, JS::Heap<JSObject*>* objp); + +extern JS_PUBLIC_API bool JS_UpdateWeakPointerAfterGCUnbarriered( + JSTracer* trc, JSObject** objp); + +extern JS_PUBLIC_API void JS_SetGCParameter(JSContext* cx, JSGCParamKey key, + uint32_t value); + +extern JS_PUBLIC_API void JS_ResetGCParameter(JSContext* cx, JSGCParamKey key); + +extern JS_PUBLIC_API uint32_t JS_GetGCParameter(JSContext* cx, + JSGCParamKey key); + +extern JS_PUBLIC_API void JS_SetGCParametersBasedOnAvailableMemory( + JSContext* cx, uint32_t availMemMB); + +/** + * Create a new JSString whose chars member refers to external memory, i.e., + * memory requiring application-specific finalization. + */ +extern JS_PUBLIC_API JSString* JS_NewExternalString( + JSContext* cx, const char16_t* chars, size_t length, + const JSExternalStringCallbacks* callbacks); + +/** + * Create a new JSString whose chars member may refer to external memory. + * If a new external string is allocated, |*allocatedExternal| is set to true. + * Otherwise the returned string is either not an external string or an + * external string allocated by a previous call and |*allocatedExternal| is set + * to false. If |*allocatedExternal| is false, |fin| won't be called. + */ +extern JS_PUBLIC_API JSString* JS_NewMaybeExternalString( + JSContext* cx, const char16_t* chars, size_t length, + const JSExternalStringCallbacks* callbacks, bool* allocatedExternal); + +/** + * Return the 'callbacks' arg passed to JS_NewExternalString or + * JS_NewMaybeExternalString. + */ +extern JS_PUBLIC_API const JSExternalStringCallbacks* +JS_GetExternalStringCallbacks(JSString* str); + +namespace JS { + +extern JS_PUBLIC_API GCReason WantEagerMinorGC(JSRuntime* rt); + +extern JS_PUBLIC_API GCReason WantEagerMajorGC(JSRuntime* rt); + +extern JS_PUBLIC_API void MaybeRunNurseryCollection(JSRuntime* rt, + JS::GCReason reason); + +extern JS_PUBLIC_API void SetHostCleanupFinalizationRegistryCallback( + JSContext* cx, JSHostCleanupFinalizationRegistryCallback cb, void* data); + +/** + * Clear kept alive objects in JS WeakRef. + * https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects + */ +extern JS_PUBLIC_API void ClearKeptObjects(JSContext* cx); + +inline JS_PUBLIC_API bool NeedGrayRootsForZone(Zone* zoneArg) { + shadow::Zone* zone = shadow::Zone::from(zoneArg); + return zone->isGCMarkingBlackAndGray() || zone->isGCCompacting(); +} + +extern JS_PUBLIC_API bool AtomsZoneIsCollecting(JSRuntime* runtime); +extern JS_PUBLIC_API bool IsAtomsZone(Zone* zone); + +} // namespace JS + +namespace js { +namespace gc { + +/** + * Create an object providing access to the garbage collector's internal notion + * of the current state of memory (both GC heap memory and GCthing-controlled + * malloc memory. + */ +extern JS_PUBLIC_API JSObject* NewMemoryInfoObject(JSContext* cx); + +/* + * Run the finalizer of a nursery-allocated JSObject that is known to be dead. + * + * This is a dangerous operation - only use this if you know what you're doing! + * + * This is used by the browser to implement nursery-allocated wrapper cached + * wrappers. + */ +extern JS_PUBLIC_API void FinalizeDeadNurseryObject(JSContext* cx, + JSObject* obj); + +} /* namespace gc */ +} /* namespace js */ + +#ifdef JS_GC_ZEAL + +# define JS_DEFAULT_ZEAL_FREQ 100 + +extern JS_PUBLIC_API void JS_GetGCZealBits(JSContext* cx, uint32_t* zealBits, + uint32_t* frequency, + uint32_t* nextScheduled); + +extern JS_PUBLIC_API void JS_SetGCZeal(JSContext* cx, uint8_t zeal, + uint32_t frequency); + +extern JS_PUBLIC_API void JS_UnsetGCZeal(JSContext* cx, uint8_t zeal); + +extern JS_PUBLIC_API void JS_ScheduleGC(JSContext* cx, uint32_t count); + +#endif + +#endif /* js_GCAPI_h */ diff --git a/js/public/GCAnnotations.h b/js/public/GCAnnotations.h new file mode 100644 index 0000000000..b952bae3ff --- /dev/null +++ b/js/public/GCAnnotations.h @@ -0,0 +1,116 @@ +/* -*- 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 js_GCAnnotations_h +#define js_GCAnnotations_h + +// Set of annotations for the rooting hazard analysis, used to categorize types +// and functions. +#ifdef XGILL_PLUGIN + +# define JS_EXPECT_HAZARDS __attribute__((annotate("Expect Hazards"))) + +// Mark a type as being a GC thing (eg js::gc::Cell has this annotation). +# define JS_HAZ_GC_THING __attribute__((annotate("GC Thing"))) + +// Mark a type as holding a pointer to a GC thing (eg JS::Value has this +// annotation.) "Inherited" by templatized types with +// MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS. +# define JS_HAZ_GC_POINTER __attribute__((annotate("GC Pointer"))) + +// Same as JS_HAZ_GC_POINTER, except additionally treat pointers to these +// as GC pointers themselves in order to check references to them, since +// the analysis cannot distinguish between pointers and references. +# define JS_HAZ_GC_REF __attribute__((annotate("GC Pointer or Reference"))) + +// Mark a type as a rooted pointer, suitable for use on the stack (eg all +// Rooted<T> instantiations should have this.) "Inherited" by templatized types +// with MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS. +# define JS_HAZ_ROOTED __attribute__((annotate("Rooted Pointer"))) + +// Mark a type as something that should not be held live across a GC, but which +// is not itself a GC pointer. Note that this property is *not* inherited by +// templatized types with MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS. +# define JS_HAZ_GC_INVALIDATED __attribute__((annotate("Invalidated by GC"))) + +// Mark a class as a base class of rooted types, eg CustomAutoRooter. All +// descendants of this class will be considered rooted, though classes that +// merely contain these as a field member will not be. "Inherited" by +// templatized types with MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS +# define JS_HAZ_ROOTED_BASE __attribute__((annotate("Rooted Base"))) + +// Mark a type that would otherwise be considered a GC Pointer (eg because it +// contains a JS::Value field) as a non-GC pointer. It is handled almost the +// same in the analysis as a rooted pointer, except it will not be reported as +// an unnecessary root if used across a GC call. This should rarely be used, +// but makes sense for something like ErrorResult, which only contains a GC +// pointer when it holds an exception (and it does its own rooting, +// conditionally.) +# define JS_HAZ_NON_GC_POINTER \ + __attribute__((annotate("Suppressed GC Pointer"))) + +// Mark a function as something that runs a garbage collection, potentially +// invalidating GC pointers. +# define JS_HAZ_GC_CALL __attribute__((annotate("GC Call"))) + +// Mark an RAII class as suppressing GC within its scope. +# define JS_HAZ_GC_SUPPRESSED __attribute__((annotate("Suppress GC"))) + +// Mark a function as one that can run script if called. This obviously +// subsumes JS_HAZ_GC_CALL, since anything that can run script can GC.` +# define JS_HAZ_CAN_RUN_SCRIPT __attribute__((annotate("Can run script"))) + +// Mark a function as able to call JSNatives. Otherwise, JSNatives don't show +// up in the callgraph. This doesn't matter for the can-GC analysis, but it is +// very nice for other uses of the callgraph. +# define JS_HAZ_JSNATIVE_CALLER __attribute__((annotate("Calls JSNatives"))) + +// Mark a variable as being "GC safe", i.e., it does not contain any +// invalidatable pointers at the current point in the code. A typical +// example might be a collection containing GC pointers, which at the +// present time is empty. This property is only temporary; the next use +// of the variable will invalidate it (on the assumption that a GC pointer +// might be added to it.) Try to use this as early as possible, probably +// immediately after construction, so that if future mutations through +// the variable are added, they won't be covered by the annotation. +# define JS_HAZ_VALUE_IS_GC_SAFE(var) JS::detail::MarkVariableAsGCSafe(var) + +#else + +# define JS_EXPECT_HAZARDS +# define JS_HAZ_GC_THING +# define JS_HAZ_GC_POINTER +# define JS_HAZ_GC_REF +# define JS_HAZ_ROOTED +# define JS_HAZ_GC_INVALIDATED +# define JS_HAZ_ROOTED_BASE +# define JS_HAZ_NON_GC_POINTER +# define JS_HAZ_GC_CALL +# define JS_HAZ_GC_SUPPRESSED +# define JS_HAZ_CAN_RUN_SCRIPT +# define JS_HAZ_JSNATIVE_CALLER +# define JS_HAZ_VALUE_IS_GC_SAFE(var) + +#endif + +#ifdef XGILL_PLUGIN + +// Implemented by passing variable to a dummy function so that it shows up +// in the control flow graph. +namespace JS { +namespace detail { + +template <typename T> +static inline void MarkVariableAsGCSafe(T& var) { + asm(""); +} + +} // namespace detail +} // namespace JS + +#endif + +#endif /* js_GCAnnotations_h */ diff --git a/js/public/GCHashTable.h b/js/public/GCHashTable.h new file mode 100644 index 0000000000..0319779d8f --- /dev/null +++ b/js/public/GCHashTable.h @@ -0,0 +1,785 @@ +/* -*- 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 GCHashTable_h +#define GCHashTable_h + +#include "mozilla/Maybe.h" + +#include "js/GCPolicyAPI.h" +#include "js/HashTable.h" +#include "js/RootingAPI.h" +#include "js/SweepingAPI.h" +#include "js/TypeDecls.h" + +class JSTracer; + +namespace JS { + +// Define a reasonable default GC policy for GC-aware Maps. +template <typename Key, typename Value> +struct DefaultMapEntryGCPolicy { + static bool traceWeak(JSTracer* trc, Key* key, Value* value) { + return GCPolicy<Key>::traceWeak(trc, key) && + GCPolicy<Value>::traceWeak(trc, value); + } +}; + +// A GCHashMap is a GC-aware HashMap, meaning that it has additional trace +// methods that know how to visit all keys and values in the table. HashMaps +// that contain GC pointers will generally want to use this GCHashMap +// specialization instead of HashMap, because this conveniently supports tracing +// keys and values, and cleaning up weak entries. +// +// GCHashMap::trace applies GCPolicy<T>::trace to each entry's key and value. +// Most types of GC pointers already have appropriate specializations of +// GCPolicy, so they should just work as keys and values. Any struct type with a +// default constructor and trace function should work as well. If you need to +// define your own GCPolicy specialization, generic helpers can be found in +// js/public/TracingAPI.h. +// +// The MapEntryGCPolicy template parameter controls how the table drops entries +// when edges are weakly held. GCHashMap::traceWeak applies the +// MapEntryGCPolicy's traceWeak method to each table entry; if it returns true, +// the entry is dropped. The default MapEntryGCPolicy drops the entry if either +// the key or value is about to be finalized, according to its +// GCPolicy<T>::traceWeak method. (This default is almost always fine: it's hard +// to imagine keeping such an entry around anyway.) +// +// Note that this HashMap only knows *how* to trace, but it does not itself +// cause tracing to be invoked. For tracing, it must be used as +// Rooted<GCHashMap> or PersistentRooted<GCHashMap>, or barriered and traced +// manually. +template <typename Key, typename Value, + typename HashPolicy = js::DefaultHasher<Key>, + typename AllocPolicy = js::TempAllocPolicy, + typename MapEntryGCPolicy = DefaultMapEntryGCPolicy<Key, Value>> +class GCHashMap : public js::HashMap<Key, Value, HashPolicy, AllocPolicy> { + using Base = js::HashMap<Key, Value, HashPolicy, AllocPolicy>; + + public: + using EntryGCPolicy = MapEntryGCPolicy; + + explicit GCHashMap(AllocPolicy a = AllocPolicy()) : Base(std::move(a)) {} + explicit GCHashMap(size_t length) : Base(length) {} + GCHashMap(AllocPolicy a, size_t length) : Base(std::move(a), length) {} + + void trace(JSTracer* trc) { + for (typename Base::Enum e(*this); !e.empty(); e.popFront()) { + GCPolicy<Value>::trace(trc, &e.front().value(), "hashmap value"); + GCPolicy<Key>::trace(trc, &e.front().mutableKey(), "hashmap key"); + } + } + + bool traceWeak(JSTracer* trc) { + typename Base::Enum e(*this); + traceWeakEntries(trc, e); + return !this->empty(); + } + + void traceWeakEntries(JSTracer* trc, typename Base::Enum& e) { + for (typename Base::Enum e(*this); !e.empty(); e.popFront()) { + if (!MapEntryGCPolicy::traceWeak(trc, &e.front().mutableKey(), + &e.front().value())) { + e.removeFront(); + } + } + } + + // GCHashMap is movable + GCHashMap(GCHashMap&& rhs) : Base(std::move(rhs)) {} + void operator=(GCHashMap&& rhs) { + MOZ_ASSERT(this != &rhs, "self-move assignment is prohibited"); + Base::operator=(std::move(rhs)); + } + + private: + // GCHashMap is not copyable or assignable + GCHashMap(const GCHashMap& hm) = delete; + GCHashMap& operator=(const GCHashMap& hm) = delete; +} MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS; + +} // namespace JS + +namespace js { + +// HashMap that supports rekeying. +// +// If your keys are pointers to something like JSObject that can be tenured or +// compacted, prefer to use GCHashMap with StableCellHasher, which takes +// advantage of the Zone's stable id table to make rekeying unnecessary. +template <typename Key, typename Value, + typename HashPolicy = DefaultHasher<Key>, + typename AllocPolicy = TempAllocPolicy, + typename MapEntryGCPolicy = JS::DefaultMapEntryGCPolicy<Key, Value>> +class GCRekeyableHashMap : public JS::GCHashMap<Key, Value, HashPolicy, + AllocPolicy, MapEntryGCPolicy> { + using Base = JS::GCHashMap<Key, Value, HashPolicy, AllocPolicy>; + + public: + explicit GCRekeyableHashMap(AllocPolicy a = AllocPolicy()) + : Base(std::move(a)) {} + explicit GCRekeyableHashMap(size_t length) : Base(length) {} + GCRekeyableHashMap(AllocPolicy a, size_t length) + : Base(std::move(a), length) {} + + bool traceWeak(JSTracer* trc) { + for (typename Base::Enum e(*this); !e.empty(); e.popFront()) { + Key key(e.front().key()); + if (!MapEntryGCPolicy::traceWeak(trc, &key, &e.front().value())) { + e.removeFront(); + } else if (!HashPolicy::match(key, e.front().key())) { + e.rekeyFront(key); + } + } + return !this->empty(); + } + + // GCRekeyableHashMap is movable + GCRekeyableHashMap(GCRekeyableHashMap&& rhs) : Base(std::move(rhs)) {} + void operator=(GCRekeyableHashMap&& rhs) { + MOZ_ASSERT(this != &rhs, "self-move assignment is prohibited"); + Base::operator=(std::move(rhs)); + } +} MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS; + +template <typename Wrapper, typename... Args> +class WrappedPtrOperations<JS::GCHashMap<Args...>, Wrapper> { + using Map = JS::GCHashMap<Args...>; + using Lookup = typename Map::Lookup; + + const Map& map() const { return static_cast<const Wrapper*>(this)->get(); } + + public: + using AddPtr = typename Map::AddPtr; + using Ptr = typename Map::Ptr; + using Range = typename Map::Range; + + Ptr lookup(const Lookup& l) const { return map().lookup(l); } + Range all() const { return map().all(); } + bool empty() const { return map().empty(); } + uint32_t count() const { return map().count(); } + size_t capacity() const { return map().capacity(); } + bool has(const Lookup& l) const { return map().lookup(l).found(); } + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return map().sizeOfExcludingThis(mallocSizeOf); + } + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + map().sizeOfExcludingThis(mallocSizeOf); + } +}; + +template <typename Wrapper, typename... Args> +class MutableWrappedPtrOperations<JS::GCHashMap<Args...>, Wrapper> + : public WrappedPtrOperations<JS::GCHashMap<Args...>, Wrapper> { + using Map = JS::GCHashMap<Args...>; + using Lookup = typename Map::Lookup; + + Map& map() { return static_cast<Wrapper*>(this)->get(); } + + public: + using AddPtr = typename Map::AddPtr; + struct Enum : public Map::Enum { + explicit Enum(Wrapper& o) : Map::Enum(o.map()) {} + }; + using Ptr = typename Map::Ptr; + using Range = typename Map::Range; + + void clear() { map().clear(); } + void clearAndCompact() { map().clearAndCompact(); } + void remove(Ptr p) { map().remove(p); } + AddPtr lookupForAdd(const Lookup& l) { return map().lookupForAdd(l); } + + template <typename KeyInput, typename ValueInput> + bool add(AddPtr& p, KeyInput&& k, ValueInput&& v) { + return map().add(p, std::forward<KeyInput>(k), std::forward<ValueInput>(v)); + } + + template <typename KeyInput> + bool add(AddPtr& p, KeyInput&& k) { + return map().add(p, std::forward<KeyInput>(k), Map::Value()); + } + + template <typename KeyInput, typename ValueInput> + bool relookupOrAdd(AddPtr& p, KeyInput&& k, ValueInput&& v) { + return map().relookupOrAdd(p, k, std::forward<KeyInput>(k), + std::forward<ValueInput>(v)); + } + + template <typename KeyInput, typename ValueInput> + bool put(KeyInput&& k, ValueInput&& v) { + return map().put(std::forward<KeyInput>(k), std::forward<ValueInput>(v)); + } + + template <typename KeyInput, typename ValueInput> + bool putNew(KeyInput&& k, ValueInput&& v) { + return map().putNew(std::forward<KeyInput>(k), std::forward<ValueInput>(v)); + } +}; + +} // namespace js + +namespace JS { + +// A GCHashSet is a HashSet with an additional trace method that knows +// be traced to be kept alive will generally want to use this GCHashSet +// specialization in lieu of HashSet. +// +// Most types of GC pointers can be traced with no extra infrastructure. For +// structs and non-gc-pointer members, ensure that there is a specialization of +// GCPolicy<T> with an appropriate trace method available to handle the custom +// type. Generic helpers can be found in js/public/TracingAPI.h. +// +// Note that although this HashSet's trace will deal correctly with moved +// elements, it does not itself know when to barrier or trace elements. To +// function properly it must either be used with Rooted or barriered and traced +// manually. +template <typename T, typename HashPolicy = js::DefaultHasher<T>, + typename AllocPolicy = js::TempAllocPolicy> +class GCHashSet : public js::HashSet<T, HashPolicy, AllocPolicy> { + using Base = js::HashSet<T, HashPolicy, AllocPolicy>; + + public: + explicit GCHashSet(AllocPolicy a = AllocPolicy()) : Base(std::move(a)) {} + explicit GCHashSet(size_t length) : Base(length) {} + GCHashSet(AllocPolicy a, size_t length) : Base(std::move(a), length) {} + + void trace(JSTracer* trc) { + for (typename Base::Enum e(*this); !e.empty(); e.popFront()) { + GCPolicy<T>::trace(trc, &e.mutableFront(), "hashset element"); + } + } + + bool traceWeak(JSTracer* trc) { + typename Base::Enum e(*this); + traceWeakEntries(trc, e); + return !this->empty(); + } + + void traceWeakEntries(JSTracer* trc, typename Base::Enum& e) { + for (; !e.empty(); e.popFront()) { + if (!GCPolicy<T>::traceWeak(trc, &e.mutableFront())) { + e.removeFront(); + } + } + } + + // GCHashSet is movable + GCHashSet(GCHashSet&& rhs) : Base(std::move(rhs)) {} + void operator=(GCHashSet&& rhs) { + MOZ_ASSERT(this != &rhs, "self-move assignment is prohibited"); + Base::operator=(std::move(rhs)); + } + + private: + // GCHashSet is not copyable or assignable + GCHashSet(const GCHashSet& hs) = delete; + GCHashSet& operator=(const GCHashSet& hs) = delete; +} MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS; + +} // namespace JS + +namespace js { + +template <typename Wrapper, typename... Args> +class WrappedPtrOperations<JS::GCHashSet<Args...>, Wrapper> { + using Set = JS::GCHashSet<Args...>; + + const Set& set() const { return static_cast<const Wrapper*>(this)->get(); } + + public: + using Lookup = typename Set::Lookup; + using AddPtr = typename Set::AddPtr; + using Entry = typename Set::Entry; + using Ptr = typename Set::Ptr; + using Range = typename Set::Range; + + Ptr lookup(const Lookup& l) const { return set().lookup(l); } + Range all() const { return set().all(); } + bool empty() const { return set().empty(); } + uint32_t count() const { return set().count(); } + size_t capacity() const { return set().capacity(); } + bool has(const Lookup& l) const { return set().lookup(l).found(); } + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return set().sizeOfExcludingThis(mallocSizeOf); + } + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + set().sizeOfExcludingThis(mallocSizeOf); + } +}; + +template <typename Wrapper, typename... Args> +class MutableWrappedPtrOperations<JS::GCHashSet<Args...>, Wrapper> + : public WrappedPtrOperations<JS::GCHashSet<Args...>, Wrapper> { + using Set = JS::GCHashSet<Args...>; + using Lookup = typename Set::Lookup; + + Set& set() { return static_cast<Wrapper*>(this)->get(); } + + public: + using AddPtr = typename Set::AddPtr; + using Entry = typename Set::Entry; + struct Enum : public Set::Enum { + explicit Enum(Wrapper& o) : Set::Enum(o.set()) {} + }; + using Ptr = typename Set::Ptr; + using Range = typename Set::Range; + + void clear() { set().clear(); } + void clearAndCompact() { set().clearAndCompact(); } + [[nodiscard]] bool reserve(uint32_t len) { return set().reserve(len); } + void remove(Ptr p) { set().remove(p); } + void remove(const Lookup& l) { set().remove(l); } + AddPtr lookupForAdd(const Lookup& l) { return set().lookupForAdd(l); } + + template <typename TInput> + void replaceKey(Ptr p, const Lookup& l, TInput&& newValue) { + set().replaceKey(p, l, std::forward<TInput>(newValue)); + } + + template <typename TInput> + bool add(AddPtr& p, TInput&& t) { + return set().add(p, std::forward<TInput>(t)); + } + + template <typename TInput> + bool relookupOrAdd(AddPtr& p, const Lookup& l, TInput&& t) { + return set().relookupOrAdd(p, l, std::forward<TInput>(t)); + } + + template <typename TInput> + bool put(TInput&& t) { + return set().put(std::forward<TInput>(t)); + } + + template <typename TInput> + bool putNew(TInput&& t) { + return set().putNew(std::forward<TInput>(t)); + } + + template <typename TInput> + bool putNew(const Lookup& l, TInput&& t) { + return set().putNew(l, std::forward<TInput>(t)); + } +}; + +} /* namespace js */ + +namespace JS { + +// Specialize WeakCache for GCHashMap to provide a barriered map that does not +// need to be swept immediately. +template <typename Key, typename Value, typename HashPolicy, + typename AllocPolicy, typename MapEntryGCPolicy> +class WeakCache< + GCHashMap<Key, Value, HashPolicy, AllocPolicy, MapEntryGCPolicy>> + final : protected detail::WeakCacheBase { + using Map = GCHashMap<Key, Value, HashPolicy, AllocPolicy, MapEntryGCPolicy>; + using Self = WeakCache<Map>; + + Map map; + JSTracer* barrierTracer = nullptr; + + public: + template <typename... Args> + explicit WeakCache(Zone* zone, Args&&... args) + : WeakCacheBase(zone), map(std::forward<Args>(args)...) {} + template <typename... Args> + explicit WeakCache(JSRuntime* rt, Args&&... args) + : WeakCacheBase(rt), map(std::forward<Args>(args)...) {} + ~WeakCache() { MOZ_ASSERT(!barrierTracer); } + + bool empty() override { return map.empty(); } + + size_t traceWeak(JSTracer* trc, js::gc::StoreBuffer* sbToLock) override { + size_t steps = map.count(); + + // Create an Enum and sweep the table entries. + mozilla::Maybe<typename Map::Enum> e; + e.emplace(map); + map.traceWeakEntries(trc, e.ref()); + + // Potentially take a lock while the Enum's destructor is called as this can + // rehash/resize the table and access the store buffer. + mozilla::Maybe<js::gc::AutoLockStoreBuffer> lock; + if (sbToLock) { + lock.emplace(sbToLock); + } + e.reset(); + + return steps; + } + + bool setIncrementalBarrierTracer(JSTracer* trc) override { + MOZ_ASSERT(bool(barrierTracer) != bool(trc)); + barrierTracer = trc; + return true; + } + + bool needsIncrementalBarrier() const override { return barrierTracer; } + + private: + using Entry = typename Map::Entry; + + static bool entryNeedsSweep(JSTracer* barrierTracer, const Entry& prior) { + Key key(prior.key()); + Value value(prior.value()); + bool needsSweep = !MapEntryGCPolicy::traceWeak(barrierTracer, &key, &value); + MOZ_ASSERT_IF(!needsSweep, + prior.key() == key); // We shouldn't update here. + MOZ_ASSERT_IF(!needsSweep, + prior.value() == value); // We shouldn't update here. + return needsSweep; + } + + public: + using Lookup = typename Map::Lookup; + using Ptr = typename Map::Ptr; + using AddPtr = typename Map::AddPtr; + + // Iterator over the whole collection. + struct Range { + explicit Range(Self& self) : cache(self), range(self.map.all()) { + settle(); + } + Range() = default; + + bool empty() const { return range.empty(); } + const Entry& front() const { return range.front(); } + + void popFront() { + range.popFront(); + settle(); + } + + private: + Self& cache; + typename Map::Range range; + + void settle() { + if (JSTracer* trc = cache.barrierTracer) { + while (!empty() && entryNeedsSweep(trc, front())) { + popFront(); + } + } + } + }; + + struct Enum : public Map::Enum { + explicit Enum(Self& cache) : Map::Enum(cache.map) { + // This operation is not allowed while barriers are in place as we + // may also need to enumerate the set for sweeping. + MOZ_ASSERT(!cache.barrierTracer); + } + }; + + Ptr lookup(const Lookup& l) const { + Ptr ptr = map.lookup(l); + if (barrierTracer && ptr && entryNeedsSweep(barrierTracer, *ptr)) { + const_cast<Map&>(map).remove(ptr); + return Ptr(); + } + return ptr; + } + + AddPtr lookupForAdd(const Lookup& l) { + AddPtr ptr = map.lookupForAdd(l); + if (barrierTracer && ptr && entryNeedsSweep(barrierTracer, *ptr)) { + const_cast<Map&>(map).remove(ptr); + return map.lookupForAdd(l); + } + return ptr; + } + + Range all() const { return Range(*const_cast<Self*>(this)); } + + bool empty() const { + // This operation is not currently allowed while barriers are in place + // as it would require iterating the map and the caller expects a + // constant time operation. + MOZ_ASSERT(!barrierTracer); + return map.empty(); + } + + uint32_t count() const { + // This operation is not currently allowed while barriers are in place + // as it would require iterating the set and the caller expects a + // constant time operation. + MOZ_ASSERT(!barrierTracer); + return map.count(); + } + + size_t capacity() const { return map.capacity(); } + + bool has(const Lookup& l) const { return lookup(l).found(); } + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return map.sizeOfExcludingThis(mallocSizeOf); + } + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + map.shallowSizeOfExcludingThis(mallocSizeOf); + } + + void clear() { + // This operation is not currently allowed while barriers are in place + // since it doesn't make sense to clear a cache while it is being swept. + MOZ_ASSERT(!barrierTracer); + map.clear(); + } + + void clearAndCompact() { + // This operation is not currently allowed while barriers are in place + // since it doesn't make sense to clear a cache while it is being swept. + MOZ_ASSERT(!barrierTracer); + map.clearAndCompact(); + } + + void remove(Ptr p) { + // This currently supports removing entries during incremental + // sweeping. If we allow these tables to be swept incrementally this may + // no longer be possible. + map.remove(p); + } + + void remove(const Lookup& l) { + Ptr p = lookup(l); + if (p) { + remove(p); + } + } + + template <typename KeyInput, typename ValueInput> + bool add(AddPtr& p, KeyInput&& k, ValueInput&& v) { + return map.add(p, std::forward<KeyInput>(k), std::forward<ValueInput>(v)); + } + + template <typename KeyInput, typename ValueInput> + bool relookupOrAdd(AddPtr& p, KeyInput&& k, ValueInput&& v) { + return map.relookupOrAdd(p, std::forward<KeyInput>(k), + std::forward<ValueInput>(v)); + } + + template <typename KeyInput, typename ValueInput> + bool put(KeyInput&& k, ValueInput&& v) { + return map.put(std::forward<KeyInput>(k), std::forward<ValueInput>(v)); + } + + template <typename KeyInput, typename ValueInput> + bool putNew(KeyInput&& k, ValueInput&& v) { + return map.putNew(std::forward<KeyInput>(k), std::forward<ValueInput>(v)); + } +} JS_HAZ_NON_GC_POINTER; + +// Specialize WeakCache for GCHashSet to provide a barriered set that does not +// need to be swept immediately. +template <typename T, typename HashPolicy, typename AllocPolicy> +class WeakCache<GCHashSet<T, HashPolicy, AllocPolicy>> final + : protected detail::WeakCacheBase { + using Set = GCHashSet<T, HashPolicy, AllocPolicy>; + using Self = WeakCache<Set>; + + Set set; + JSTracer* barrierTracer = nullptr; + + public: + using Entry = typename Set::Entry; + + template <typename... Args> + explicit WeakCache(Zone* zone, Args&&... args) + : WeakCacheBase(zone), set(std::forward<Args>(args)...) {} + template <typename... Args> + explicit WeakCache(JSRuntime* rt, Args&&... args) + : WeakCacheBase(rt), set(std::forward<Args>(args)...) {} + + size_t traceWeak(JSTracer* trc, js::gc::StoreBuffer* sbToLock) override { + size_t steps = set.count(); + + // Create an Enum and sweep the table entries. It's not necessary to take + // the store buffer lock yet. + mozilla::Maybe<typename Set::Enum> e; + e.emplace(set); + set.traceWeakEntries(trc, e.ref()); + + // Destroy the Enum, potentially rehashing or resizing the table. Since this + // can access the store buffer, we need to take a lock for this if we're + // called off main thread. + mozilla::Maybe<js::gc::AutoLockStoreBuffer> lock; + if (sbToLock) { + lock.emplace(sbToLock); + } + e.reset(); + + return steps; + } + + bool empty() override { return set.empty(); } + + bool setIncrementalBarrierTracer(JSTracer* trc) override { + MOZ_ASSERT(bool(barrierTracer) != bool(trc)); + barrierTracer = trc; + return true; + } + + bool needsIncrementalBarrier() const override { return barrierTracer; } + + private: + static bool entryNeedsSweep(JSTracer* barrierTracer, const Entry& prior) { + Entry entry(prior); + bool needsSweep = !GCPolicy<T>::traceWeak(barrierTracer, &entry); + MOZ_ASSERT_IF(!needsSweep, prior == entry); // We shouldn't update here. + return needsSweep; + } + + public: + using Lookup = typename Set::Lookup; + using Ptr = typename Set::Ptr; + using AddPtr = typename Set::AddPtr; + + // Iterator over the whole collection. + struct Range { + explicit Range(Self& self) : cache(self), range(self.set.all()) { + settle(); + } + Range() = default; + + bool empty() const { return range.empty(); } + const Entry& front() const { return range.front(); } + + void popFront() { + range.popFront(); + settle(); + } + + private: + Self& cache; + typename Set::Range range; + + void settle() { + if (JSTracer* trc = cache.barrierTracer) { + while (!empty() && entryNeedsSweep(trc, front())) { + popFront(); + } + } + } + }; + + struct Enum : public Set::Enum { + explicit Enum(Self& cache) : Set::Enum(cache.set) { + // This operation is not allowed while barriers are in place as we + // may also need to enumerate the set for sweeping. + MOZ_ASSERT(!cache.barrierTracer); + } + }; + + Ptr lookup(const Lookup& l) const { + Ptr ptr = set.lookup(l); + if (barrierTracer && ptr && entryNeedsSweep(barrierTracer, *ptr)) { + const_cast<Set&>(set).remove(ptr); + return Ptr(); + } + return ptr; + } + + AddPtr lookupForAdd(const Lookup& l) { + AddPtr ptr = set.lookupForAdd(l); + if (barrierTracer && ptr && entryNeedsSweep(barrierTracer, *ptr)) { + const_cast<Set&>(set).remove(ptr); + return set.lookupForAdd(l); + } + return ptr; + } + + Range all() const { return Range(*const_cast<Self*>(this)); } + + bool empty() const { + // This operation is not currently allowed while barriers are in place + // as it would require iterating the set and the caller expects a + // constant time operation. + MOZ_ASSERT(!barrierTracer); + return set.empty(); + } + + uint32_t count() const { + // This operation is not currently allowed while barriers are in place + // as it would require iterating the set and the caller expects a + // constant time operation. + MOZ_ASSERT(!barrierTracer); + return set.count(); + } + + size_t capacity() const { return set.capacity(); } + + bool has(const Lookup& l) const { return lookup(l).found(); } + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return set.shallowSizeOfExcludingThis(mallocSizeOf); + } + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + set.shallowSizeOfExcludingThis(mallocSizeOf); + } + + void clear() { + // This operation is not currently allowed while barriers are in place + // since it doesn't make sense to clear a cache while it is being swept. + MOZ_ASSERT(!barrierTracer); + set.clear(); + } + + void clearAndCompact() { + // This operation is not currently allowed while barriers are in place + // since it doesn't make sense to clear a cache while it is being swept. + MOZ_ASSERT(!barrierTracer); + set.clearAndCompact(); + } + + void remove(Ptr p) { + // This currently supports removing entries during incremental + // sweeping. If we allow these tables to be swept incrementally this may + // no longer be possible. + set.remove(p); + } + + void remove(const Lookup& l) { + Ptr p = lookup(l); + if (p) { + remove(p); + } + } + + template <typename TInput> + void replaceKey(Ptr p, const Lookup& l, TInput&& newValue) { + set.replaceKey(p, l, std::forward<TInput>(newValue)); + } + + template <typename TInput> + bool add(AddPtr& p, TInput&& t) { + return set.add(p, std::forward<TInput>(t)); + } + + template <typename TInput> + bool relookupOrAdd(AddPtr& p, const Lookup& l, TInput&& t) { + return set.relookupOrAdd(p, l, std::forward<TInput>(t)); + } + + template <typename TInput> + bool put(TInput&& t) { + return set.put(std::forward<TInput>(t)); + } + + template <typename TInput> + bool putNew(TInput&& t) { + return set.putNew(std::forward<TInput>(t)); + } + + template <typename TInput> + bool putNew(const Lookup& l, TInput&& t) { + return set.putNew(l, std::forward<TInput>(t)); + } +} JS_HAZ_NON_GC_POINTER; + +} // namespace JS + +#endif /* GCHashTable_h */ diff --git a/js/public/GCPolicyAPI.h b/js/public/GCPolicyAPI.h new file mode 100644 index 0000000000..eef7ee7623 --- /dev/null +++ b/js/public/GCPolicyAPI.h @@ -0,0 +1,235 @@ +/* -*- 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/. */ + +// GC Policy Mechanism + +// A GCPolicy controls how the GC interacts with both direct pointers to GC +// things (e.g. JSObject* or JSString*), tagged and/or optional pointers to GC +// things (e.g. Value or jsid), and C++ container types (e.g. +// JSPropertyDescriptor or GCHashMap). +// +// The GCPolicy provides at a minimum: +// +// static void trace(JSTracer, T* tp, const char* name) +// - Trace the edge |*tp|, calling the edge |name|. Containers like +// GCHashMap and GCHashSet use this method to trace their children. +// +// static bool traceWeak(T* tp) +// - Return false if |*tp| has been set to nullptr. Otherwise, update the +// edge for moving GC, and return true. Containers like GCHashMap and +// GCHashSet use this method to decide when to remove an entry: if this +// function returns false on a key/value/member/etc, its entry is +// dropped from the container. Specializing this method is the standard +// way to get custom weak behavior from a container type. +// +// static bool isValid(const T& t) +// - Return false only if |t| is corrupt in some way. The built-in GC +// types do some memory layout checks. For debugging only; it is ok +// to always return true or even to omit this member entirely. +// +// The default GCPolicy<T> assumes that T has a default constructor and |trace| +// and |traceWeak| methods, and forwards to them. GCPolicy has appropriate +// specializations for pointers to GC things and pointer-like types like +// JS::Heap<T> and mozilla::UniquePtr<T>. +// +// There are some stock structs your specializations can inherit from. +// IgnoreGCPolicy<T> does nothing. StructGCPolicy<T> forwards the methods to the +// referent type T. + +#ifndef GCPolicyAPI_h +#define GCPolicyAPI_h + +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" + +#include <type_traits> + +#include "js/GCTypeMacros.h" // JS_FOR_EACH_PUBLIC_GC_POINTER_TYPE +#include "js/TraceKind.h" +#include "js/TracingAPI.h" +#include "js/TypeDecls.h" + +namespace JS { + +// Defines a policy for container types with non-GC, i.e. C storage. This +// policy dispatches to the underlying struct for GC interactions. Note that +// currently a type can define only the subset of the methods (trace and/or +// traceWeak) if it is never used in a context that requires the other. +template <typename T> +struct StructGCPolicy { + static_assert(!std::is_pointer_v<T>, + "Pointer type not allowed for StructGCPolicy"); + + static void trace(JSTracer* trc, T* tp, const char* name) { tp->trace(trc); } + + static bool traceWeak(JSTracer* trc, T* tp) { return tp->traceWeak(trc); } + + static bool isValid(const T& tp) { return true; } +}; + +// The default GC policy attempts to defer to methods on the underlying type. +// Most C++ structures that contain a default constructor, a trace function and +// a sweep function will work out of the box with Rooted, Handle, GCVector, +// and GCHash{Set,Map}. +template <typename T> +struct GCPolicy : public StructGCPolicy<T> {}; + +// This policy ignores any GC interaction, e.g. for non-GC types. +template <typename T> +struct IgnoreGCPolicy { + static void trace(JSTracer* trc, T* t, const char* name) {} + static bool traceWeak(JSTracer*, T* v) { return true; } + static bool isValid(const T& v) { return true; } +}; +template <> +struct GCPolicy<uint32_t> : public IgnoreGCPolicy<uint32_t> {}; +template <> +struct GCPolicy<uint64_t> : public IgnoreGCPolicy<uint64_t> {}; +template <> +struct GCPolicy<bool> : public IgnoreGCPolicy<bool> {}; + +template <typename T> +struct GCPointerPolicy { + static_assert(std::is_pointer_v<T>, + "Non-pointer type not allowed for GCPointerPolicy"); + + static void trace(JSTracer* trc, T* vp, const char* name) { + // This should only be called as part of root marking since that's the only + // time we should trace unbarriered GC thing pointers. This will assert if + // called at other times. + TraceRoot(trc, vp, name); + } + static bool isTenured(T v) { return !v || !js::gc::IsInsideNursery(v); } + static bool isValid(T v) { return js::gc::IsCellPointerValidOrNull(v); } +}; +#define EXPAND_SPECIALIZE_GCPOLICY(Type) \ + template <> \ + struct GCPolicy<Type> : public GCPointerPolicy<Type> {}; \ + template <> \ + struct GCPolicy<Type const> : public GCPointerPolicy<Type const> {}; +JS_FOR_EACH_PUBLIC_GC_POINTER_TYPE(EXPAND_SPECIALIZE_GCPOLICY) +#undef EXPAND_SPECIALIZE_GCPOLICY + +template <typename T> +struct NonGCPointerPolicy { + static void trace(JSTracer* trc, T* vp, const char* name) { + if (*vp) { + (*vp)->trace(trc); + } + } + static bool traceWeak(JSTracer* trc, T* vp) { + if (*vp) { + return (*vp)->traceWeak(trc); + } + return true; + } + + static bool isValid(T v) { return true; } +}; + +template <typename T> +struct GCPolicy<JS::Heap<T>> { + static void trace(JSTracer* trc, JS::Heap<T>* thingp, const char* name) { + TraceEdge(trc, thingp, name); + } + static bool traceWeak(JSTracer* trc, JS::Heap<T>* thingp) { + return !*thingp || js::gc::TraceWeakEdge(trc, thingp); + } +}; + +// GCPolicy<UniquePtr<T>> forwards the contained pointer to GCPolicy<T>. +template <typename T, typename D> +struct GCPolicy<mozilla::UniquePtr<T, D>> { + static void trace(JSTracer* trc, mozilla::UniquePtr<T, D>* tp, + const char* name) { + if (tp->get()) { + GCPolicy<T>::trace(trc, tp->get(), name); + } + } + static bool traceWeak(JSTracer* trc, mozilla::UniquePtr<T, D>* tp) { + if (tp->get()) { + return GCPolicy<T>::traceWeak(trc, tp->get()); + } + return true; + } + static bool isValid(const mozilla::UniquePtr<T, D>& t) { + if (t.get()) { + return GCPolicy<T>::isValid(*t.get()); + } + return true; + } +}; + +template <> +struct GCPolicy<mozilla::Nothing> : public IgnoreGCPolicy<mozilla::Nothing> {}; + +// GCPolicy<Maybe<T>> forwards tracing/sweeping to GCPolicy<T*> if +// the Maybe<T> is filled and T* can be traced via GCPolicy<T*>. +template <typename T> +struct GCPolicy<mozilla::Maybe<T>> { + static void trace(JSTracer* trc, mozilla::Maybe<T>* tp, const char* name) { + if (tp->isSome()) { + GCPolicy<T>::trace(trc, tp->ptr(), name); + } + } + static bool traceWeak(JSTracer* trc, mozilla::Maybe<T>* tp) { + if (tp->isSome()) { + return GCPolicy<T>::traceWeak(trc, tp->ptr()); + } + return true; + } + static bool isValid(const mozilla::Maybe<T>& t) { + if (t.isSome()) { + return GCPolicy<T>::isValid(t.ref()); + } + return true; + } +}; + +template <typename T1, typename T2> +struct GCPolicy<std::pair<T1, T2>> { + static void trace(JSTracer* trc, std::pair<T1, T2>* tp, const char* name) { + GCPolicy<T1>::trace(trc, &tp->first, name); + GCPolicy<T2>::trace(trc, &tp->second, name); + } + static bool traceWeak(JSTracer* trc, std::pair<T1, T2>* tp) { + return GCPolicy<T1>::traceWeak(trc, &tp->first) && + GCPolicy<T2>::traceWeak(trc, &tp->second); + } + static bool isValid(const std::pair<T1, T2>& t) { + return GCPolicy<T1>::isValid(t.first) && GCPolicy<T2>::isValid(t.second); + } +}; + +template <> +struct GCPolicy<JS::Realm*>; // see Realm.h + +template <> +struct GCPolicy<mozilla::Ok> : public IgnoreGCPolicy<mozilla::Ok> {}; + +template <typename V, typename E> +struct GCPolicy<mozilla::Result<V, E>> { + static void trace(JSTracer* trc, mozilla::Result<V, E>* tp, + const char* name) { + if (tp->isOk()) { + V tmp = tp->unwrap(); + JS::GCPolicy<V>::trace(trc, &tmp, "Result value"); + tp->updateAfterTracing(std::move(tmp)); + } + + if (tp->isErr()) { + E tmp = tp->unwrapErr(); + JS::GCPolicy<E>::trace(trc, &tmp, "Result error"); + tp->updateErrorAfterTracing(std::move(tmp)); + } + } + + static bool isValid(const mozilla::Result<V, E>& t) { return true; } +}; + +} // namespace JS + +#endif // GCPolicyAPI_h diff --git a/js/public/GCTypeMacros.h b/js/public/GCTypeMacros.h new file mode 100644 index 0000000000..fafe62d57f --- /dev/null +++ b/js/public/GCTypeMacros.h @@ -0,0 +1,45 @@ +/* -*- 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/. */ + +/* + * Higher-order macros enumerating public untagged and tagged GC pointer types. + */ + +#ifndef GCTypeMacros_h +#define GCTypeMacros_h + +#include "jstypes.h" // JS_PUBLIC_API + +class JS_PUBLIC_API JSAtom; +class JS_PUBLIC_API JSFunction; +class JS_PUBLIC_API JSObject; +class JS_PUBLIC_API JSScript; +class JS_PUBLIC_API JSString; + +namespace JS { +class JS_PUBLIC_API BigInt; +class JS_PUBLIC_API PropertyKey; +class JS_PUBLIC_API Symbol; +class JS_PUBLIC_API Value; +} // namespace JS + +// Expand the given macro D for each public GC pointer. +#define JS_FOR_EACH_PUBLIC_GC_POINTER_TYPE(D) \ + D(JS::BigInt*) \ + D(JS::Symbol*) \ + D(JSAtom*) \ + D(JSFunction*) \ + D(JSLinearString*) \ + D(JSObject*) \ + D(JSScript*) \ + D(JSString*) + +// Expand the given macro D for each public tagged GC pointer type. +#define JS_FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(D) \ + D(JS::Value) \ + D(JS::PropertyKey) // i.e. jsid + +#endif // GCTypeMacros_h diff --git a/js/public/GCVariant.h b/js/public/GCVariant.h new file mode 100644 index 0000000000..43610b898b --- /dev/null +++ b/js/public/GCVariant.h @@ -0,0 +1,182 @@ +/* -*- 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 js_GCVariant_h +#define js_GCVariant_h + +#include "mozilla/Variant.h" + +#include <type_traits> + +#include "js/GCPolicyAPI.h" +#include "js/RootingAPI.h" +#include "js/TracingAPI.h" + +namespace JS { + +// These template specializations allow Variant to be used inside GC wrappers. +// +// When matching on GC wrappers around Variants, matching should be done on +// the wrapper itself. The matcher class's methods should take Handles or +// MutableHandles. For example, +// +// struct MyMatcher +// { +// using ReturnType = const char*; +// ReturnType match(HandleObject o) { return "object"; } +// ReturnType match(HandleScript s) { return "script"; } +// }; +// +// Rooted<Variant<JSObject*, JSScript*>> v(cx, someScript); +// MyMatcher mm; +// v.match(mm); +// +// If you get compile errors about inability to upcast subclasses (e.g., from +// NativeObject* to JSObject*) and are inside js/src, be sure to also include +// "gc/Policy.h". + +namespace detail { + +template <typename... Ts> +struct GCVariantImplementation; + +// The base case. +template <typename T> +struct GCVariantImplementation<T> { + template <typename ConcreteVariant> + static void trace(JSTracer* trc, ConcreteVariant* v, const char* name) { + T& thing = v->template as<T>(); + GCPolicy<T>::trace(trc, &thing, name); + } + + template <typename Matcher, typename ConcreteVariant> + static typename Matcher::ReturnType match(Matcher& matcher, + Handle<ConcreteVariant> v) { + const T& thing = v.get().template as<T>(); + return matcher.match(Handle<T>::fromMarkedLocation(&thing)); + } + + template <typename Matcher, typename ConcreteVariant> + static typename Matcher::ReturnType match(Matcher& matcher, + MutableHandle<ConcreteVariant> v) { + T& thing = v.get().template as<T>(); + return matcher.match(MutableHandle<T>::fromMarkedLocation(&thing)); + } +}; + +// The inductive case. +template <typename T, typename... Ts> +struct GCVariantImplementation<T, Ts...> { + using Next = GCVariantImplementation<Ts...>; + + template <typename ConcreteVariant> + static void trace(JSTracer* trc, ConcreteVariant* v, const char* name) { + if (v->template is<T>()) { + T& thing = v->template as<T>(); + GCPolicy<T>::trace(trc, &thing, name); + } else { + Next::trace(trc, v, name); + } + } + + template <typename Matcher, typename ConcreteVariant> + static typename Matcher::ReturnType match(Matcher& matcher, + Handle<ConcreteVariant> v) { + if (v.get().template is<T>()) { + const T& thing = v.get().template as<T>(); + return matcher.match(Handle<T>::fromMarkedLocation(&thing)); + } + return Next::match(matcher, v); + } + + template <typename Matcher, typename ConcreteVariant> + static typename Matcher::ReturnType match(Matcher& matcher, + MutableHandle<ConcreteVariant> v) { + if (v.get().template is<T>()) { + T& thing = v.get().template as<T>(); + return matcher.match(MutableHandle<T>::fromMarkedLocation(&thing)); + } + return Next::match(matcher, v); + } +}; + +} // namespace detail + +template <typename... Ts> +struct GCPolicy<mozilla::Variant<Ts...>> { + using Impl = detail::GCVariantImplementation<Ts...>; + + static void trace(JSTracer* trc, mozilla::Variant<Ts...>* v, + const char* name) { + Impl::trace(trc, v, name); + } + + static bool isValid(const mozilla::Variant<Ts...>& v) { + return v.match([](auto& v) { + return GCPolicy<std::remove_reference_t<decltype(v)>>::isValid(v); + }); + } +}; + +} // namespace JS + +namespace js { + +template <typename Wrapper, typename... Ts> +class WrappedPtrOperations<mozilla::Variant<Ts...>, Wrapper> { + using Impl = JS::detail::GCVariantImplementation<Ts...>; + using Variant = mozilla::Variant<Ts...>; + + const Variant& variant() const { + return static_cast<const Wrapper*>(this)->get(); + } + + public: + template <typename T> + bool is() const { + return variant().template is<T>(); + } + + template <typename T> + JS::Handle<T> as() const { + return JS::Handle<T>::fromMarkedLocation(&variant().template as<T>()); + } + + template <typename Matcher> + typename Matcher::ReturnType match(Matcher& matcher) const { + return Impl::match(matcher, + JS::Handle<Variant>::fromMarkedLocation(&variant())); + } +}; + +template <typename Wrapper, typename... Ts> +class MutableWrappedPtrOperations<mozilla::Variant<Ts...>, Wrapper> + : public WrappedPtrOperations<mozilla::Variant<Ts...>, Wrapper> { + using Impl = JS::detail::GCVariantImplementation<Ts...>; + using Variant = mozilla::Variant<Ts...>; + + const Variant& variant() const { + return static_cast<const Wrapper*>(this)->get(); + } + Variant& variant() { return static_cast<Wrapper*>(this)->get(); } + + public: + template <typename T> + JS::MutableHandle<T> as() { + return JS::MutableHandle<T>::fromMarkedLocation( + &variant().template as<T>()); + } + + template <typename Matcher> + typename Matcher::ReturnType match(Matcher& matcher) { + return Impl::match( + matcher, JS::MutableHandle<Variant>::fromMarkedLocation(&variant())); + } +}; + +} // namespace js + +#endif // js_GCVariant_h diff --git a/js/public/GCVector.h b/js/public/GCVector.h new file mode 100644 index 0000000000..f2e8b24b2d --- /dev/null +++ b/js/public/GCVector.h @@ -0,0 +1,363 @@ +/* -*- 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 js_GCVector_h +#define js_GCVector_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS +#include "mozilla/MemoryReporting.h" // MallocSizeOf +#include "mozilla/Span.h" +#include "mozilla/Vector.h" + +#include <stddef.h> // size_t +#include <utility> // forward, move + +#include "js/AllocPolicy.h" +#include "js/GCPolicyAPI.h" +#include "js/RootingAPI.h" + +class JSTracer; +struct JSContext; + +namespace JS { + +// A GCVector is a Vector with an additional trace method that knows how +// to visit all of the items stored in the Vector. For vectors that contain GC +// things, this is usually more convenient than manually iterating and marking +// the contents. +// +// Most types of GC pointers as keys and values can be traced with no extra +// infrastructure. For structs and non-gc-pointer members, ensure that there is +// a specialization of GCPolicy<T> with an appropriate trace method available +// to handle the custom type. Generic helpers can be found in +// js/public/TracingAPI.h. +// +// Note that although this Vector's trace will deal correctly with moved items, +// it does not itself know when to barrier or trace items. To function properly +// it must either be used with Rooted, or barriered and traced manually. +template <typename T, size_t MinInlineCapacity = 0, + typename AllocPolicy = js::TempAllocPolicy> +class GCVector { + mozilla::Vector<T, MinInlineCapacity, AllocPolicy> vector; + + public: + using ElementType = T; + + explicit GCVector(AllocPolicy alloc) : vector(std::move(alloc)) {} + GCVector() : GCVector(AllocPolicy()) {} + + GCVector(GCVector&& vec) : vector(std::move(vec.vector)) {} + + GCVector& operator=(GCVector&& vec) { + vector = std::move(vec.vector); + return *this; + } + + size_t length() const { return vector.length(); } + bool empty() const { return vector.empty(); } + size_t capacity() const { return vector.capacity(); } + + T* begin() { return vector.begin(); } + const T* begin() const { return vector.begin(); } + + T* end() { return vector.end(); } + const T* end() const { return vector.end(); } + + T& operator[](size_t i) { return vector[i]; } + const T& operator[](size_t i) const { return vector[i]; } + + T& back() { return vector.back(); } + const T& back() const { return vector.back(); } + + operator mozilla::Span<T>() { return vector; } + operator mozilla::Span<const T>() const { return vector; } + + bool initCapacity(size_t cap) { return vector.initCapacity(cap); } + [[nodiscard]] bool reserve(size_t req) { return vector.reserve(req); } + void shrinkBy(size_t amount) { return vector.shrinkBy(amount); } + void shrinkTo(size_t newLen) { return vector.shrinkTo(newLen); } + [[nodiscard]] bool growBy(size_t amount) { return vector.growBy(amount); } + [[nodiscard]] bool resize(size_t newLen) { return vector.resize(newLen); } + + void clear() { return vector.clear(); } + void clearAndFree() { return vector.clearAndFree(); } + + template <typename U> + bool append(U&& item) { + return vector.append(std::forward<U>(item)); + } + + void erase(T* it) { vector.erase(it); } + void erase(T* begin, T* end) { vector.erase(begin, end); } + template <typename Pred> + void eraseIf(Pred pred) { + vector.eraseIf(pred); + } + template <typename U> + void eraseIfEqual(const U& u) { + vector.eraseIfEqual(u); + } + + template <typename... Args> + [[nodiscard]] bool emplaceBack(Args&&... args) { + return vector.emplaceBack(std::forward<Args>(args)...); + } + + template <typename... Args> + void infallibleEmplaceBack(Args&&... args) { + vector.infallibleEmplaceBack(std::forward<Args>(args)...); + } + + template <typename U> + void infallibleAppend(U&& aU) { + return vector.infallibleAppend(std::forward<U>(aU)); + } + void infallibleAppendN(const T& aT, size_t aN) { + return vector.infallibleAppendN(aT, aN); + } + template <typename U> + void infallibleAppend(const U* aBegin, const U* aEnd) { + return vector.infallibleAppend(aBegin, aEnd); + } + template <typename U> + void infallibleAppend(const U* aBegin, size_t aLength) { + return vector.infallibleAppend(aBegin, aLength); + } + + template <typename U> + [[nodiscard]] bool appendAll(const U& aU) { + return vector.append(aU.begin(), aU.end()); + } + template <typename T2, size_t MinInlineCapacity2, typename AllocPolicy2> + [[nodiscard]] bool appendAll( + GCVector<T2, MinInlineCapacity2, AllocPolicy2>&& aU) { + return vector.appendAll(aU.begin(), aU.end()); + } + + [[nodiscard]] bool appendN(const T& val, size_t count) { + return vector.appendN(val, count); + } + + template <typename U> + [[nodiscard]] bool append(const U* aBegin, const U* aEnd) { + return vector.append(aBegin, aEnd); + } + template <typename U> + [[nodiscard]] bool append(const U* aBegin, size_t aLength) { + return vector.append(aBegin, aLength); + } + + void popBack() { return vector.popBack(); } + T popCopy() { return vector.popCopy(); } + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return vector.sizeOfExcludingThis(mallocSizeOf); + } + + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return vector.sizeOfIncludingThis(mallocSizeOf); + } + + void trace(JSTracer* trc) { + for (auto& elem : vector) { + GCPolicy<T>::trace(trc, &elem, "vector element"); + } + } + + bool traceWeak(JSTracer* trc) { + mutableEraseIf( + [trc](T& elem) { return !GCPolicy<T>::traceWeak(trc, &elem); }); + return !empty(); + } + + // Like eraseIf, but may mutate the contents of the vector. + template <typename Pred> + void mutableEraseIf(Pred pred) { + T* src = begin(); + T* dst = begin(); + while (src != end()) { + if (!pred(*src)) { + if (src != dst) { + *dst = std::move(*src); + } + dst++; + } + src++; + } + + MOZ_ASSERT(dst <= end()); + shrinkBy(end() - dst); + } +}; + +// AllocPolicy is optional. It has a default value declared in TypeDecls.h +template <typename T, typename AllocPolicy> +class MOZ_STACK_CLASS StackGCVector : public GCVector<T, 8, AllocPolicy> { + public: + using Base = GCVector<T, 8, AllocPolicy>; + + private: + // Inherit constructor from GCVector. + using Base::Base; +}; + +} // namespace JS + +namespace js { + +template <typename Wrapper, typename T, size_t Capacity, typename AllocPolicy> +class WrappedPtrOperations<JS::GCVector<T, Capacity, AllocPolicy>, Wrapper> { + using Vec = JS::GCVector<T, Capacity, AllocPolicy>; + const Vec& vec() const { return static_cast<const Wrapper*>(this)->get(); } + + public: + const AllocPolicy& allocPolicy() const { return vec().allocPolicy(); } + size_t length() const { return vec().length(); } + bool empty() const { return vec().empty(); } + size_t capacity() const { return vec().capacity(); } + const T* begin() const { return vec().begin(); } + const T* end() const { return vec().end(); } + const T& back() const { return vec().back(); } + + JS::Handle<T> operator[](size_t aIndex) const { + return JS::Handle<T>::fromMarkedLocation(&vec().operator[](aIndex)); + } +}; + +template <typename Wrapper, typename T, size_t Capacity, typename AllocPolicy> +class MutableWrappedPtrOperations<JS::GCVector<T, Capacity, AllocPolicy>, + Wrapper> + : public WrappedPtrOperations<JS::GCVector<T, Capacity, AllocPolicy>, + Wrapper> { + using Vec = JS::GCVector<T, Capacity, AllocPolicy>; + const Vec& vec() const { return static_cast<const Wrapper*>(this)->get(); } + Vec& vec() { return static_cast<Wrapper*>(this)->get(); } + + public: + const AllocPolicy& allocPolicy() const { return vec().allocPolicy(); } + AllocPolicy& allocPolicy() { return vec().allocPolicy(); } + const T* begin() const { return vec().begin(); } + T* begin() { return vec().begin(); } + const T* end() const { return vec().end(); } + T* end() { return vec().end(); } + const T& back() const { return vec().back(); } + T& back() { return vec().back(); } + + JS::Handle<T> operator[](size_t aIndex) const { + return JS::Handle<T>::fromMarkedLocation(&vec().operator[](aIndex)); + } + JS::MutableHandle<T> operator[](size_t aIndex) { + return JS::MutableHandle<T>::fromMarkedLocation(&vec().operator[](aIndex)); + } + + [[nodiscard]] bool initCapacity(size_t aRequest) { + return vec().initCapacity(aRequest); + } + [[nodiscard]] bool reserve(size_t aRequest) { + return vec().reserve(aRequest); + } + void shrinkBy(size_t aIncr) { vec().shrinkBy(aIncr); } + [[nodiscard]] bool growBy(size_t aIncr) { return vec().growBy(aIncr); } + [[nodiscard]] bool resize(size_t aNewLength) { + return vec().resize(aNewLength); + } + void clear() { vec().clear(); } + void clearAndFree() { vec().clearAndFree(); } + template <typename U> + [[nodiscard]] bool append(U&& aU) { + return vec().append(std::forward<U>(aU)); + } + template <typename... Args> + [[nodiscard]] bool emplaceBack(Args&&... aArgs) { + return vec().emplaceBack(std::forward<Args>(aArgs)...); + } + template <typename... Args> + void infallibleEmplaceBack(Args&&... args) { + vec().infallibleEmplaceBack(std::forward<Args>(args)...); + } + template <typename U> + [[nodiscard]] bool appendAll(U&& aU) { + return vec().appendAll(aU); + } + [[nodiscard]] bool appendN(const T& aT, size_t aN) { + return vec().appendN(aT, aN); + } + template <typename U> + [[nodiscard]] bool append(const U* aBegin, const U* aEnd) { + return vec().append(aBegin, aEnd); + } + template <typename U> + [[nodiscard]] bool append(const U* aBegin, size_t aLength) { + return vec().append(aBegin, aLength); + } + template <typename U> + void infallibleAppend(U&& aU) { + vec().infallibleAppend(std::forward<U>(aU)); + } + void infallibleAppendN(const T& aT, size_t aN) { + vec().infallibleAppendN(aT, aN); + } + template <typename U> + void infallibleAppend(const U* aBegin, const U* aEnd) { + vec().infallibleAppend(aBegin, aEnd); + } + template <typename U> + void infallibleAppend(const U* aBegin, size_t aLength) { + vec().infallibleAppend(aBegin, aLength); + } + void popBack() { vec().popBack(); } + T popCopy() { return vec().popCopy(); } + void erase(T* aT) { vec().erase(aT); } + void erase(T* aBegin, T* aEnd) { vec().erase(aBegin, aEnd); } + template <typename Pred> + void eraseIf(Pred pred) { + vec().eraseIf(pred); + } + template <typename U> + void eraseIfEqual(const U& u) { + vec().eraseIfEqual(u); + } +}; + +template <typename Wrapper, typename T, typename AllocPolicy> +class WrappedPtrOperations<JS::StackGCVector<T, AllocPolicy>, Wrapper> + : public WrappedPtrOperations< + typename JS::StackGCVector<T, AllocPolicy>::Base, Wrapper> {}; + +template <typename Wrapper, typename T, typename AllocPolicy> +class MutableWrappedPtrOperations<JS::StackGCVector<T, AllocPolicy>, Wrapper> + : public MutableWrappedPtrOperations< + typename JS::StackGCVector<T, AllocPolicy>::Base, Wrapper> {}; + +} // namespace js + +namespace JS { + +// An automatically rooted GCVector for stack use. +template <typename T> +class RootedVector : public Rooted<StackGCVector<T>> { + using Vec = StackGCVector<T>; + using Base = Rooted<Vec>; + + public: + explicit RootedVector(JSContext* cx) : Base(cx, Vec(cx)) {} +}; + +// For use in rust code, an analog to RootedVector that doesn't require +// instances to be destroyed in LIFO order. +template <typename T> +class PersistentRootedVector : public PersistentRooted<StackGCVector<T>> { + using Vec = StackGCVector<T>; + using Base = PersistentRooted<Vec>; + + public: + explicit PersistentRootedVector(JSContext* cx) : Base(cx, Vec(cx)) {} +}; + +} // namespace JS + +#endif // js_GCVector_h diff --git a/js/public/GlobalObject.h b/js/public/GlobalObject.h new file mode 100644 index 0000000000..22da1fc28b --- /dev/null +++ b/js/public/GlobalObject.h @@ -0,0 +1,99 @@ +/* -*- 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 js_GlobalObject_h +#define js_GlobalObject_h + +#include "mozilla/Attributes.h" + +#include "jstypes.h" + +#include "js/TypeDecls.h" + +class JS_PUBLIC_API JSTracer; + +struct JSClassOps; + +extern JS_PUBLIC_API bool JS_IsGlobalObject(JSObject* obj); + +namespace JS { + +class JS_PUBLIC_API RealmOptions; + +/** + * Get the current realm's global. Returns nullptr if no realm has been + * entered. + */ +extern JS_PUBLIC_API JSObject* CurrentGlobalOrNull(JSContext* cx); + +/** + * Get the global object associated with an object's realm. The object must not + * be a cross-compartment wrapper (because CCWs are shared by all realms in the + * compartment). + */ +extern JS_PUBLIC_API JSObject* GetNonCCWObjectGlobal(JSObject* obj); + +/** + * During global creation, we fire notifications to callbacks registered + * via the Debugger API. These callbacks are arbitrary script, and can touch + * the global in arbitrary ways. When that happens, the global should not be + * in a half-baked state. But this creates a problem for consumers that need + * to set slots on the global to put it in a consistent state. + * + * This API provides a way for consumers to set slots atomically (immediately + * after the global is created), before any debugger hooks are fired. It's + * unfortunately on the clunky side, but that's the way the cookie crumbles. + * + * If callers have no additional state on the global to set up, they may pass + * |FireOnNewGlobalHook| to JS_NewGlobalObject, which causes that function to + * fire the hook as its final act before returning. Otherwise, callers should + * pass |DontFireOnNewGlobalHook|, which means that they are responsible for + * invoking JS_FireOnNewGlobalObject upon successfully creating the global. If + * an error occurs and the operation aborts, callers should skip firing the + * hook. But otherwise, callers must take care to fire the hook exactly once + * before compiling any script in the global's scope (we have assertions in + * place to enforce this). This lets us be sure that debugger clients never miss + * breakpoints. + */ +enum OnNewGlobalHookOption { FireOnNewGlobalHook, DontFireOnNewGlobalHook }; + +} // namespace JS + +extern JS_PUBLIC_API JSObject* JS_NewGlobalObject( + JSContext* cx, const JSClass* clasp, JSPrincipals* principals, + JS::OnNewGlobalHookOption hookOption, const JS::RealmOptions& options); +/** + * Spidermonkey does not have a good way of keeping track of what compartments + * should be marked on their own. We can mark the roots unconditionally, but + * marking GC things only relevant in live compartments is hard. To mitigate + * this, we create a static trace hook, installed on each global object, from + * which we can be sure the compartment is relevant, and mark it. + * + * It is still possible to specify custom trace hooks for global object classes. + * They can be provided via the RealmOptions passed to JS_NewGlobalObject. + */ +extern JS_PUBLIC_API void JS_GlobalObjectTraceHook(JSTracer* trc, + JSObject* global); + +namespace JS { + +/** + * This allows easily constructing a global object without having to deal with + * JSClassOps, forgetting to add JS_GlobalObjectTraceHook, or forgetting to call + * JS::InitRealmStandardClasses(). Example: + * + * const JSClass globalClass = { "MyGlobal", JSCLASS_GLOBAL_FLAGS, + * &JS::DefaultGlobalClassOps }; + * JS_NewGlobalObject(cx, &globalClass, ...); + */ +extern JS_PUBLIC_DATA const JSClassOps DefaultGlobalClassOps; + +} // namespace JS + +extern JS_PUBLIC_API void JS_FireOnNewGlobalObject(JSContext* cx, + JS::HandleObject global); + +#endif // js_GlobalObject_h diff --git a/js/public/HashTable.h b/js/public/HashTable.h new file mode 100644 index 0000000000..3e7d0ffa6a --- /dev/null +++ b/js/public/HashTable.h @@ -0,0 +1,38 @@ +/* -*- 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 js_HashTable_h +#define js_HashTable_h + +#include "mozilla/HashTable.h" + +#include "jstypes.h" + +namespace js { + +using HashNumber = mozilla::HashNumber; +static const uint32_t kHashNumberBits = mozilla::kHashNumberBits; + +class JS_PUBLIC_API TempAllocPolicy; + +template <class T> +using DefaultHasher = mozilla::DefaultHasher<T>; + +template <typename Key> +using PointerHasher = mozilla::PointerHasher<Key>; + +template <typename T, class HashPolicy = mozilla::DefaultHasher<T>, + class AllocPolicy = TempAllocPolicy> +using HashSet = mozilla::HashSet<T, HashPolicy, AllocPolicy>; + +template <typename Key, typename Value, + class HashPolicy = mozilla::DefaultHasher<Key>, + class AllocPolicy = TempAllocPolicy> +using HashMap = mozilla::HashMap<Key, Value, HashPolicy, AllocPolicy>; + +} // namespace js + +#endif /* js_HashTable_h */ diff --git a/js/public/HeapAPI.h b/js/public/HeapAPI.h new file mode 100644 index 0000000000..0c8d766a37 --- /dev/null +++ b/js/public/HeapAPI.h @@ -0,0 +1,834 @@ +/* -*- 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 js_HeapAPI_h +#define js_HeapAPI_h + +#include "mozilla/Atomics.h" +#include "mozilla/BitSet.h" + +#include <limits.h> +#include <type_traits> + +#include "js/AllocPolicy.h" +#include "js/GCAnnotations.h" +#include "js/HashTable.h" +#include "js/shadow/String.h" // JS::shadow::String +#include "js/shadow/Symbol.h" // JS::shadow::Symbol +#include "js/shadow/Zone.h" // JS::shadow::Zone +#include "js/TraceKind.h" +#include "js/TypeDecls.h" + +/* These values are private to the JS engine. */ +namespace js { + +class NurseryDecommitTask; + +JS_PUBLIC_API bool CurrentThreadCanAccessZone(JS::Zone* zone); + +namespace gc { + +class Arena; +struct Cell; +class TenuredChunk; +class StoreBuffer; +class TenuredCell; + +const size_t ArenaShift = 12; +const size_t ArenaSize = size_t(1) << ArenaShift; +const size_t ArenaMask = ArenaSize - 1; + +#if defined(XP_MACOSX) && defined(__aarch64__) +const size_t PageShift = 14; +#else +const size_t PageShift = 12; +#endif +// Expected page size, so we could initialze ArenasPerPage at compile-time. +// The actual system page size should be queried by SystemPageSize(). +const size_t PageSize = size_t(1) << PageShift; +constexpr size_t ArenasPerPage = PageSize / ArenaSize; + +#ifdef JS_GC_SMALL_CHUNK_SIZE +const size_t ChunkShift = 18; +#else +const size_t ChunkShift = 20; +#endif +const size_t ChunkSize = size_t(1) << ChunkShift; +const size_t ChunkMask = ChunkSize - 1; + +const size_t CellAlignShift = 3; +const size_t CellAlignBytes = size_t(1) << CellAlignShift; +const size_t CellAlignMask = CellAlignBytes - 1; + +const size_t CellBytesPerMarkBit = CellAlignBytes; +const size_t MarkBitsPerCell = 2; + +/* + * The mark bitmap has one bit per each possible cell start position. This + * wastes some space for larger GC things but allows us to avoid division by the + * cell's size when accessing the bitmap. + */ +const size_t ArenaBitmapBits = ArenaSize / CellBytesPerMarkBit; +const size_t ArenaBitmapBytes = HowMany(ArenaBitmapBits, 8); +const size_t ArenaBitmapWords = HowMany(ArenaBitmapBits, JS_BITS_PER_WORD); + +// The base class for all GC chunks, either in the nursery or in the tenured +// heap memory. This structure is locatable from any GC pointer by aligning to +// the chunk size. +class alignas(CellAlignBytes) ChunkBase { + protected: + ChunkBase(JSRuntime* rt, StoreBuffer* sb) { + MOZ_ASSERT((uintptr_t(this) & ChunkMask) == 0); + initBase(rt, sb); + } + + void initBase(JSRuntime* rt, StoreBuffer* sb) { + runtime = rt; + storeBuffer = sb; + } + + public: + // The store buffer for pointers from tenured things to things in this + // chunk. Will be non-null if and only if this is a nursery chunk. + StoreBuffer* storeBuffer; + + // Provide quick access to the runtime from absolutely anywhere. + JSRuntime* runtime; +}; + +// Information about tenured heap chunks. +struct TenuredChunkInfo { + private: + friend class ChunkPool; + TenuredChunk* next = nullptr; + TenuredChunk* prev = nullptr; + + public: + /* Number of free arenas, either committed or decommitted. */ + uint32_t numArenasFree; + + /* Number of free, committed arenas. */ + uint32_t numArenasFreeCommitted; +}; + +/* + * Calculating ArenasPerChunk: + * + * To figure out how many Arenas will fit in a chunk we need to know how much + * extra space is available after we allocate the header data. This is a problem + * because the header size depends on the number of arenas in the chunk. + * + * The dependent fields are markBits, decommittedPages and + * freeCommittedArenas. markBits needs ArenaBitmapBytes bytes per arena, + * decommittedPages needs one bit per page and freeCommittedArenas needs one + * bit per arena. + * + * We can calculate an approximate value by dividing the number of bits of free + * space in the chunk by the number of bits needed per arena. This is an + * approximation because it doesn't take account of the fact that the variable + * sized fields must be rounded up to a whole number of words, or any padding + * the compiler adds between fields. + * + * Fortunately, for the chunk and arena size parameters we use this + * approximation turns out to be correct. If it were not we might need to adjust + * the arena count down by one to allow more space for the padding. + */ +const size_t BitsPerPageWithHeaders = + (ArenaSize + ArenaBitmapBytes) * ArenasPerPage * CHAR_BIT + ArenasPerPage + + 1; +const size_t ChunkBitsAvailable = + (ChunkSize - sizeof(ChunkBase) - sizeof(TenuredChunkInfo)) * CHAR_BIT; +const size_t PagesPerChunk = ChunkBitsAvailable / BitsPerPageWithHeaders; +const size_t ArenasPerChunk = PagesPerChunk * ArenasPerPage; +const size_t FreeCommittedBits = ArenasPerChunk; +const size_t DecommitBits = PagesPerChunk; +const size_t BitsPerArenaWithHeaders = + (ArenaSize + ArenaBitmapBytes) * CHAR_BIT + + (DecommitBits / ArenasPerChunk) + 1; + +const size_t CalculatedChunkSizeRequired = + sizeof(ChunkBase) + sizeof(TenuredChunkInfo) + + RoundUp(ArenasPerChunk * ArenaBitmapBytes, sizeof(uintptr_t)) + + RoundUp(FreeCommittedBits, sizeof(uint32_t) * CHAR_BIT) / CHAR_BIT + + RoundUp(DecommitBits, sizeof(uint32_t) * CHAR_BIT) / CHAR_BIT + + ArenasPerChunk * ArenaSize; +static_assert(CalculatedChunkSizeRequired <= ChunkSize, + "Calculated ArenasPerChunk is too large"); + +const size_t CalculatedChunkPadSize = ChunkSize - CalculatedChunkSizeRequired; +static_assert(CalculatedChunkPadSize * CHAR_BIT < BitsPerArenaWithHeaders, + "Calculated ArenasPerChunk is too small"); + +// Define a macro for the expected number of arenas so its value appears in the +// error message if the assertion fails. +#ifdef JS_GC_SMALL_CHUNK_SIZE +# define EXPECTED_ARENA_COUNT 63 +#else +# define EXPECTED_ARENA_COUNT 252 +#endif +static_assert(ArenasPerChunk == EXPECTED_ARENA_COUNT, + "Do not accidentally change our heap's density."); +#undef EXPECTED_ARENA_COUNT + +// Mark bitmaps are atomic because they can be written by gray unmarking on the +// main thread while read by sweeping on a background thread. The former does +// not affect the result of the latter. +using MarkBitmapWord = mozilla::Atomic<uintptr_t, mozilla::Relaxed>; + +/* + * Live objects are marked black or gray. Everything reachable from a JS root is + * marked black. Objects marked gray are eligible for cycle collection. + * + * BlackBit: GrayOrBlackBit: Color: + * 0 0 white + * 0 1 gray + * 1 0 black + * 1 1 black + */ +enum class ColorBit : uint32_t { BlackBit = 0, GrayOrBlackBit = 1 }; + +// Mark colors. Order is important here: the greater value the 'more marked' a +// cell is. +enum class MarkColor : uint8_t { Gray = 1, Black = 2 }; + +// Mark bitmap for a tenured heap chunk. +struct MarkBitmap { + static constexpr size_t WordCount = ArenaBitmapWords * ArenasPerChunk; + MarkBitmapWord bitmap[WordCount]; + + inline void getMarkWordAndMask(const TenuredCell* cell, ColorBit colorBit, + MarkBitmapWord** wordp, uintptr_t* maskp); + + // The following are not exported and are defined in gc/Heap.h: + inline bool markBit(const TenuredCell* cell, ColorBit colorBit); + inline bool isMarkedAny(const TenuredCell* cell); + inline bool isMarkedBlack(const TenuredCell* cell); + inline bool isMarkedGray(const TenuredCell* cell); + inline bool markIfUnmarked(const TenuredCell* cell, MarkColor color); + inline bool markIfUnmarkedAtomic(const TenuredCell* cell, MarkColor color); + inline void markBlack(const TenuredCell* cell); + inline void markBlackAtomic(const TenuredCell* cell); + inline void copyMarkBit(TenuredCell* dst, const TenuredCell* src, + ColorBit colorBit); + inline void unmark(const TenuredCell* cell); + inline MarkBitmapWord* arenaBits(Arena* arena); +}; + +static_assert(ArenaBitmapBytes * ArenasPerChunk == sizeof(MarkBitmap), + "Ensure our MarkBitmap actually covers all arenas."); + +// Bitmap with one bit per page used for decommitted page set. +using ChunkPageBitmap = mozilla::BitSet<PagesPerChunk, uint32_t>; + +// Bitmap with one bit per arena used for free committed arena set. +using ChunkArenaBitmap = mozilla::BitSet<ArenasPerChunk, uint32_t>; + +// Base class containing data members for a tenured heap chunk. +class TenuredChunkBase : public ChunkBase { + public: + TenuredChunkInfo info; + MarkBitmap markBits; + ChunkArenaBitmap freeCommittedArenas; + ChunkPageBitmap decommittedPages; + + protected: + explicit TenuredChunkBase(JSRuntime* runtime) : ChunkBase(runtime, nullptr) { + info.numArenasFree = ArenasPerChunk; + } + + void initAsDecommitted(); +}; + +/* + * We sometimes use an index to refer to a cell in an arena. The index for a + * cell is found by dividing by the cell alignment so not all indices refer to + * valid cells. + */ +const size_t ArenaCellIndexBytes = CellAlignBytes; +const size_t MaxArenaCellIndex = ArenaSize / CellAlignBytes; + +const size_t MarkBitmapWordBits = sizeof(MarkBitmapWord) * CHAR_BIT; + +constexpr size_t FirstArenaAdjustmentBits = + RoundUp(sizeof(gc::TenuredChunkBase), ArenaSize) / gc::CellBytesPerMarkBit; + +static_assert((FirstArenaAdjustmentBits % MarkBitmapWordBits) == 0); +constexpr size_t FirstArenaAdjustmentWords = + FirstArenaAdjustmentBits / MarkBitmapWordBits; + +const size_t ChunkStoreBufferOffset = offsetof(ChunkBase, storeBuffer); +const size_t ChunkMarkBitmapOffset = offsetof(TenuredChunkBase, markBits); + +// Hardcoded offsets into Arena class. +const size_t ArenaZoneOffset = 2 * sizeof(uint32_t); +const size_t ArenaHeaderSize = ArenaZoneOffset + 2 * sizeof(uintptr_t) + + sizeof(size_t) + sizeof(uintptr_t); + +// The first word of a GC thing has certain requirements from the GC and is used +// to store flags in the low bits. +const size_t CellFlagBitsReservedForGC = 3; + +// The first word can be used to store JSClass pointers for some thing kinds, so +// these must be suitably aligned. +const size_t JSClassAlignBytes = size_t(1) << CellFlagBitsReservedForGC; + +#ifdef JS_DEBUG +/* When downcasting, ensure we are actually the right type. */ +extern JS_PUBLIC_API void AssertGCThingHasType(js::gc::Cell* cell, + JS::TraceKind kind); +#else +inline void AssertGCThingHasType(js::gc::Cell* cell, JS::TraceKind kind) {} +#endif + +MOZ_ALWAYS_INLINE bool IsInsideNursery(const js::gc::Cell* cell); +MOZ_ALWAYS_INLINE bool IsInsideNursery(const js::gc::TenuredCell* cell); + +} /* namespace gc */ +} /* namespace js */ + +namespace JS { + +enum class HeapState { + Idle, // doing nothing with the GC heap + Tracing, // tracing the GC heap without collecting, e.g. + // IterateCompartments() + MajorCollecting, // doing a GC of the major heap + MinorCollecting, // doing a GC of the minor heap (nursery) + CycleCollecting // in the "Unlink" phase of cycle collection +}; + +JS_PUBLIC_API HeapState RuntimeHeapState(); + +static inline bool RuntimeHeapIsBusy() { + return RuntimeHeapState() != HeapState::Idle; +} + +static inline bool RuntimeHeapIsTracing() { + return RuntimeHeapState() == HeapState::Tracing; +} + +static inline bool RuntimeHeapIsMajorCollecting() { + return RuntimeHeapState() == HeapState::MajorCollecting; +} + +static inline bool RuntimeHeapIsMinorCollecting() { + return RuntimeHeapState() == HeapState::MinorCollecting; +} + +static inline bool RuntimeHeapIsCollecting(HeapState state) { + return state == HeapState::MajorCollecting || + state == HeapState::MinorCollecting; +} + +static inline bool RuntimeHeapIsCollecting() { + return RuntimeHeapIsCollecting(RuntimeHeapState()); +} + +static inline bool RuntimeHeapIsCycleCollecting() { + return RuntimeHeapState() == HeapState::CycleCollecting; +} + +/* + * This list enumerates the different types of conceptual stacks we have in + * SpiderMonkey. In reality, they all share the C stack, but we allow different + * stack limits depending on the type of code running. + */ +enum StackKind { + StackForSystemCode, // C++, such as the GC, running on behalf of the VM. + StackForTrustedScript, // Script running with trusted principals. + StackForUntrustedScript, // Script running with untrusted principals. + StackKindCount +}; + +/* + * Default maximum size for the generational nursery in bytes. This is the + * initial value. In the browser this configured by the + * javascript.options.mem.nursery.max_kb pref. + */ +const uint32_t DefaultNurseryMaxBytes = 16 * js::gc::ChunkSize; + +/* Default maximum heap size in bytes to pass to JS_NewContext(). */ +const uint32_t DefaultHeapMaxBytes = 32 * 1024 * 1024; + +/** + * A GC pointer, tagged with the trace kind. + * + * In general, a GC pointer should be stored with an exact type. This class + * is for use when that is not possible because a single pointer must point + * to several kinds of GC thing. + */ +class JS_PUBLIC_API GCCellPtr { + public: + GCCellPtr() : GCCellPtr(nullptr) {} + + // Construction from a void* and trace kind. + GCCellPtr(void* gcthing, JS::TraceKind traceKind) + : ptr(checkedCast(gcthing, traceKind)) {} + + // Automatically construct a null GCCellPtr from nullptr. + MOZ_IMPLICIT GCCellPtr(decltype(nullptr)) + : ptr(checkedCast(nullptr, JS::TraceKind::Null)) {} + + // Construction from an explicit type. + template <typename T> + explicit GCCellPtr(T* p) + : ptr(checkedCast(p, JS::MapTypeToTraceKind<T>::kind)) {} + explicit GCCellPtr(JSFunction* p) + : ptr(checkedCast(p, JS::TraceKind::Object)) {} + explicit GCCellPtr(JSScript* p) + : ptr(checkedCast(p, JS::TraceKind::Script)) {} + explicit GCCellPtr(const Value& v); + + JS::TraceKind kind() const { + uintptr_t kindBits = ptr & OutOfLineTraceKindMask; + if (kindBits != OutOfLineTraceKindMask) { + return JS::TraceKind(kindBits); + } + return outOfLineKind(); + } + + // Allow GCCellPtr to be used in a boolean context. + explicit operator bool() const { + MOZ_ASSERT(bool(asCell()) == (kind() != JS::TraceKind::Null)); + return asCell(); + } + + // Simplify checks to the kind. + template <typename T, typename = std::enable_if_t<JS::IsBaseTraceType_v<T>>> + bool is() const { + return kind() == JS::MapTypeToTraceKind<T>::kind; + } + + // Conversions to more specific types must match the kind. Access to + // further refined types is not allowed directly from a GCCellPtr. + template <typename T, typename = std::enable_if_t<JS::IsBaseTraceType_v<T>>> + T& as() const { + MOZ_ASSERT(kind() == JS::MapTypeToTraceKind<T>::kind); + // We can't use static_cast here, because the fact that JSObject + // inherits from js::gc::Cell is not part of the public API. + return *reinterpret_cast<T*>(asCell()); + } + + // Return a pointer to the cell this |GCCellPtr| refers to, or |nullptr|. + // (It would be more symmetrical with |to| for this to return a |Cell&|, but + // the result can be |nullptr|, and null references are undefined behavior.) + js::gc::Cell* asCell() const { + return reinterpret_cast<js::gc::Cell*>(ptr & ~OutOfLineTraceKindMask); + } + + // The CC's trace logger needs an identity that is XPIDL serializable. + uint64_t unsafeAsInteger() const { + return static_cast<uint64_t>(unsafeAsUIntPtr()); + } + // Inline mark bitmap access requires direct pointer arithmetic. + uintptr_t unsafeAsUIntPtr() const { + MOZ_ASSERT(asCell()); + MOZ_ASSERT(!js::gc::IsInsideNursery(asCell())); + return reinterpret_cast<uintptr_t>(asCell()); + } + + MOZ_ALWAYS_INLINE bool mayBeOwnedByOtherRuntime() const { + if (!is<JSString>() && !is<JS::Symbol>()) { + return false; + } + if (is<JSString>()) { + return JS::shadow::String::isPermanentAtom(asCell()); + } + MOZ_ASSERT(is<JS::Symbol>()); + return JS::shadow::Symbol::isWellKnownSymbol(asCell()); + } + + private: + static uintptr_t checkedCast(void* p, JS::TraceKind traceKind) { + auto* cell = static_cast<js::gc::Cell*>(p); + MOZ_ASSERT((uintptr_t(p) & OutOfLineTraceKindMask) == 0); + AssertGCThingHasType(cell, traceKind); + // Store trace in the bottom bits of pointer for common kinds. + uintptr_t kindBits = uintptr_t(traceKind); + if (kindBits >= OutOfLineTraceKindMask) { + kindBits = OutOfLineTraceKindMask; + } + return uintptr_t(p) | kindBits; + } + + JS::TraceKind outOfLineKind() const; + + uintptr_t ptr; +} JS_HAZ_GC_POINTER; + +// Unwraps the given GCCellPtr, calls the functor |f| with a template argument +// of the actual type of the pointer, and returns the result. +template <typename F> +auto MapGCThingTyped(GCCellPtr thing, F&& f) { + switch (thing.kind()) { +#define JS_EXPAND_DEF(name, type, _, _1) \ + case JS::TraceKind::name: \ + return f(&thing.as<type>()); + JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF); +#undef JS_EXPAND_DEF + default: + MOZ_CRASH("Invalid trace kind in MapGCThingTyped for GCCellPtr."); + } +} + +// Unwraps the given GCCellPtr and calls the functor |f| with a template +// argument of the actual type of the pointer. Doesn't return anything. +template <typename F> +void ApplyGCThingTyped(GCCellPtr thing, F&& f) { + // This function doesn't do anything but is supplied for symmetry with other + // MapGCThingTyped/ApplyGCThingTyped implementations that have to wrap the + // functor to return a dummy value that is ignored. + MapGCThingTyped(thing, f); +} + +} /* namespace JS */ + +// These are defined in the toplevel namespace instead of within JS so that +// they won't shadow other operator== overloads (see bug 1456512.) + +inline bool operator==(JS::GCCellPtr ptr1, JS::GCCellPtr ptr2) { + return ptr1.asCell() == ptr2.asCell(); +} + +inline bool operator!=(JS::GCCellPtr ptr1, JS::GCCellPtr ptr2) { + return !(ptr1 == ptr2); +} + +namespace js { +namespace gc { + +/* static */ +MOZ_ALWAYS_INLINE void MarkBitmap::getMarkWordAndMask(const TenuredCell* cell, + ColorBit colorBit, + MarkBitmapWord** wordp, + uintptr_t* maskp) { + // Note: the JIT pre-barrier trampolines inline this code. Update + // MacroAssembler::emitPreBarrierFastPath code too when making changes here! + + MOZ_ASSERT(size_t(colorBit) < MarkBitsPerCell); + + size_t offset = uintptr_t(cell) & ChunkMask; + const size_t bit = offset / CellBytesPerMarkBit + size_t(colorBit); + size_t word = bit / MarkBitmapWordBits - FirstArenaAdjustmentWords; + MOZ_ASSERT(word < WordCount); + *wordp = &bitmap[word]; + *maskp = uintptr_t(1) << (bit % MarkBitmapWordBits); +} + +namespace detail { + +static MOZ_ALWAYS_INLINE ChunkBase* GetCellChunkBase(const Cell* cell) { + MOZ_ASSERT(cell); + return reinterpret_cast<ChunkBase*>(uintptr_t(cell) & ~ChunkMask); +} + +static MOZ_ALWAYS_INLINE TenuredChunkBase* GetCellChunkBase( + const TenuredCell* cell) { + MOZ_ASSERT(cell); + return reinterpret_cast<TenuredChunkBase*>(uintptr_t(cell) & ~ChunkMask); +} + +static MOZ_ALWAYS_INLINE JS::Zone* GetTenuredGCThingZone(const uintptr_t addr) { + MOZ_ASSERT(addr); + const uintptr_t zone_addr = (addr & ~ArenaMask) | ArenaZoneOffset; + return *reinterpret_cast<JS::Zone**>(zone_addr); +} + +static MOZ_ALWAYS_INLINE bool TenuredCellIsMarkedBlack( + const TenuredCell* cell) { + // Return true if BlackBit is set. + + MOZ_ASSERT(cell); + MOZ_ASSERT(!js::gc::IsInsideNursery(cell)); + + MarkBitmapWord* blackWord; + uintptr_t blackMask; + TenuredChunkBase* chunk = GetCellChunkBase(cell); + chunk->markBits.getMarkWordAndMask(cell, js::gc::ColorBit::BlackBit, + &blackWord, &blackMask); + return *blackWord & blackMask; +} + +static MOZ_ALWAYS_INLINE bool NonBlackCellIsMarkedGray( + const TenuredCell* cell) { + // Return true if GrayOrBlackBit is set. Callers should check BlackBit first. + + MOZ_ASSERT(cell); + MOZ_ASSERT(!js::gc::IsInsideNursery(cell)); + MOZ_ASSERT(!TenuredCellIsMarkedBlack(cell)); + + MarkBitmapWord* grayWord; + uintptr_t grayMask; + TenuredChunkBase* chunk = GetCellChunkBase(cell); + chunk->markBits.getMarkWordAndMask(cell, js::gc::ColorBit::GrayOrBlackBit, + &grayWord, &grayMask); + return *grayWord & grayMask; +} + +static MOZ_ALWAYS_INLINE bool TenuredCellIsMarkedGray(const TenuredCell* cell) { + return !TenuredCellIsMarkedBlack(cell) && NonBlackCellIsMarkedGray(cell); +} + +static MOZ_ALWAYS_INLINE bool CellIsMarkedGray(const Cell* cell) { + MOZ_ASSERT(cell); + if (js::gc::IsInsideNursery(cell)) { + return false; + } + return TenuredCellIsMarkedGray(reinterpret_cast<const TenuredCell*>(cell)); +} + +extern JS_PUBLIC_API bool CanCheckGrayBits(const TenuredCell* cell); + +extern JS_PUBLIC_API bool CellIsMarkedGrayIfKnown(const TenuredCell* cell); + +#ifdef DEBUG +extern JS_PUBLIC_API void AssertCellIsNotGray(const Cell* cell); + +extern JS_PUBLIC_API bool ObjectIsMarkedBlack(const JSObject* obj); +#endif + +MOZ_ALWAYS_INLINE bool CellHasStoreBuffer(const Cell* cell) { + return GetCellChunkBase(cell)->storeBuffer; +} + +} /* namespace detail */ + +MOZ_ALWAYS_INLINE bool IsInsideNursery(const Cell* cell) { + MOZ_ASSERT(cell); + return detail::CellHasStoreBuffer(cell); +} + +MOZ_ALWAYS_INLINE bool IsInsideNursery(const TenuredCell* cell) { + MOZ_ASSERT(cell); + MOZ_ASSERT(!IsInsideNursery(reinterpret_cast<const Cell*>(cell))); + return false; +} + +// Allow use before the compiler knows the derivation of JSObject, JSString, and +// JS::BigInt. +MOZ_ALWAYS_INLINE bool IsInsideNursery(const JSObject* obj) { + return IsInsideNursery(reinterpret_cast<const Cell*>(obj)); +} +MOZ_ALWAYS_INLINE bool IsInsideNursery(const JSString* str) { + return IsInsideNursery(reinterpret_cast<const Cell*>(str)); +} +MOZ_ALWAYS_INLINE bool IsInsideNursery(const JS::BigInt* bi) { + return IsInsideNursery(reinterpret_cast<const Cell*>(bi)); +} + +MOZ_ALWAYS_INLINE bool IsCellPointerValid(const void* ptr) { + auto addr = uintptr_t(ptr); + if (addr < ChunkSize || addr % CellAlignBytes != 0) { + return false; + } + + auto* cell = reinterpret_cast<const Cell*>(ptr); + if (!IsInsideNursery(cell)) { + return detail::GetTenuredGCThingZone(addr) != nullptr; + } + + return true; +} + +MOZ_ALWAYS_INLINE bool IsCellPointerValidOrNull(const void* cell) { + if (!cell) { + return true; + } + return IsCellPointerValid(cell); +} + +} /* namespace gc */ +} /* namespace js */ + +namespace JS { + +static MOZ_ALWAYS_INLINE Zone* GetTenuredGCThingZone(GCCellPtr thing) { + MOZ_ASSERT(!js::gc::IsInsideNursery(thing.asCell())); + return js::gc::detail::GetTenuredGCThingZone(thing.unsafeAsUIntPtr()); +} + +extern JS_PUBLIC_API Zone* GetNurseryCellZone(js::gc::Cell* cell); + +static MOZ_ALWAYS_INLINE Zone* GetGCThingZone(GCCellPtr thing) { + if (!js::gc::IsInsideNursery(thing.asCell())) { + return js::gc::detail::GetTenuredGCThingZone(thing.unsafeAsUIntPtr()); + } + + return GetNurseryCellZone(thing.asCell()); +} + +static MOZ_ALWAYS_INLINE Zone* GetStringZone(JSString* str) { + if (!js::gc::IsInsideNursery(str)) { + return js::gc::detail::GetTenuredGCThingZone( + reinterpret_cast<uintptr_t>(str)); + } + return GetNurseryCellZone(reinterpret_cast<js::gc::Cell*>(str)); +} + +extern JS_PUBLIC_API Zone* GetObjectZone(JSObject* obj); + +static MOZ_ALWAYS_INLINE bool GCThingIsMarkedGray(GCCellPtr thing) { + js::gc::Cell* cell = thing.asCell(); + if (IsInsideNursery(cell)) { + return false; + } + + auto* tenuredCell = reinterpret_cast<js::gc::TenuredCell*>(cell); + return js::gc::detail::CellIsMarkedGrayIfKnown(tenuredCell); +} + +// Specialised gray marking check for use by the cycle collector. This is not +// called during incremental GC or when the gray bits are invalid. +static MOZ_ALWAYS_INLINE bool GCThingIsMarkedGrayInCC(GCCellPtr thing) { + js::gc::Cell* cell = thing.asCell(); + if (IsInsideNursery(cell)) { + return false; + } + + auto* tenuredCell = reinterpret_cast<js::gc::TenuredCell*>(cell); + if (!js::gc::detail::TenuredCellIsMarkedGray(tenuredCell)) { + return false; + } + + MOZ_ASSERT(js::gc::detail::CanCheckGrayBits(tenuredCell)); + + return true; +} + +extern JS_PUBLIC_API JS::TraceKind GCThingTraceKind(void* thing); + +extern JS_PUBLIC_API void EnableNurseryStrings(JSContext* cx); + +extern JS_PUBLIC_API void DisableNurseryStrings(JSContext* cx); + +extern JS_PUBLIC_API void EnableNurseryBigInts(JSContext* cx); + +extern JS_PUBLIC_API void DisableNurseryBigInts(JSContext* cx); + +/* + * Returns true when writes to GC thing pointers (and reads from weak pointers) + * must call an incremental barrier. This is generally only true when running + * mutator code in-between GC slices. At other times, the barrier may be elided + * for performance. + */ +extern JS_PUBLIC_API bool IsIncrementalBarrierNeeded(JSContext* cx); + +/* + * Notify the GC that a reference to a JSObject is about to be overwritten. + * This method must be called if IsIncrementalBarrierNeeded. + */ +extern JS_PUBLIC_API void IncrementalPreWriteBarrier(JSObject* obj); + +/* + * Notify the GC that a reference to a tenured GC cell is about to be + * overwritten. This method must be called if IsIncrementalBarrierNeeded. + */ +extern JS_PUBLIC_API void IncrementalPreWriteBarrier(GCCellPtr thing); + +/** + * Unsets the gray bit for anything reachable from |thing|. |kind| should not be + * JS::TraceKind::Shape. |thing| should be non-null. The return value indicates + * if anything was unmarked. + */ +extern JS_PUBLIC_API bool UnmarkGrayGCThingRecursively(GCCellPtr thing); + +} // namespace JS + +namespace js { +namespace gc { + +extern JS_PUBLIC_API void PerformIncrementalReadBarrier(JS::GCCellPtr thing); + +static MOZ_ALWAYS_INLINE void ExposeGCThingToActiveJS(JS::GCCellPtr thing) { + // TODO: I'd like to assert !RuntimeHeapIsBusy() here but this gets + // called while we are tracing the heap, e.g. during memory reporting + // (see bug 1313318). + MOZ_ASSERT(!JS::RuntimeHeapIsCollecting()); + + // GC things residing in the nursery cannot be gray: they have no mark bits. + // All live objects in the nursery are moved to tenured at the beginning of + // each GC slice, so the gray marker never sees nursery things. + if (IsInsideNursery(thing.asCell())) { + return; + } + + auto* cell = reinterpret_cast<TenuredCell*>(thing.asCell()); + if (detail::TenuredCellIsMarkedBlack(cell)) { + return; + } + + // GC things owned by other runtimes are always black. + MOZ_ASSERT(!thing.mayBeOwnedByOtherRuntime()); + + auto* zone = JS::shadow::Zone::from(JS::GetTenuredGCThingZone(thing)); + if (zone->needsIncrementalBarrier()) { + PerformIncrementalReadBarrier(thing); + } else if (!zone->isGCPreparing() && detail::NonBlackCellIsMarkedGray(cell)) { + MOZ_ALWAYS_TRUE(JS::UnmarkGrayGCThingRecursively(thing)); + } + + MOZ_ASSERT_IF(!zone->isGCPreparing(), !detail::TenuredCellIsMarkedGray(cell)); +} + +static MOZ_ALWAYS_INLINE void IncrementalReadBarrier(JS::GCCellPtr thing) { + // This is a lighter version of ExposeGCThingToActiveJS that doesn't do gray + // unmarking. + + if (IsInsideNursery(thing.asCell())) { + return; + } + + auto* zone = JS::shadow::Zone::from(JS::GetTenuredGCThingZone(thing)); + auto* cell = reinterpret_cast<TenuredCell*>(thing.asCell()); + if (zone->needsIncrementalBarrier() && + !detail::TenuredCellIsMarkedBlack(cell)) { + // GC things owned by other runtimes are always black. + MOZ_ASSERT(!thing.mayBeOwnedByOtherRuntime()); + PerformIncrementalReadBarrier(thing); + } +} + +template <typename T> +extern JS_PUBLIC_API bool EdgeNeedsSweepUnbarrieredSlow(T* thingp); + +static MOZ_ALWAYS_INLINE bool EdgeNeedsSweepUnbarriered(JSObject** objp) { + // This function does not handle updating nursery pointers. Raw JSObject + // pointers should be updated separately or replaced with + // JS::Heap<JSObject*> which handles this automatically. + MOZ_ASSERT(!JS::RuntimeHeapIsMinorCollecting()); + if (IsInsideNursery(*objp)) { + return false; + } + + auto zone = + JS::shadow::Zone::from(detail::GetTenuredGCThingZone(uintptr_t(*objp))); + if (!zone->isGCSweepingOrCompacting()) { + return false; + } + + return EdgeNeedsSweepUnbarrieredSlow(objp); +} + +} // namespace gc +} // namespace js + +namespace JS { + +/* + * This should be called when an object that is marked gray is exposed to the JS + * engine (by handing it to running JS code or writing it into live JS + * data). During incremental GC, since the gray bits haven't been computed yet, + * we conservatively mark the object black. + */ +static MOZ_ALWAYS_INLINE void ExposeObjectToActiveJS(JSObject* obj) { + MOZ_ASSERT(obj); + MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarrieredSlow(&obj)); + js::gc::ExposeGCThingToActiveJS(GCCellPtr(obj)); +} + +} /* namespace JS */ + +#endif /* js_HeapAPI_h */ diff --git a/js/public/HelperThreadAPI.h b/js/public/HelperThreadAPI.h new file mode 100644 index 0000000000..c877fbb49b --- /dev/null +++ b/js/public/HelperThreadAPI.h @@ -0,0 +1,40 @@ +/* -*- 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/. */ + +/* + * API for supplying an external thread pool to run internal work off the main + * thread. + */ + +#ifndef js_HelperThreadAPI_h +#define js_HelperThreadAPI_h + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +namespace JS { + +// Argument passed to the task callback to indicate whether we're invoking it +// because a new task was added by the JS engine or because we're on a helper +// thread that just finished a task and there are other tasks pending. +enum class DispatchReason { NewTask, FinishedTask }; + +/** + * Set callback to dispatch a tasks to an external thread pool. + * + * When the task runs it should call JS::RunHelperThreadTask. + */ +using HelperThreadTaskCallback = void (*)(DispatchReason reason); +extern JS_PUBLIC_API void SetHelperThreadTaskCallback( + HelperThreadTaskCallback callback, size_t threadCount, size_t stackSize); + +// Function to call from external thread pool to run a helper thread task. +extern JS_PUBLIC_API void RunHelperThreadTask(); + +} // namespace JS + +#endif // js_HelperThreadAPI_h diff --git a/js/public/Id.h b/js/public/Id.h new file mode 100644 index 0000000000..e57c526678 --- /dev/null +++ b/js/public/Id.h @@ -0,0 +1,356 @@ +/* -*- 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 js_Id_h +#define js_Id_h + +// [SMDOC] PropertyKey / jsid +// +// A PropertyKey is an identifier for a property of an object which is either a +// 31-bit unsigned integer, interned string or symbol. +// +// Also, there is an additional PropertyKey value, PropertyKey::Void(), which +// does not occur in JS scripts but may be used to indicate the absence of a +// valid key. A void PropertyKey is not a valid key and only arises as an +// exceptional API return value. Embeddings must not pass a void PropertyKey +// into JSAPI entry points expecting a PropertyKey and do not need to handle +// void keys in hooks receiving a PropertyKey except when explicitly noted in +// the API contract. +// +// A PropertyKey is not implicitly convertible to or from a Value; JS_ValueToId +// or JS_IdToValue must be used instead. +// +// jsid is an alias for JS::PropertyKey. New code should use PropertyKey instead +// of jsid. + +#include "mozilla/Maybe.h" + +#include "jstypes.h" + +#include "js/GCAnnotations.h" +#include "js/HeapAPI.h" +#include "js/RootingAPI.h" +#include "js/TraceKind.h" +#include "js/TracingAPI.h" +#include "js/TypeDecls.h" + +namespace JS { + +enum class SymbolCode : uint32_t; + +class PropertyKey { + uintptr_t asBits_; + + public: + // All keys with the low bit set are integer keys. This means the other type + // tags must all be even. These constants are public only for the JITs. + static constexpr uintptr_t IntTagBit = 0x1; + // Use 0 for StringTypeTag to avoid a bitwise op for atom <-> id conversions. + static constexpr uintptr_t StringTypeTag = 0x0; + static constexpr uintptr_t VoidTypeTag = 0x2; + static constexpr uintptr_t SymbolTypeTag = 0x4; + // (0x6 is unused) + static constexpr uintptr_t TypeMask = 0x7; + + static constexpr uint32_t IntMin = 0; + static constexpr uint32_t IntMax = INT32_MAX; + + constexpr PropertyKey() : asBits_(VoidTypeTag) {} + + static constexpr MOZ_ALWAYS_INLINE PropertyKey fromRawBits(uintptr_t bits) { + PropertyKey id; + id.asBits_ = bits; + return id; + } + + bool operator==(const PropertyKey& rhs) const { + return asBits_ == rhs.asBits_; + } + bool operator!=(const PropertyKey& rhs) const { + return asBits_ != rhs.asBits_; + } + + MOZ_ALWAYS_INLINE bool isVoid() const { + MOZ_ASSERT_IF((asBits_ & TypeMask) == VoidTypeTag, asBits_ == VoidTypeTag); + return asBits_ == VoidTypeTag; + } + + MOZ_ALWAYS_INLINE bool isInt() const { return !!(asBits_ & IntTagBit); } + + MOZ_ALWAYS_INLINE bool isString() const { + return (asBits_ & TypeMask) == StringTypeTag; + } + + MOZ_ALWAYS_INLINE bool isSymbol() const { + return (asBits_ & TypeMask) == SymbolTypeTag; + } + + MOZ_ALWAYS_INLINE bool isGCThing() const { return isString() || isSymbol(); } + + constexpr uintptr_t asRawBits() const { return asBits_; } + + MOZ_ALWAYS_INLINE int32_t toInt() const { + MOZ_ASSERT(isInt()); + uint32_t bits = static_cast<uint32_t>(asBits_) >> 1; + return static_cast<int32_t>(bits); + } + + MOZ_ALWAYS_INLINE JSString* toString() const { + MOZ_ASSERT(isString()); + // Use XOR instead of `& ~TypeMask` because small immediates can be + // encoded more efficiently on some platorms. + return reinterpret_cast<JSString*>(asBits_ ^ StringTypeTag); + } + + MOZ_ALWAYS_INLINE JS::Symbol* toSymbol() const { + MOZ_ASSERT(isSymbol()); + return reinterpret_cast<JS::Symbol*>(asBits_ ^ SymbolTypeTag); + } + + js::gc::Cell* toGCThing() const { + MOZ_ASSERT(isGCThing()); + return reinterpret_cast<js::gc::Cell*>(asBits_ & ~TypeMask); + } + + GCCellPtr toGCCellPtr() const { + js::gc::Cell* thing = toGCThing(); + if (isString()) { + return JS::GCCellPtr(thing, JS::TraceKind::String); + } + MOZ_ASSERT(isSymbol()); + return JS::GCCellPtr(thing, JS::TraceKind::Symbol); + } + + bool isPrivateName() const; + + bool isWellKnownSymbol(JS::SymbolCode code) const; + + // A void PropertyKey. This is equivalent to a PropertyKey created by the + // default constructor. + static constexpr PropertyKey Void() { return PropertyKey(); } + + static constexpr bool fitsInInt(int32_t i) { return i >= 0; } + + static constexpr PropertyKey Int(int32_t i) { + MOZ_ASSERT(fitsInInt(i)); + uint32_t bits = (static_cast<uint32_t>(i) << 1) | IntTagBit; + return PropertyKey::fromRawBits(bits); + } + + static PropertyKey Symbol(JS::Symbol* sym) { + MOZ_ASSERT(sym != nullptr); + MOZ_ASSERT((uintptr_t(sym) & TypeMask) == 0); + MOZ_ASSERT(!js::gc::IsInsideNursery(reinterpret_cast<js::gc::Cell*>(sym))); + return PropertyKey::fromRawBits(uintptr_t(sym) | SymbolTypeTag); + } + + // Must not be used on atoms that are representable as integer PropertyKey. + // Prefer NameToId or AtomToId over this function: + // + // A PropertyName is an atom that does not contain an integer in the range + // [0, UINT32_MAX]. However, PropertyKey can only hold an integer in the range + // [0, IntMax] (where IntMax == 2^31-1). Thus, for the range of integers + // (IntMax, UINT32_MAX], to represent as a 'id', it must be + // the case id.isString() and id.toString()->isIndex(). In most + // cases when creating a PropertyKey, code does not have to care about + // this corner case because: + // + // - When given an arbitrary JSAtom*, AtomToId must be used, which checks for + // integer atoms representable as integer PropertyKey, and does this + // conversion. + // + // - When given a PropertyName*, NameToId can be used which does not need + // to do any dynamic checks. + // + // Thus, it is only the rare third case which needs this function, which + // handles any JSAtom* that is known not to be representable with an int + // PropertyKey. + static PropertyKey NonIntAtom(JSAtom* atom) { + MOZ_ASSERT((uintptr_t(atom) & TypeMask) == 0); + MOZ_ASSERT(PropertyKey::isNonIntAtom(atom)); + return PropertyKey::fromRawBits(uintptr_t(atom) | StringTypeTag); + } + + // The JSAtom/JSString type exposed to embedders is opaque. + static PropertyKey NonIntAtom(JSString* str) { + MOZ_ASSERT((uintptr_t(str) & TypeMask) == 0); + MOZ_ASSERT(PropertyKey::isNonIntAtom(str)); + return PropertyKey::fromRawBits(uintptr_t(str) | StringTypeTag); + } + + // This API can be used by embedders to convert pinned (aka interned) strings, + // as created by JS_AtomizeAndPinString, into PropertyKeys. This means the + // string does not have to be explicitly rooted. + // + // Only use this API when absolutely necessary, otherwise use JS_StringToId. + static PropertyKey fromPinnedString(JSString* str); + + // Internal API! + // All string PropertyKeys are actually atomized. + MOZ_ALWAYS_INLINE bool isAtom() const { return isString(); } + + MOZ_ALWAYS_INLINE bool isAtom(JSAtom* atom) const { + MOZ_ASSERT(PropertyKey::isNonIntAtom(atom)); + return isAtom() && toAtom() == atom; + } + + MOZ_ALWAYS_INLINE JSAtom* toAtom() const { + return reinterpret_cast<JSAtom*>(toString()); + } + MOZ_ALWAYS_INLINE JSLinearString* toLinearString() const { + return reinterpret_cast<JSLinearString*>(toString()); + } + + private: + static bool isNonIntAtom(JSAtom* atom); + static bool isNonIntAtom(JSString* atom); +} JS_HAZ_GC_POINTER; + +} // namespace JS + +using jsid = JS::PropertyKey; + +namespace JS { + +// Handle<PropertyKey> version of PropertyKey::Void(). +extern JS_PUBLIC_DATA const JS::HandleId VoidHandlePropertyKey; + +template <> +struct GCPolicy<jsid> { + static void trace(JSTracer* trc, jsid* idp, const char* name) { + // This should only be called as part of root marking since that's the only + // time we should trace unbarriered GC thing pointers. This will assert if + // called at other times. + TraceRoot(trc, idp, name); + } + static bool isValid(jsid id) { + return !id.isGCThing() || + js::gc::IsCellPointerValid(id.toGCCellPtr().asCell()); + } + + static bool isTenured(jsid id) { + MOZ_ASSERT_IF(id.isGCThing(), + !js::gc::IsInsideNursery(id.toGCCellPtr().asCell())); + return true; + } +}; + +#ifdef DEBUG +MOZ_ALWAYS_INLINE void AssertIdIsNotGray(jsid id) { + if (id.isGCThing()) { + AssertCellIsNotGray(id.toGCCellPtr().asCell()); + } +} +#endif + +/** + * Get one of the well-known symbols defined by ES6 as PropertyKey. This is + * equivalent to calling JS::GetWellKnownSymbol and then creating a PropertyKey. + * + * `which` must be in the range [0, WellKnownSymbolLimit). + */ +extern JS_PUBLIC_API PropertyKey GetWellKnownSymbolKey(JSContext* cx, + SymbolCode which); + +/** + * Generate getter/setter id for given id, by adding "get " or "set " prefix. + */ +extern JS_PUBLIC_API bool ToGetterId( + JSContext* cx, JS::Handle<JS::PropertyKey> id, + JS::MutableHandle<JS::PropertyKey> getterId); +extern JS_PUBLIC_API bool ToSetterId( + JSContext* cx, JS::Handle<JS::PropertyKey> id, + JS::MutableHandle<JS::PropertyKey> setterId); + +} // namespace JS + +namespace js { + +template <> +struct BarrierMethods<jsid> { + static gc::Cell* asGCThingOrNull(jsid id) { + if (id.isGCThing()) { + return id.toGCThing(); + } + return nullptr; + } + static void postWriteBarrier(jsid* idp, jsid prev, jsid next) { + MOZ_ASSERT_IF(next.isString(), !gc::IsInsideNursery(next.toString())); + } + static void exposeToJS(jsid id) { + if (id.isGCThing()) { + js::gc::ExposeGCThingToActiveJS(id.toGCCellPtr()); + } + } + static void readBarrier(jsid id) { + if (id.isGCThing()) { + js::gc::IncrementalReadBarrier(id.toGCCellPtr()); + } + } +}; + +// If the jsid is a GC pointer type, convert to that type and call |f| with the +// pointer and return the result wrapped in a Maybe, otherwise return None(). +template <typename F> +auto MapGCThingTyped(const jsid& id, F&& f) { + if (id.isString()) { + return mozilla::Some(f(id.toString())); + } + if (id.isSymbol()) { + return mozilla::Some(f(id.toSymbol())); + } + MOZ_ASSERT(!id.isGCThing()); + using ReturnType = decltype(f(static_cast<JSString*>(nullptr))); + return mozilla::Maybe<ReturnType>(); +} + +// If the jsid is a GC pointer type, convert to that type and call |f| with the +// pointer. Return whether this happened. +template <typename F> +bool ApplyGCThingTyped(const jsid& id, F&& f) { + return MapGCThingTyped(id, + [&f](auto t) { + f(t); + return true; + }) + .isSome(); +} + +template <typename Wrapper> +class WrappedPtrOperations<JS::PropertyKey, Wrapper> { + const JS::PropertyKey& id() const { + return static_cast<const Wrapper*>(this)->get(); + } + + public: + bool isVoid() const { return id().isVoid(); } + bool isInt() const { return id().isInt(); } + bool isString() const { return id().isString(); } + bool isSymbol() const { return id().isSymbol(); } + bool isGCThing() const { return id().isGCThing(); } + + int32_t toInt() const { return id().toInt(); } + JSString* toString() const { return id().toString(); } + JS::Symbol* toSymbol() const { return id().toSymbol(); } + + bool isPrivateName() const { return id().isPrivateName(); } + + bool isWellKnownSymbol(JS::SymbolCode code) const { + return id().isWellKnownSymbol(code); + } + + uintptr_t asRawBits() const { return id().asRawBits(); } + + // Internal API + bool isAtom() const { return id().isAtom(); } + bool isAtom(JSAtom* atom) const { return id().isAtom(atom); } + JSAtom* toAtom() const { return id().toAtom(); } + JSLinearString* toLinearString() const { return id().toLinearString(); } +}; + +} // namespace js + +#endif /* js_Id_h */ diff --git a/js/public/Initialization.h b/js/public/Initialization.h new file mode 100644 index 0000000000..46715b6c4f --- /dev/null +++ b/js/public/Initialization.h @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* SpiderMonkey initialization and shutdown APIs. */ + +#ifndef js_Initialization_h +#define js_Initialization_h + +#include "mozilla/Span.h" + +#include "jstypes.h" + +struct JS_PUBLIC_API JSContext; + +namespace JS { +namespace detail { + +enum class InitState { Uninitialized = 0, Initializing, Running, ShutDown }; + +/** + * SpiderMonkey's initialization status is tracked here, and it controls things + * that should happen only once across all runtimes. It's an API requirement + * that JS_Init (and JS_ShutDown, if called) be called in a thread-aware + * manner, so this (internal -- embedders, don't use!) variable doesn't need to + * be atomic. + */ +extern JS_PUBLIC_DATA InitState libraryInitState; + +enum class FrontendOnly { No, Yes }; + +extern JS_PUBLIC_API const char* InitWithFailureDiagnostic( + bool isDebugBuild, FrontendOnly frontendOnly = FrontendOnly::No); + +} // namespace detail +} // namespace JS + +// These are equivalent to ICU's |UMemAllocFn|, |UMemReallocFn|, and +// |UMemFreeFn| types. The first argument (called |context| in the ICU docs) +// will always be nullptr and should be ignored. +typedef void* (*JS_ICUAllocFn)(const void*, size_t size); +typedef void* (*JS_ICUReallocFn)(const void*, void* p, size_t size); +typedef void (*JS_ICUFreeFn)(const void*, void* p); + +/** + * This function can be used to track memory used by ICU. If it is called, it + * *must* be called before JS_Init. Don't use it unless you know what you're + * doing! + */ +extern JS_PUBLIC_API bool JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, + JS_ICUReallocFn reallocFn, + JS_ICUFreeFn freeFn); + +/** + * Initialize SpiderMonkey, returning true only if initialization succeeded. + * Once this method has succeeded, it is safe to call JS_NewContext and other + * JSAPI methods. + * + * This method must be called before any other JSAPI method is used on any + * thread. Once it has been used, it is safe to call any JSAPI method, and it + * remains safe to do so until JS_ShutDown is correctly called. + * + * It is currently not possible to initialize SpiderMonkey multiple times (that + * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so + * again). This restriction may eventually be lifted. + */ +inline bool JS_Init(void) { +#ifdef DEBUG + return !JS::detail::InitWithFailureDiagnostic(true); +#else + return !JS::detail::InitWithFailureDiagnostic(false); +#endif +} + +/** + * A variant of JS_Init. On success it returns nullptr. On failure it returns a + * pointer to a string literal that describes how initialization failed, which + * can be useful for debugging purposes. + */ +inline const char* JS_InitWithFailureDiagnostic(void) { +#ifdef DEBUG + return JS::detail::InitWithFailureDiagnostic(true); +#else + return JS::detail::InitWithFailureDiagnostic(false); +#endif +} + +/** + * A lightweight variant of JS_Init, which skips initializing runtime-specific + * part. + * Suitable for processes where only JSContext-free stencil-APIs are used. + */ +inline bool JS_FrontendOnlyInit(void) { +#ifdef DEBUG + return !JS::detail::InitWithFailureDiagnostic(true, + JS::detail::FrontendOnly::Yes); +#else + return !JS::detail::InitWithFailureDiagnostic(false, + JS::detail::FrontendOnly::Yes); +#endif +} + +/* + * Returns true if SpiderMonkey has been initialized successfully, even if it + * has possibly been shut down. + * + * Note that it is the responsibility of the embedder to call JS_Init() and + * JS_ShutDown() at the correct times, and therefore this API should ideally not + * be necessary to use. This is only intended to be used in cases where the + * embedder isn't in full control of deciding whether to initialize SpiderMonkey + * or hand off the task to another consumer. + */ +inline bool JS_IsInitialized(void) { + return JS::detail::libraryInitState >= JS::detail::InitState::Running; +} + +namespace JS { + +// Reference to a sequence of bytes. +// TODO: This type should be Span<cont uint8_t> (Bug 1709135) +using SelfHostedCache = mozilla::Span<const uint8_t>; + +// Callback function used to copy the SelfHosted content to memory or to disk. +using SelfHostedWriter = bool (*)(JSContext*, SelfHostedCache); + +/* + * Initialize the runtime's self-hosted code. Embeddings should call this + * exactly once per runtime/context, before the first JS_NewGlobalObject + * call. + * + * This function parses the self-hosted code, except if the provided cache span + * is not empty, in which case the self-hosted content is decoded from the span. + * + * The cached content provided as argument, when non-empty, should come from the + * a previous execution of JS::InitSelfHostedCode where a writer was registered. + * The content should come from the same version of the binary, otherwise this + * would cause an error. + * + * The cached content provided with the Span should remain alive until + * JS_Shutdown is called. + * + * The writer callback given as argument would be called by when the result of + * the parser is ready to be cached. The writer is in charge of saving the + * content in memory or on disk. The span given as argument of the writer only + * last for the time of the call, and contains the content to be saved. + * + * The writer is not called if the cached content given as argument of + * InitSelfHostedCode is non-empty. + * + * Errors returned by the writer callback would bubble up through + * JS::InitSelfHostedCode. + * + * The cached content provided by the writer callback is safe to reuse across + * threads, and even across multiple executions as long as the executable is + * identical. + * + * NOTE: This may not set a pending exception in the case of OOM since this + * runs very early in startup. + */ +JS_PUBLIC_API bool InitSelfHostedCode(JSContext* cx, + SelfHostedCache cache = nullptr, + SelfHostedWriter writer = nullptr); + +/* + * Permanently disable the JIT backend for this process. This disables the JS + * Baseline Interpreter, JIT compilers, regular expression JIT and support for + * WebAssembly. + * + * If called, this *must* be called before JS_Init. + */ +JS_PUBLIC_API void DisableJitBackend(); + +} // namespace JS + +/** + * Destroy free-standing resources allocated by SpiderMonkey, not associated + * with any runtime, context, or other structure. + * + * This method should be called after all other JSAPI data has been properly + * cleaned up: every new runtime must have been destroyed, every new context + * must have been destroyed, and so on. Calling this method before all other + * resources have been destroyed has undefined behavior. + * + * Failure to call this method, at present, has no adverse effects other than + * leaking memory. This may not always be the case; it's recommended that all + * embedders call this method when all other JSAPI operations have completed. + * + * It is currently not possible to initialize SpiderMonkey multiple times (that + * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so + * again). This restriction may eventually be lifted. + */ +extern JS_PUBLIC_API void JS_ShutDown(void); + +/** + * A variant of JS_ShutDown for process which used JS_FrontendOnlyInit instead + * of JS_Init. + */ +extern JS_PUBLIC_API void JS_FrontendOnlyShutDown(void); + +#if defined(ENABLE_WASM_SIMD) && \ + (defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)) +namespace JS { +// Enable support for AVX instructions in the JIT/Wasm backend on x86/x64 +// platforms. Must be called before JS_Init*. +void SetAVXEnabled(bool enabled); +} // namespace JS +#endif + +#endif /* js_Initialization_h */ diff --git a/js/public/Interrupt.h b/js/public/Interrupt.h new file mode 100644 index 0000000000..3e7ead7b28 --- /dev/null +++ b/js/public/Interrupt.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_Interrupt_h +#define js_Interrupt_h + +#include "jstypes.h" + +struct JS_PUBLIC_API JSContext; + +using JSInterruptCallback = bool (*)(JSContext*); + +extern JS_PUBLIC_API bool JS_CheckForInterrupt(JSContext* cx); + +/* + * These functions allow setting an interrupt callback that will be called + * from the JS thread some time after any thread triggered the callback using + * JS_RequestInterruptCallback(cx). + * + * To schedule the GC and for other activities the engine internally triggers + * interrupt callbacks. The embedding should thus not rely on callbacks being + * triggered through the external API only. + * + * Important note: Additional callbacks can occur inside the callback handler + * if it re-enters the JS engine. The embedding must ensure that the callback + * is disconnected before attempting such re-entry. + */ +extern JS_PUBLIC_API bool JS_AddInterruptCallback(JSContext* cx, + JSInterruptCallback callback); + +extern JS_PUBLIC_API bool JS_DisableInterruptCallback(JSContext* cx); + +extern JS_PUBLIC_API void JS_ResetInterruptCallback(JSContext* cx, bool enable); + +extern JS_PUBLIC_API void JS_RequestInterruptCallback(JSContext* cx); + +extern JS_PUBLIC_API void JS_RequestInterruptCallbackCanWait(JSContext* cx); + +#endif // js_Interrupt_h diff --git a/js/public/JSON.h b/js/public/JSON.h new file mode 100644 index 0000000000..5337fc5e95 --- /dev/null +++ b/js/public/JSON.h @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * JSON serialization and deserialization operations. + */ + +#ifndef js_JSON_h +#define js_JSON_h + +#include <stdint.h> // uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/TypeDecls.h" + +using JSONWriteCallback = bool (*)(const char16_t* buf, uint32_t len, + void* data); + +/** + * Performs the JSON.stringify operation, as specified by ECMAScript, except + * writing stringified data by exactly one call of |callback|, passing |data| as + * argument. + * + * In cases where JSON.stringify would return undefined, this function calls + * |callback| with the string "null". + */ +extern JS_PUBLIC_API bool JS_Stringify(JSContext* cx, + JS::MutableHandle<JS::Value> value, + JS::Handle<JSObject*> replacer, + JS::Handle<JS::Value> space, + JSONWriteCallback callback, void* data); + +namespace JS { + +/** + * An API akin to JS_Stringify but with the goal of not having observable + * side-effects when the stringification is performed. This means it does not + * allow a replacer or a custom space and has the following constraints on its + * input: + * + * 1) The input must be a plain object or array, not an abitrary value. + * 2) Every value in the graph reached by the algorithm starting with this + * object must be one of the following: null, undefined, a string (NOT a + * string object!), a boolean, a finite number (i.e. no NaN or Infinity or + * -Infinity), a plain object with no accessor properties, or an Array with + * no holes. + * + * The actual behavior differs from JS_Stringify only in asserting the above and + * NOT attempting to get the "toJSON" property from things, since that could + * clearly have side-effects. + */ +extern JS_PUBLIC_API bool ToJSONMaybeSafely(JSContext* cx, + JS::Handle<JSObject*> input, + JSONWriteCallback callback, + void* data); + +/** + * Performs the JSON.stringify operation, as specified by ECMAScript, except + * writing stringified data by one call of |callback|, passing |data| as + * argument. + * + * In cases where JSON.stringify would return undefined, this function does not + * call |callback| at all. + */ +extern JS_PUBLIC_API bool ToJSON(JSContext* cx, Handle<Value> value, + Handle<JSObject*> replacer, + Handle<Value> space, + JSONWriteCallback callback, void* data); + +} /* namespace JS */ + +/** + * Performs the JSON.parse operation as specified by ECMAScript. + */ +extern JS_PUBLIC_API bool JS_ParseJSON(JSContext* cx, const char16_t* chars, + uint32_t len, + JS::MutableHandle<JS::Value> vp); + +/** + * Performs the JSON.parse operation as specified by ECMAScript. + */ +extern JS_PUBLIC_API bool JS_ParseJSON(JSContext* cx, JS::Handle<JSString*> str, + JS::MutableHandle<JS::Value> vp); + +/** + * Performs the JSON.parse operation as specified by ECMAScript. + */ +extern JS_PUBLIC_API bool JS_ParseJSON(JSContext* cx, + const JS::Latin1Char* chars, + uint32_t len, + JS::MutableHandle<JS::Value> vp); + +/** + * Performs the JSON.parse operation as specified by ECMAScript, using the + * given |reviver| argument as the corresponding optional argument to that + * function. + */ +extern JS_PUBLIC_API bool JS_ParseJSONWithReviver( + JSContext* cx, const char16_t* chars, uint32_t len, + JS::Handle<JS::Value> reviver, JS::MutableHandle<JS::Value> vp); + +/** + * Performs the JSON.parse operation as specified by ECMAScript, using the + * given |reviver| argument as the corresponding optional argument to that + * function. + */ +extern JS_PUBLIC_API bool JS_ParseJSONWithReviver( + JSContext* cx, JS::Handle<JSString*> str, JS::Handle<JS::Value> reviver, + JS::MutableHandle<JS::Value> vp); + +namespace JS { + +/** + * Returns true if the given text is valid JSON. + */ +extern JS_PUBLIC_API bool IsValidJSON(const JS::Latin1Char* chars, + uint32_t len); +extern JS_PUBLIC_API bool IsValidJSON(const char16_t* chars, uint32_t len); + +} // namespace JS + +#endif /* js_JSON_h */ diff --git a/js/public/JitCodeAPI.h b/js/public/JitCodeAPI.h new file mode 100644 index 0000000000..20a33a778c --- /dev/null +++ b/js/public/JitCodeAPI.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* SpiderMonkey API for obtaining JitCode information. */ + +#ifndef js_JitCodeAPI_h +#define js_JitCodeAPI_h + +#include "js/AllocPolicy.h" +#include "js/Initialization.h" +#include "js/Printf.h" +#include "js/Vector.h" + +namespace JS { + +enum class JitTier { Baseline, IC, Ion, Other }; + +class JitOpcodeDictionary { + typedef js::Vector<UniqueChars, 0, js::SystemAllocPolicy> StringVector; + + public: + JitOpcodeDictionary(); + + StringVector& GetBaselineDictionary() { return baselineDictionary; } + StringVector& GetIonDictionary() { return ionDictionary; } + StringVector& GetInlineCacheDictionary() { return icDictionary; } + + private: + StringVector baselineDictionary; + StringVector icDictionary; + StringVector ionDictionary; +}; + +struct JitCodeSourceInfo { + UniqueChars filename; + uint32_t offset = 0; + uint32_t lineno = 0; + uint32_t colno = 0; +}; + +struct JitCodeIRInfo { + uint32_t offset = 0; + uint32_t opcode = 0; + UniqueChars str; +}; + +typedef js::Vector<JitCodeSourceInfo, 0, js::SystemAllocPolicy> + SourceInfoVector; +typedef js::Vector<JitCodeIRInfo, 0, js::SystemAllocPolicy> IRInfoVector; + +struct JitCodeRecord { + UniqueChars functionName; + uint64_t code_addr = 0; + uint32_t instructionSize = 0; + JitTier tier = JitTier::Other; + + SourceInfoVector sourceInfo; + IRInfoVector irInfo; +}; + +class JitCodeIterator { + void getDataForIndex(size_t iteratorIndex); + + public: + JitCodeIterator(); + ~JitCodeIterator(); + + void operator++(int) { + iteratorIndex++; + getDataForIndex(iteratorIndex); + } + + explicit operator bool() const { return data != nullptr; } + + SourceInfoVector& sourceData() { return data->sourceInfo; } + + IRInfoVector& irData() { return data->irInfo; } + + const char* functionName() const { return data->functionName.get(); } + uint64_t code_addr() const { return data->code_addr; } + uint32_t instructionSize() { return data->instructionSize; } + JitTier jit_tier() const { return data->tier; } + + private: + JitCodeRecord* data = nullptr; + size_t iteratorIndex = 0; +}; + +} // namespace JS + +#endif /* js_JitCodeAPI_h */ diff --git a/js/public/LocaleSensitive.h b/js/public/LocaleSensitive.h new file mode 100644 index 0000000000..22a5db04c6 --- /dev/null +++ b/js/public/LocaleSensitive.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * Functions and structures related to locale-sensitive behavior, including + * exposure of the default locale (used by operations like toLocaleString). + */ + +#ifndef js_LocaleSensitive_h +#define js_LocaleSensitive_h + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/RootingAPI.h" // JS::Handle, JS::MutableHandle +#include "js/Utility.h" // JS::UniqueChars +#include "js/Value.h" // JS::Value + +struct JS_PUBLIC_API JSContext; +struct JS_PUBLIC_API JSRuntime; +class JS_PUBLIC_API JSString; + +/** + * Set the default locale for the ECMAScript Internationalization API + * (Intl.Collator, Intl.NumberFormat, Intl.DateTimeFormat, and others that will + * arise as time passes). (Note that the Internationalization API encourages + * clients to specify their own locales; this default locale is only used when + * no locale is specified, e.g. calling a toLocaleString function without + * passing a locale argument to it.) + * + * The locale string remains owned by the caller. + */ +extern JS_PUBLIC_API bool JS_SetDefaultLocale(JSRuntime* rt, + const char* locale); + +/** + * Return a copy of the default locale for the ECMAScript Internationalization + * API (and for various ECMAScript functions that will invoke it). The locale + * is retrieved from the |JSRuntime| that corresponds to |cx|. + * + * XXX Bug 1483961 means it's difficult to interpret the meaning of a null + * return value for the time being, and we should fix this! + */ +extern JS_PUBLIC_API JS::UniqueChars JS_GetDefaultLocale(JSContext* cx); + +/** Reset the default locale to OS defaults. */ +extern JS_PUBLIC_API void JS_ResetDefaultLocale(JSRuntime* rt); + +using JSLocaleToUpperCase = bool (*)(JSContext* cx, JS::Handle<JSString*> src, + JS::MutableHandle<JS::Value> rval); + +using JSLocaleToLowerCase = bool (*)(JSContext* cx, JS::Handle<JSString*> src, + JS::MutableHandle<JS::Value> rval); + +using JSLocaleCompare = bool (*)(JSContext* cx, JS::Handle<JSString*> src1, + JS::Handle<JSString*> src2, + JS::MutableHandle<JS::Value> rval); + +using JSLocaleToUnicode = bool (*)(JSContext* cx, const char* src, + JS::MutableHandle<JS::Value> rval); + +/** + * A suite of locale-specific string conversion and error message callbacks + * used to implement locale-sensitive behaviors (such as those performed by + * the various toLocaleString and toLocale{Date,Time}String functions). + * + * If SpiderMonkey is compiled --with-intl-api, then #if JS_HAS_INTL_API. In + * this case, SpiderMonkey itself will implement ECMA-402-compliant behavior by + * calling on ICU, and none of the fields in this struct will ever be used. + * (You'll still be able to call the get/set-callbacks functions; they just + * won't affect JavaScript semantics.) + */ +struct JSLocaleCallbacks { + JSLocaleToUpperCase localeToUpperCase; + JSLocaleToLowerCase localeToLowerCase; + JSLocaleCompare localeCompare; + JSLocaleToUnicode localeToUnicode; +}; + +/** + * Set locale callbacks to be used in builds not compiled --with-intl-api. + * |callbacks| must persist as long as the |JSRuntime|. Pass |nullptr| to + * restore default behavior. + */ +extern JS_PUBLIC_API void JS_SetLocaleCallbacks( + JSRuntime* rt, const JSLocaleCallbacks* callbacks); + +/** + * Return the current locale callbacks, which may be nullptr. + */ +extern JS_PUBLIC_API const JSLocaleCallbacks* JS_GetLocaleCallbacks( + JSRuntime* rt); + +#endif /* js_LocaleSensitive_h */ diff --git a/js/public/MapAndSet.h b/js/public/MapAndSet.h new file mode 100644 index 0000000000..8963322ff4 --- /dev/null +++ b/js/public/MapAndSet.h @@ -0,0 +1,85 @@ +/* -*- 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/. */ + +/* + * Weak Maps. + */ + +#ifndef js_MapAndSet_h +#define js_MapAndSet_h + +#include "jspubtd.h" + +namespace JS { + +/* + * Map + */ +extern JS_PUBLIC_API JSObject* NewMapObject(JSContext* cx); + +extern JS_PUBLIC_API uint32_t MapSize(JSContext* cx, HandleObject obj); + +extern JS_PUBLIC_API bool MapGet(JSContext* cx, HandleObject obj, + HandleValue key, MutableHandleValue rval); + +extern JS_PUBLIC_API bool MapHas(JSContext* cx, HandleObject obj, + HandleValue key, bool* rval); + +extern JS_PUBLIC_API bool MapSet(JSContext* cx, HandleObject obj, + HandleValue key, HandleValue val); + +extern JS_PUBLIC_API bool MapDelete(JSContext* cx, HandleObject obj, + HandleValue key, bool* rval); + +extern JS_PUBLIC_API bool MapClear(JSContext* cx, HandleObject obj); + +extern JS_PUBLIC_API bool MapKeys(JSContext* cx, HandleObject obj, + MutableHandleValue rval); + +extern JS_PUBLIC_API bool MapValues(JSContext* cx, HandleObject obj, + MutableHandleValue rval); + +extern JS_PUBLIC_API bool MapEntries(JSContext* cx, HandleObject obj, + MutableHandleValue rval); + +extern JS_PUBLIC_API bool MapForEach(JSContext* cx, HandleObject obj, + HandleValue callbackFn, + HandleValue thisVal); + +/* + * Set + */ +extern JS_PUBLIC_API JSObject* NewSetObject(JSContext* cx); + +extern JS_PUBLIC_API uint32_t SetSize(JSContext* cx, HandleObject obj); + +extern JS_PUBLIC_API bool SetHas(JSContext* cx, HandleObject obj, + HandleValue key, bool* rval); + +extern JS_PUBLIC_API bool SetDelete(JSContext* cx, HandleObject obj, + HandleValue key, bool* rval); + +extern JS_PUBLIC_API bool SetAdd(JSContext* cx, HandleObject obj, + HandleValue key); + +extern JS_PUBLIC_API bool SetClear(JSContext* cx, HandleObject obj); + +extern JS_PUBLIC_API bool SetKeys(JSContext* cx, HandleObject obj, + MutableHandleValue rval); + +extern JS_PUBLIC_API bool SetValues(JSContext* cx, HandleObject obj, + MutableHandleValue rval); + +extern JS_PUBLIC_API bool SetEntries(JSContext* cx, HandleObject obj, + MutableHandleValue rval); + +extern JS_PUBLIC_API bool SetForEach(JSContext* cx, HandleObject obj, + HandleValue callbackFn, + HandleValue thisVal); + +} // namespace JS + +#endif // js_MapAndSet_h diff --git a/js/public/MemoryCallbacks.h b/js/public/MemoryCallbacks.h new file mode 100644 index 0000000000..dad63915d4 --- /dev/null +++ b/js/public/MemoryCallbacks.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_MemoryCallbacks_h +#define js_MemoryCallbacks_h + +#include "jstypes.h" + +struct JS_PUBLIC_API JSContext; + +namespace JS { + +/** + * If a large allocation fails when calling pod_{calloc,realloc}CanGC, the JS + * engine may call the large-allocation-failure callback, if set, to allow the + * embedding to flush caches, possibly perform shrinking GCs, etc. to make some + * room. The allocation will then be retried (and may still fail.) This callback + * can be called on any thread and must be set at most once in a process. + */ + +using LargeAllocationFailureCallback = void (*)(); + +extern JS_PUBLIC_API void SetProcessLargeAllocationFailureCallback( + LargeAllocationFailureCallback afc); + +/** + * Unlike the error reporter, which is only called if the exception for an OOM + * bubbles up and is not caught, the OutOfMemoryCallback is called immediately + * at the OOM site to allow the embedding to capture the current state of heap + * allocation before anything is freed. If the large-allocation-failure callback + * is called at all (not all allocation sites call the large-allocation-failure + * callback on failure), it is called before the out-of-memory callback; the + * out-of-memory callback is only called if the allocation still fails after the + * large-allocation-failure callback has returned. + */ + +using OutOfMemoryCallback = void (*)(JSContext*, void*); + +extern JS_PUBLIC_API void SetOutOfMemoryCallback(JSContext* cx, + OutOfMemoryCallback cb, + void* data); + +} // namespace JS + +#endif // js_MemoryCallbacks_h diff --git a/js/public/MemoryFunctions.h b/js/public/MemoryFunctions.h new file mode 100644 index 0000000000..f0417f5eb3 --- /dev/null +++ b/js/public/MemoryFunctions.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* Low-level memory-allocation functions. */ + +#ifndef js_MemoryFunctions_h +#define js_MemoryFunctions_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; +struct JS_PUBLIC_API JSRuntime; + +extern JS_PUBLIC_API void* JS_malloc(JSContext* cx, size_t nbytes); + +extern JS_PUBLIC_API void* JS_realloc(JSContext* cx, void* p, size_t oldBytes, + size_t newBytes); + +/** + * A wrapper for |js_free(p)| that may delay |js_free(p)| invocation as a + * performance optimization. |cx| may be nullptr. + */ +extern JS_PUBLIC_API void JS_free(JSContext* cx, void* p); + +/** + * Same as above, but for buffers that will be used with the BYOB + * (Bring Your Own Buffer) JSString creation functions, such as + * JS_NewLatin1String and JS_NewUCString + */ +extern JS_PUBLIC_API void* JS_string_malloc(JSContext* cx, size_t nbytes); + +extern JS_PUBLIC_API void* JS_string_realloc(JSContext* cx, void* p, + size_t oldBytes, size_t newBytes); + +extern JS_PUBLIC_API void JS_string_free(JSContext* cx, void* p); + +namespace JS { + +/** + * The different possible memory uses to pass to Add/RemoveAssociatedMemory. + */ +#define JS_FOR_EACH_PUBLIC_MEMORY_USE(_) \ + _(XPCWrappedNative) \ + _(DOMBinding) \ + _(CTypeFFIType) \ + _(CTypeFFITypeElements) \ + _(CTypeFunctionInfo) \ + _(CTypeFieldInfo) \ + _(CDataBufferPtr) \ + _(CDataBuffer) \ + _(CClosureInfo) \ + _(CTypesInt64) \ + _(Embedding1) \ + _(Embedding2) \ + _(Embedding3) \ + _(Embedding4) \ + _(Embedding5) + +enum class MemoryUse : uint8_t { +#define DEFINE_MEMORY_USE(Name) Name, + JS_FOR_EACH_PUBLIC_MEMORY_USE(DEFINE_MEMORY_USE) +#undef DEFINE_MEMORY_USE +}; + +/** + * Advise the GC of external memory owned by a JSObject. This is used to + * determine when to collect zones. Calls must be matched by calls to + * RemoveAssociatedMemory() when the memory is deallocated or no longer owned by + * the object. + */ +extern JS_PUBLIC_API void AddAssociatedMemory(JSObject* obj, size_t nbytes, + MemoryUse use); + +/** + * Advise the GC that external memory reported by JS::AddAssociatedMemory() is + * no longer owned by a JSObject. Calls must match those to + * AddAssociatedMemory(). + */ +extern JS_PUBLIC_API void RemoveAssociatedMemory(JSObject* obj, size_t nbytes, + MemoryUse use); + +} // namespace JS + +#endif /* js_MemoryFunctions_h */ diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h new file mode 100644 index 0000000000..96718f99de --- /dev/null +++ b/js/public/MemoryMetrics.h @@ -0,0 +1,916 @@ +/* -*- 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 js_MemoryMetrics_h +#define js_MemoryMetrics_h + +// These declarations are highly likely to change in the future. Depend on them +// at your own risk. + +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" + +#include <string.h> +#include <type_traits> + +#include "jstypes.h" + +#include "js/AllocPolicy.h" +#include "js/HashTable.h" +#include "js/TraceKind.h" +#include "js/TypeDecls.h" +#include "js/Utility.h" +#include "js/Vector.h" + +class nsISupports; // Needed for ObjectPrivateVisitor. + +namespace js { +class SystemAllocPolicy; +} + +namespace mozilla { +struct CStringHasher; +} + +namespace JS { +class JS_PUBLIC_API AutoRequireNoGC; + +struct TabSizes { + TabSizes() = default; + + enum Kind { Objects, Strings, Private, Other }; + + void add(Kind kind, size_t n) { + switch (kind) { + case Objects: + objects_ += n; + break; + case Strings: + strings_ += n; + break; + case Private: + private_ += n; + break; + case Other: + other_ += n; + break; + default: + MOZ_CRASH("bad TabSizes kind"); + } + } + + size_t objects_ = 0; + size_t strings_ = 0; + size_t private_ = 0; + size_t other_ = 0; +}; + +/** These are the measurements used by Servo. */ +struct ServoSizes { + ServoSizes() = default; + + enum Kind { + GCHeapUsed, + GCHeapUnused, + GCHeapAdmin, + GCHeapDecommitted, + MallocHeap, + NonHeap, + Ignore + }; + + void add(Kind kind, size_t n) { + switch (kind) { + case GCHeapUsed: + gcHeapUsed += n; + break; + case GCHeapUnused: + gcHeapUnused += n; + break; + case GCHeapAdmin: + gcHeapAdmin += n; + break; + case GCHeapDecommitted: + gcHeapDecommitted += n; + break; + case MallocHeap: + mallocHeap += n; + break; + case NonHeap: + nonHeap += n; + break; + case Ignore: /* do nothing */ + break; + default: + MOZ_CRASH("bad ServoSizes kind"); + } + } + + size_t gcHeapUsed = 0; + size_t gcHeapUnused = 0; + size_t gcHeapAdmin = 0; + size_t gcHeapDecommitted = 0; + size_t mallocHeap = 0; + size_t nonHeap = 0; +}; + +} // namespace JS + +namespace js { + +/** + * In memory reporting, we have concept of "sundries", line items which are too + * small to be worth reporting individually. Under some circumstances, a memory + * reporter gets tossed into the sundries bucket if it's smaller than + * MemoryReportingSundriesThreshold() bytes. + * + * We need to define this value here, rather than in the code which actually + * generates the memory reports, because NotableStringInfo uses this value. + */ +JS_PUBLIC_API size_t MemoryReportingSundriesThreshold(); + +/** + * This hash policy avoids flattening ropes (which perturbs the site being + * measured and requires a JSContext) at the expense of doing a FULL ROPE COPY + * on every hash and match! Beware. + */ +struct InefficientNonFlatteningStringHashPolicy { + typedef JSString* Lookup; + static HashNumber hash(const Lookup& l); + static bool match(const JSString* const& k, const Lookup& l); +}; + +// This file features many classes with numerous size_t fields, and each such +// class has one or more methods that need to operate on all of these fields. +// Writing these individually is error-prone -- it's easy to add a new field +// without updating all the required methods. So we define a single macro list +// in each class to name the fields (and notable characteristics of them), and +// then use the following macros to transform those lists into the required +// methods. +// +// - The |tabKind| value is used when measuring TabSizes. +// +// - The |servoKind| value is used when measuring ServoSizes and also for +// the various sizeOfLiveGCThings() methods. +// +// In some classes, one or more of the macro arguments aren't used. We use '_' +// for those. +// +#define DECL_SIZE_ZERO(tabKind, servoKind, mSize) size_t mSize = 0; +#define ADD_OTHER_SIZE(tabKind, servoKind, mSize) mSize += other.mSize; +#define SUB_OTHER_SIZE(tabKind, servoKind, mSize) \ + MOZ_ASSERT(mSize >= other.mSize); \ + mSize -= other.mSize; +#define ADD_SIZE_TO_N(tabKind, servoKind, mSize) n += mSize; +#define ADD_SIZE_TO_N_IF_LIVE_GC_THING(tabKind, servoKind, mSize) \ + /* Avoid self-comparison warnings by comparing enums indirectly. */ \ + n += (std::is_same_v<int[ServoSizes::servoKind], \ + int[ServoSizes::GCHeapUsed]>) \ + ? mSize \ + : 0; +#define ADD_TO_TAB_SIZES(tabKind, servoKind, mSize) \ + sizes->add(JS::TabSizes::tabKind, mSize); +#define ADD_TO_SERVO_SIZES(tabKind, servoKind, mSize) \ + sizes->add(JS::ServoSizes::servoKind, mSize); + +} // namespace js + +namespace JS { + +struct ClassInfo { +#define FOR_EACH_SIZE(MACRO) \ + MACRO(Objects, GCHeapUsed, objectsGCHeap) \ + MACRO(Objects, MallocHeap, objectsMallocHeapSlots) \ + MACRO(Objects, MallocHeap, objectsMallocHeapElementsNormal) \ + MACRO(Objects, MallocHeap, objectsMallocHeapElementsAsmJS) \ + MACRO(Objects, MallocHeap, objectsMallocHeapGlobalData) \ + MACRO(Objects, MallocHeap, objectsMallocHeapGlobalVarNamesSet) \ + MACRO(Objects, MallocHeap, objectsMallocHeapMisc) \ + MACRO(Objects, NonHeap, objectsNonHeapElementsNormal) \ + MACRO(Objects, NonHeap, objectsNonHeapElementsShared) \ + MACRO(Objects, NonHeap, objectsNonHeapElementsWasm) \ + MACRO(Objects, NonHeap, objectsNonHeapElementsWasmShared) \ + MACRO(Objects, NonHeap, objectsNonHeapCodeWasm) + + ClassInfo() = default; + + void add(const ClassInfo& other) { FOR_EACH_SIZE(ADD_OTHER_SIZE); } + + void subtract(const ClassInfo& other) { FOR_EACH_SIZE(SUB_OTHER_SIZE); } + + size_t sizeOfAllThings() const { + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N); + return n; + } + + bool isNotable() const { + static const size_t NotabilityThreshold = 16 * 1024; + return sizeOfAllThings() >= NotabilityThreshold; + } + + size_t sizeOfLiveGCThings() const { + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING); + return n; + } + + void addToTabSizes(TabSizes* sizes) const { FOR_EACH_SIZE(ADD_TO_TAB_SIZES); } + + void addToServoSizes(ServoSizes* sizes) const { + FOR_EACH_SIZE(ADD_TO_SERVO_SIZES); + } + + FOR_EACH_SIZE(DECL_SIZE_ZERO); + +#undef FOR_EACH_SIZE +}; + +struct ShapeInfo { +#define FOR_EACH_SIZE(MACRO) \ + MACRO(Other, GCHeapUsed, shapesGCHeapShared) \ + MACRO(Other, GCHeapUsed, shapesGCHeapDict) \ + MACRO(Other, GCHeapUsed, shapesGCHeapBase) \ + MACRO(Other, MallocHeap, shapesMallocHeapCache) + + ShapeInfo() = default; + + void add(const ShapeInfo& other) { FOR_EACH_SIZE(ADD_OTHER_SIZE); } + + void subtract(const ShapeInfo& other) { FOR_EACH_SIZE(SUB_OTHER_SIZE); } + + size_t sizeOfAllThings() const { + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N); + return n; + } + + size_t sizeOfLiveGCThings() const { + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING); + return n; + } + + void addToTabSizes(TabSizes* sizes) const { FOR_EACH_SIZE(ADD_TO_TAB_SIZES); } + + void addToServoSizes(ServoSizes* sizes) const { + FOR_EACH_SIZE(ADD_TO_SERVO_SIZES); + } + + FOR_EACH_SIZE(DECL_SIZE_ZERO); + +#undef FOR_EACH_SIZE +}; + +/** + * Holds data about a notable class (one whose combined object and shape + * instances use more than a certain amount of memory) so we can report it + * individually. + * + * The only difference between this class and ClassInfo is that this class + * holds a copy of the filename. + */ +struct NotableClassInfo : public ClassInfo { + NotableClassInfo() = default; + NotableClassInfo(NotableClassInfo&&) = default; + NotableClassInfo(const NotableClassInfo& info) = delete; + + NotableClassInfo(const char* className, const ClassInfo& info); + + UniqueChars className_ = nullptr; +}; + +/** Data for tracking JIT-code memory usage. */ +struct CodeSizes { +#define FOR_EACH_SIZE(MACRO) \ + MACRO(_, NonHeap, ion) \ + MACRO(_, NonHeap, baseline) \ + MACRO(_, NonHeap, regexp) \ + MACRO(_, NonHeap, other) \ + MACRO(_, NonHeap, unused) + + CodeSizes() = default; + + void addToServoSizes(ServoSizes* sizes) const { + FOR_EACH_SIZE(ADD_TO_SERVO_SIZES); + } + + FOR_EACH_SIZE(DECL_SIZE_ZERO); + +#undef FOR_EACH_SIZE +}; + +/** Data for tracking GC memory usage. */ +struct GCSizes { + // |nurseryDecommitted| is marked as NonHeap rather than GCHeapDecommitted + // because we don't consider the nursery to be part of the GC heap. +#define FOR_EACH_SIZE(MACRO) \ + MACRO(_, MallocHeap, marker) \ + MACRO(_, NonHeap, nurseryCommitted) \ + MACRO(_, MallocHeap, nurseryMallocedBuffers) \ + MACRO(_, MallocHeap, nurseryMallocedBlockCache) \ + MACRO(_, MallocHeap, nurseryTrailerBlockSets) \ + MACRO(_, MallocHeap, storeBufferVals) \ + MACRO(_, MallocHeap, storeBufferCells) \ + MACRO(_, MallocHeap, storeBufferSlots) \ + MACRO(_, MallocHeap, storeBufferWholeCells) \ + MACRO(_, MallocHeap, storeBufferGenerics) + + GCSizes() = default; + + void addToServoSizes(ServoSizes* sizes) const { + FOR_EACH_SIZE(ADD_TO_SERVO_SIZES); + } + + FOR_EACH_SIZE(DECL_SIZE_ZERO); + +#undef FOR_EACH_SIZE +}; + +/** + * This class holds information about the memory taken up by identical copies of + * a particular string. Multiple JSStrings may have their sizes aggregated + * together into one StringInfo object. Note that two strings with identical + * chars will not be aggregated together if one is a short string and the other + * is not. + */ +struct StringInfo { +#define FOR_EACH_SIZE(MACRO) \ + MACRO(Strings, GCHeapUsed, gcHeapLatin1) \ + MACRO(Strings, GCHeapUsed, gcHeapTwoByte) \ + MACRO(Strings, MallocHeap, mallocHeapLatin1) \ + MACRO(Strings, MallocHeap, mallocHeapTwoByte) + + StringInfo() = default; + + void add(const StringInfo& other) { + FOR_EACH_SIZE(ADD_OTHER_SIZE); + numCopies++; + } + + void subtract(const StringInfo& other) { + FOR_EACH_SIZE(SUB_OTHER_SIZE); + numCopies--; + } + + bool isNotable() const { + static const size_t NotabilityThreshold = 16 * 1024; + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N); + return n >= NotabilityThreshold; + } + + size_t sizeOfLiveGCThings() const { + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING); + return n; + } + + void addToTabSizes(TabSizes* sizes) const { FOR_EACH_SIZE(ADD_TO_TAB_SIZES); } + + void addToServoSizes(ServoSizes* sizes) const { + FOR_EACH_SIZE(ADD_TO_SERVO_SIZES); + } + + FOR_EACH_SIZE(DECL_SIZE_ZERO); + + uint32_t numCopies = 0; // How many copies of the string have we seen? + +#undef FOR_EACH_SIZE +}; + +/** + * Holds data about a notable string (one which, counting all duplicates, uses + * more than a certain amount of memory) so we can report it individually. + * + * The only difference between this class and StringInfo is that + * NotableStringInfo holds a copy of some or all of the string's chars. + */ +struct NotableStringInfo : public StringInfo { + static const size_t MAX_SAVED_CHARS = 1024; + + NotableStringInfo() = default; + NotableStringInfo(NotableStringInfo&&) = default; + NotableStringInfo(const NotableStringInfo&) = delete; + + NotableStringInfo(JSString* str, const StringInfo& info); + + UniqueChars buffer = nullptr; + size_t length = 0; +}; + +/** + * This class holds information about the memory taken up by script sources + * from a particular file. + */ +struct ScriptSourceInfo { +#define FOR_EACH_SIZE(MACRO) MACRO(_, MallocHeap, misc) + + ScriptSourceInfo() = default; + + void add(const ScriptSourceInfo& other) { + FOR_EACH_SIZE(ADD_OTHER_SIZE); + numScripts++; + } + + void subtract(const ScriptSourceInfo& other) { + FOR_EACH_SIZE(SUB_OTHER_SIZE); + numScripts--; + } + + void addToServoSizes(ServoSizes* sizes) const { + FOR_EACH_SIZE(ADD_TO_SERVO_SIZES); + } + + bool isNotable() const { + static const size_t NotabilityThreshold = 16 * 1024; + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N); + return n >= NotabilityThreshold; + } + + FOR_EACH_SIZE(DECL_SIZE_ZERO); + + uint32_t numScripts = 0; // How many ScriptSources come from this file? (It + // can be more than one in XML files that have + // multiple scripts in CDATA sections.) +#undef FOR_EACH_SIZE +}; + +/** + * Holds data about a notable script source file (one whose combined + * script sources use more than a certain amount of memory) so we can report it + * individually. + * + * The only difference between this class and ScriptSourceInfo is that this + * class holds a copy of the filename. + */ +struct NotableScriptSourceInfo : public ScriptSourceInfo { + NotableScriptSourceInfo() = default; + NotableScriptSourceInfo(NotableScriptSourceInfo&&) = default; + NotableScriptSourceInfo(const NotableScriptSourceInfo&) = delete; + + NotableScriptSourceInfo(const char* filename, const ScriptSourceInfo& info); + + UniqueChars filename_ = nullptr; +}; + +struct HelperThreadStats { +#define FOR_EACH_SIZE(MACRO) \ + MACRO(_, MallocHeap, stateData) \ + MACRO(_, MallocHeap, parseTask) \ + MACRO(_, MallocHeap, ionCompileTask) \ + MACRO(_, MallocHeap, wasmCompile) \ + MACRO(_, MallocHeap, contexts) + + HelperThreadStats() = default; + + FOR_EACH_SIZE(DECL_SIZE_ZERO); + + unsigned idleThreadCount = 0; + unsigned activeThreadCount = 0; + +#undef FOR_EACH_SIZE +}; + +/** + * Measurements that not associated with any individual runtime. + */ +struct GlobalStats { + explicit GlobalStats(mozilla::MallocSizeOf mallocSizeOf) + : mallocSizeOf_(mallocSizeOf) {} + + HelperThreadStats helperThread; + + mozilla::MallocSizeOf mallocSizeOf_; +}; + +/** + * These measurements relate directly to the JSRuntime, and not to zones, + * compartments, and realms within it. + */ +struct RuntimeSizes { +#define FOR_EACH_SIZE(MACRO) \ + MACRO(_, MallocHeap, object) \ + MACRO(_, MallocHeap, atomsTable) \ + MACRO(_, MallocHeap, atomsMarkBitmaps) \ + MACRO(_, MallocHeap, selfHostStencil) \ + MACRO(_, MallocHeap, contexts) \ + MACRO(_, MallocHeap, temporary) \ + MACRO(_, MallocHeap, interpreterStack) \ + MACRO(_, MallocHeap, sharedImmutableStringsCache) \ + MACRO(_, MallocHeap, sharedIntlData) \ + MACRO(_, MallocHeap, uncompressedSourceCache) \ + MACRO(_, MallocHeap, scriptData) \ + MACRO(_, MallocHeap, wasmRuntime) \ + MACRO(_, Ignore, wasmGuardPages) \ + MACRO(_, MallocHeap, jitLazyLink) + + RuntimeSizes() { allScriptSources.emplace(); } + + void addToServoSizes(ServoSizes* sizes) const { + FOR_EACH_SIZE(ADD_TO_SERVO_SIZES); + scriptSourceInfo.addToServoSizes(sizes); + gc.addToServoSizes(sizes); + } + + FOR_EACH_SIZE(DECL_SIZE_ZERO); + + // The script source measurements in |scriptSourceInfo| are initially for + // all script sources. At the end, if the measurement granularity is + // FineGrained, we subtract the measurements of the notable script sources + // and move them into |notableScriptSources|. + ScriptSourceInfo scriptSourceInfo; + GCSizes gc; + + typedef js::HashMap<const char*, ScriptSourceInfo, mozilla::CStringHasher, + js::SystemAllocPolicy> + ScriptSourcesHashMap; + + // |allScriptSources| is only used transiently. During the reporting phase + // it is filled with info about every script source in the runtime. It's + // then used to fill in |notableScriptSources| (which actually gets + // reported), and immediately discarded afterwards. + mozilla::Maybe<ScriptSourcesHashMap> allScriptSources; + js::Vector<NotableScriptSourceInfo, 0, js::SystemAllocPolicy> + notableScriptSources; + +#undef FOR_EACH_SIZE +}; + +struct UnusedGCThingSizes { +#define FOR_EACH_SIZE(MACRO) \ + MACRO(Other, GCHeapUnused, object) \ + MACRO(Other, GCHeapUnused, script) \ + MACRO(Other, GCHeapUnused, shape) \ + MACRO(Other, GCHeapUnused, baseShape) \ + MACRO(Other, GCHeapUnused, getterSetter) \ + MACRO(Other, GCHeapUnused, propMap) \ + MACRO(Other, GCHeapUnused, string) \ + MACRO(Other, GCHeapUnused, symbol) \ + MACRO(Other, GCHeapUnused, bigInt) \ + MACRO(Other, GCHeapUnused, jitcode) \ + MACRO(Other, GCHeapUnused, scope) \ + MACRO(Other, GCHeapUnused, regExpShared) + + UnusedGCThingSizes() = default; + UnusedGCThingSizes(UnusedGCThingSizes&& other) = default; + + void addToKind(JS::TraceKind kind, intptr_t n) { + switch (kind) { + case JS::TraceKind::Object: + object += n; + break; + case JS::TraceKind::String: + string += n; + break; + case JS::TraceKind::Symbol: + symbol += n; + break; + case JS::TraceKind::BigInt: + bigInt += n; + break; + case JS::TraceKind::Script: + script += n; + break; + case JS::TraceKind::Shape: + shape += n; + break; + case JS::TraceKind::BaseShape: + baseShape += n; + break; + case JS::TraceKind::GetterSetter: + getterSetter += n; + break; + case JS::TraceKind::PropMap: + propMap += n; + break; + case JS::TraceKind::JitCode: + jitcode += n; + break; + case JS::TraceKind::Scope: + scope += n; + break; + case JS::TraceKind::RegExpShared: + regExpShared += n; + break; + default: + MOZ_CRASH("Bad trace kind for UnusedGCThingSizes"); + } + } + + void addSizes(const UnusedGCThingSizes& other) { + FOR_EACH_SIZE(ADD_OTHER_SIZE); + } + + size_t totalSize() const { + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N); + return n; + } + + void addToTabSizes(JS::TabSizes* sizes) const { + FOR_EACH_SIZE(ADD_TO_TAB_SIZES); + } + + void addToServoSizes(JS::ServoSizes* sizes) const { + FOR_EACH_SIZE(ADD_TO_SERVO_SIZES); + } + + FOR_EACH_SIZE(DECL_SIZE_ZERO); + +#undef FOR_EACH_SIZE +}; + +struct ZoneStats { +#define FOR_EACH_SIZE(MACRO) \ + MACRO(Other, GCHeapUsed, symbolsGCHeap) \ + MACRO(Other, GCHeapUsed, bigIntsGCHeap) \ + MACRO(Other, MallocHeap, bigIntsMallocHeap) \ + MACRO(Other, GCHeapAdmin, gcHeapArenaAdmin) \ + MACRO(Other, GCHeapUsed, jitCodesGCHeap) \ + MACRO(Other, GCHeapUsed, getterSettersGCHeap) \ + MACRO(Other, GCHeapUsed, compactPropMapsGCHeap) \ + MACRO(Other, GCHeapUsed, normalPropMapsGCHeap) \ + MACRO(Other, GCHeapUsed, dictPropMapsGCHeap) \ + MACRO(Other, MallocHeap, propMapChildren) \ + MACRO(Other, MallocHeap, propMapTables) \ + MACRO(Other, GCHeapUsed, scopesGCHeap) \ + MACRO(Other, MallocHeap, scopesMallocHeap) \ + MACRO(Other, GCHeapUsed, regExpSharedsGCHeap) \ + MACRO(Other, MallocHeap, regExpSharedsMallocHeap) \ + MACRO(Other, MallocHeap, regexpZone) \ + MACRO(Other, MallocHeap, jitZone) \ + MACRO(Other, MallocHeap, baselineStubsOptimized) \ + MACRO(Other, MallocHeap, uniqueIdMap) \ + MACRO(Other, MallocHeap, initialPropMapTable) \ + MACRO(Other, MallocHeap, shapeTables) \ + MACRO(Other, MallocHeap, compartmentObjects) \ + MACRO(Other, MallocHeap, crossCompartmentWrappersTables) \ + MACRO(Other, MallocHeap, compartmentsPrivateData) \ + MACRO(Other, MallocHeap, scriptCountsMap) + + ZoneStats() = default; + ZoneStats(ZoneStats&& other) = default; + + void initStrings(); + + void addSizes(const ZoneStats& other) { + MOZ_ASSERT(isTotals); + FOR_EACH_SIZE(ADD_OTHER_SIZE); + unusedGCThings.addSizes(other.unusedGCThings); + stringInfo.add(other.stringInfo); + shapeInfo.add(other.shapeInfo); + } + + size_t sizeOfLiveGCThings() const { + MOZ_ASSERT(isTotals); + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING); + n += stringInfo.sizeOfLiveGCThings(); + n += shapeInfo.sizeOfLiveGCThings(); + return n; + } + + void addToTabSizes(JS::TabSizes* sizes) const { + MOZ_ASSERT(isTotals); + FOR_EACH_SIZE(ADD_TO_TAB_SIZES); + unusedGCThings.addToTabSizes(sizes); + stringInfo.addToTabSizes(sizes); + shapeInfo.addToTabSizes(sizes); + } + + void addToServoSizes(JS::ServoSizes* sizes) const { + MOZ_ASSERT(isTotals); + FOR_EACH_SIZE(ADD_TO_SERVO_SIZES); + unusedGCThings.addToServoSizes(sizes); + stringInfo.addToServoSizes(sizes); + shapeInfo.addToServoSizes(sizes); + code.addToServoSizes(sizes); + } + + FOR_EACH_SIZE(DECL_SIZE_ZERO); + + // These string measurements are initially for all strings. At the end, + // if the measurement granularity is FineGrained, we subtract the + // measurements of the notable script sources and move them into + // |notableStrings|. + UnusedGCThingSizes unusedGCThings; + StringInfo stringInfo; + ShapeInfo shapeInfo; + CodeSizes code; + void* extra = nullptr; // This field can be used by embedders. + + typedef js::HashMap<JSString*, StringInfo, + js::InefficientNonFlatteningStringHashPolicy, + js::SystemAllocPolicy> + StringsHashMap; + + // |allStrings| is only used transiently. During the zone traversal it is + // filled with info about every string in the zone. It's then used to fill + // in |notableStrings| (which actually gets reported), and immediately + // discarded afterwards. + mozilla::Maybe<StringsHashMap> allStrings; + js::Vector<NotableStringInfo, 0, js::SystemAllocPolicy> notableStrings; + bool isTotals = true; + +#undef FOR_EACH_SIZE +}; + +struct RealmStats { + // We assume that |objectsPrivate| is on the malloc heap, but it's not + // actually guaranteed. But for Servo, at least, it's a moot point because + // it doesn't provide an ObjectPrivateVisitor so the value will always be + // zero. +#define FOR_EACH_SIZE(MACRO) \ + MACRO(Private, MallocHeap, objectsPrivate) \ + MACRO(Other, GCHeapUsed, scriptsGCHeap) \ + MACRO(Other, MallocHeap, scriptsMallocHeapData) \ + MACRO(Other, MallocHeap, baselineData) \ + MACRO(Other, MallocHeap, baselineStubsFallback) \ + MACRO(Other, MallocHeap, ionData) \ + MACRO(Other, MallocHeap, jitScripts) \ + MACRO(Other, MallocHeap, realmObject) \ + MACRO(Other, MallocHeap, realmTables) \ + MACRO(Other, MallocHeap, innerViewsTable) \ + MACRO(Other, MallocHeap, objectMetadataTable) \ + MACRO(Other, MallocHeap, savedStacksSet) \ + MACRO(Other, MallocHeap, nonSyntacticLexicalScopesTable) \ + MACRO(Other, MallocHeap, jitRealm) + + RealmStats() = default; + RealmStats(RealmStats&& other) = default; + + RealmStats(const RealmStats&) = delete; // disallow copying + + void initClasses(); + + void addSizes(const RealmStats& other) { + MOZ_ASSERT(isTotals); + FOR_EACH_SIZE(ADD_OTHER_SIZE); + classInfo.add(other.classInfo); + } + + size_t sizeOfLiveGCThings() const { + MOZ_ASSERT(isTotals); + size_t n = 0; + FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING); + n += classInfo.sizeOfLiveGCThings(); + return n; + } + + void addToTabSizes(TabSizes* sizes) const { + MOZ_ASSERT(isTotals); + FOR_EACH_SIZE(ADD_TO_TAB_SIZES); + classInfo.addToTabSizes(sizes); + } + + void addToServoSizes(ServoSizes* sizes) const { + MOZ_ASSERT(isTotals); + FOR_EACH_SIZE(ADD_TO_SERVO_SIZES); + classInfo.addToServoSizes(sizes); + } + + FOR_EACH_SIZE(DECL_SIZE_ZERO); + + // The class measurements in |classInfo| are initially for all classes. At + // the end, if the measurement granularity is FineGrained, we subtract the + // measurements of the notable classes and move them into |notableClasses|. + ClassInfo classInfo; + void* extra = nullptr; // This field can be used by embedders. + + typedef js::HashMap<const char*, ClassInfo, mozilla::CStringHasher, + js::SystemAllocPolicy> + ClassesHashMap; + + // These are similar to |allStrings| and |notableStrings| in ZoneStats. + mozilla::Maybe<ClassesHashMap> allClasses; + js::Vector<NotableClassInfo, 0, js::SystemAllocPolicy> notableClasses; + bool isTotals = true; + +#undef FOR_EACH_SIZE +}; + +typedef js::Vector<RealmStats, 0, js::SystemAllocPolicy> RealmStatsVector; +typedef js::Vector<ZoneStats, 0, js::SystemAllocPolicy> ZoneStatsVector; + +struct RuntimeStats { + // |gcHeapChunkTotal| is ignored because it's the sum of all the other + // values. |gcHeapGCThings| is ignored because it's the sum of some of the + // values from the zones and compartments. Both of those values are not + // reported directly, but are just present for sanity-checking other + // values. +#define FOR_EACH_SIZE(MACRO) \ + MACRO(_, Ignore, gcHeapChunkTotal) \ + MACRO(_, GCHeapDecommitted, gcHeapDecommittedPages) \ + MACRO(_, GCHeapUnused, gcHeapUnusedChunks) \ + MACRO(_, GCHeapUnused, gcHeapUnusedArenas) \ + MACRO(_, GCHeapAdmin, gcHeapChunkAdmin) \ + MACRO(_, Ignore, gcHeapGCThings) + + explicit RuntimeStats(mozilla::MallocSizeOf mallocSizeOf) + : mallocSizeOf_(mallocSizeOf) {} + + // Here's a useful breakdown of the GC heap. + // + // - rtStats.gcHeapChunkTotal + // - decommitted bytes + // - rtStats.gcHeapDecommittedPages + // (decommitted pages in non-empty chunks) + // - unused bytes + // - rtStats.gcHeapUnusedChunks (empty chunks) + // - rtStats.gcHeapUnusedArenas (empty arenas within non-empty chunks) + // - rtStats.zTotals.unusedGCThings.totalSize() + // (empty GC thing slots within non-empty arenas) + // - used bytes + // - rtStats.gcHeapChunkAdmin + // - rtStats.zTotals.gcHeapArenaAdmin + // - rtStats.gcHeapGCThings (in-use GC things) + // == (rtStats.zTotals.sizeOfLiveGCThings() + + // rtStats.cTotals.sizeOfLiveGCThings()) + // + // It's possible that some pages in empty chunks may be decommitted, but + // we don't count those under rtStats.gcHeapDecommittedPages because (a) + // it's rare, and (b) this means that rtStats.gcHeapUnusedChunks is a + // multiple of the chunk size, which is good. + + void addToServoSizes(ServoSizes* sizes) const { + FOR_EACH_SIZE(ADD_TO_SERVO_SIZES); + runtime.addToServoSizes(sizes); + } + + FOR_EACH_SIZE(DECL_SIZE_ZERO); + + RuntimeSizes runtime; + + RealmStats realmTotals; // The sum of this runtime's realms' measurements. + ZoneStats zTotals; // The sum of this runtime's zones' measurements. + + RealmStatsVector realmStatsVector; + ZoneStatsVector zoneStatsVector; + + ZoneStats* currZoneStats = nullptr; + + mozilla::MallocSizeOf mallocSizeOf_; + + virtual void initExtraRealmStats(JS::Realm* realm, RealmStats* rstats, + const JS::AutoRequireNoGC& nogc) = 0; + virtual void initExtraZoneStats(JS::Zone* zone, ZoneStats* zstats, + const JS::AutoRequireNoGC& nogc) = 0; + +#undef FOR_EACH_SIZE +}; + +class ObjectPrivateVisitor { + public: + // Within CollectRuntimeStats, this method is called for each JS object + // that has an nsISupports pointer. + virtual size_t sizeOfIncludingThis(nsISupports* aSupports) = 0; + + // A callback that gets a JSObject's nsISupports pointer, if it has one. + // Note: this function does *not* addref |iface|. + typedef bool (*GetISupportsFun)(JSObject* obj, nsISupports** iface); + GetISupportsFun getISupports_; + + explicit ObjectPrivateVisitor(GetISupportsFun getISupports) + : getISupports_(getISupports) {} +}; + +extern JS_PUBLIC_API bool CollectGlobalStats(GlobalStats* gStats); + +extern JS_PUBLIC_API bool CollectRuntimeStats(JSContext* cx, + RuntimeStats* rtStats, + ObjectPrivateVisitor* opv, + bool anonymize); + +extern JS_PUBLIC_API size_t SystemCompartmentCount(JSContext* cx); +extern JS_PUBLIC_API size_t UserCompartmentCount(JSContext* cx); + +extern JS_PUBLIC_API size_t SystemRealmCount(JSContext* cx); +extern JS_PUBLIC_API size_t UserRealmCount(JSContext* cx); + +extern JS_PUBLIC_API size_t PeakSizeOfTemporary(const JSContext* cx); + +extern JS_PUBLIC_API bool AddSizeOfTab(JSContext* cx, JS::HandleObject obj, + mozilla::MallocSizeOf mallocSizeOf, + ObjectPrivateVisitor* opv, + TabSizes* sizes); + +extern JS_PUBLIC_API bool AddServoSizeOf(JSContext* cx, + mozilla::MallocSizeOf mallocSizeOf, + ObjectPrivateVisitor* opv, + ServoSizes* sizes); + +} // namespace JS + +#undef DECL_SIZE_ZERO +#undef ADD_OTHER_SIZE +#undef SUB_OTHER_SIZE +#undef ADD_SIZE_TO_N +#undef ADD_SIZE_TO_N_IF_LIVE_GC_THING +#undef ADD_TO_TAB_SIZES + +#endif /* js_MemoryMetrics_h */ diff --git a/js/public/Modules.h b/js/public/Modules.h new file mode 100644 index 0000000000..096e017b73 --- /dev/null +++ b/js/public/Modules.h @@ -0,0 +1,304 @@ +/* -*- 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/. */ + +/* JavaScript module (as in, the syntactic construct) operations. */ + +#ifndef js_Modules_h +#define js_Modules_h + +#include <stdint.h> // uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/AllocPolicy.h" // js::SystemAllocPolicy +#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions +#include "js/RootingAPI.h" // JS::{Mutable,}Handle +#include "js/Value.h" // JS::Value +#include "js/Vector.h" // js::Vector + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; +struct JS_PUBLIC_API JSRuntime; +class JS_PUBLIC_API JSString; + +namespace JS { +template <typename UnitT> +class SourceText; +} // namespace JS + +namespace mozilla { +union Utf8Unit; +} + +namespace JS { + +enum class ImportAssertion { Type }; + +using ImportAssertionVector = + js::Vector<ImportAssertion, 1, js::SystemAllocPolicy>; + +/** + * Set the supported assertions for the runtime to the given vector. + * + * See: + * https://tc39.es/proposal-import-assertions/#sec-hostgetsupportedimportassertions + */ +extern JS_PUBLIC_API void SetSupportedImportAssertions( + JSRuntime* rt, const ImportAssertionVector& assertions); + +/** + * The HostResolveImportedModule hook. + * + * See: https://tc39.es/ecma262/#sec-hostresolveimportedmodule + * + * This embedding-defined hook is used to implement module loading. It is called + * to get or create a module object corresponding to |moduleRequest| occurring + * in the context of the script or module with private value + * |referencingPrivate|. + * + * The module specifier string for the request can be obtained by calling + * JS::GetModuleRequestSpecifier. + * + * The private value for a script or module is set with JS::SetScriptPrivate or + * JS::SetModulePrivate. It's assumed that the embedding can handle receiving + * either here. + * + * This hook must obey the restrictions defined in the spec: + * - Each time the hook is called with the same arguemnts, the same module must + * be returned. + * - If a module cannot be created for the given arguments, an exception must + * be thrown. + * + * This is a synchronous operation. + */ +using ModuleResolveHook = JSObject* (*)(JSContext* cx, + Handle<Value> referencingPrivate, + Handle<JSObject*> moduleRequest); + +/** + * Get the HostResolveImportedModule hook for the runtime. + */ +extern JS_PUBLIC_API ModuleResolveHook GetModuleResolveHook(JSRuntime* rt); + +/** + * Set the HostResolveImportedModule hook for the runtime to the given function. + */ +extern JS_PUBLIC_API void SetModuleResolveHook(JSRuntime* rt, + ModuleResolveHook func); + +/** + * The module metadata hook. + * + * See: https://tc39.es/ecma262/#sec-hostgetimportmetaproperties + * + * Populate the |metaObject| object returned when import.meta is evaluated in + * the context of the script or module with private value |privateValue|. + * + * This is based on the spec's HostGetImportMetaProperties hook but defines + * properties on the meta object directly rather than returning a list. + */ +using ModuleMetadataHook = bool (*)(JSContext* cx, Handle<Value> privateValue, + Handle<JSObject*> metaObject); + +/** + * Get the hook for populating the import.meta metadata object. + */ +extern JS_PUBLIC_API ModuleMetadataHook GetModuleMetadataHook(JSRuntime* rt); + +/** + * Set the hook for populating the import.meta metadata object to the given + * function. + */ +extern JS_PUBLIC_API void SetModuleMetadataHook(JSRuntime* rt, + ModuleMetadataHook func); + +/** + * The HostImportModuleDynamically hook. + * + * See https://tc39.es/ecma262/#sec-hostimportmoduledynamically + * + * Used to implement dynamic module import. Called when evaluating import() + * expressions. + * + * This starts an asynchronous operation. Some time after this hook is called + * the embedding must call JS::FinishDynamicModuleImport() passing the + * |referencingPrivate|, |moduleRequest| and |promise| arguments from the + * call. This must happen for both success and failure cases. + * + * In the meantime the embedding can take whatever steps it needs to make the + * module available. If successful, after calling FinishDynamicModuleImport() + * the module should be returned by the resolve hook when passed + * |referencingPrivate| and |moduleRequest|. + */ +using ModuleDynamicImportHook = bool (*)(JSContext* cx, + Handle<Value> referencingPrivate, + Handle<JSObject*> moduleRequest, + Handle<JSObject*> promise); + +/** + * Get the HostImportModuleDynamically hook for the runtime. + */ +extern JS_PUBLIC_API ModuleDynamicImportHook +GetModuleDynamicImportHook(JSRuntime* rt); + +/** + * Set the HostImportModuleDynamically hook for the runtime to the given + * function. + * + * If this hook is not set (or set to nullptr) then the JS engine will throw an + * exception if dynamic module import is attempted. + */ +extern JS_PUBLIC_API void SetModuleDynamicImportHook( + JSRuntime* rt, ModuleDynamicImportHook func); + +/** + * This must be called after a dynamic import operation is complete. + * + * If |evaluationPromise| is rejected, the rejection reason will be used to + * complete the user's promise. + */ +extern JS_PUBLIC_API bool FinishDynamicModuleImport( + JSContext* cx, Handle<JSObject*> evaluationPromise, + Handle<Value> referencingPrivate, Handle<JSObject*> moduleRequest, + Handle<JSObject*> promise); + +/** + * Parse the given source buffer as a module in the scope of the current global + * of cx and return a source text module record. + */ +extern JS_PUBLIC_API JSObject* CompileModule( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf); + +/** + * Parse the given source buffer as a module in the scope of the current global + * of cx and return a source text module record. An error is reported if a + * UTF-8 encoding error is encountered. + */ +extern JS_PUBLIC_API JSObject* CompileModule( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<mozilla::Utf8Unit>& srcBuf); + +/** + * Set a private value associated with a source text module record. + */ +extern JS_PUBLIC_API void SetModulePrivate(JSObject* module, + const Value& value); +/** + * Clear the private value associated with a source text module record. + * + * This is used during unlinking and can be called on a gray module, skipping + * the usual checks. + */ +extern JS_PUBLIC_API void ClearModulePrivate(JSObject* module); + +/** + * Get the private value associated with a source text module record. + */ +extern JS_PUBLIC_API Value GetModulePrivate(JSObject* module); + +/* + * Perform the ModuleLink operation on the given source text module record. + * + * This transitively resolves all module dependencies (calling the + * HostResolveImportedModule hook) and initializes the environment record for + * the module. + */ +extern JS_PUBLIC_API bool ModuleLink(JSContext* cx, + Handle<JSObject*> moduleRecord); + +/* + * Perform the ModuleEvaluate operation on the given source text module record + * and returns a bool. A result value is returned in result and is either + * undefined (and ignored) or a promise (if Top Level Await is enabled). + * + * If this module has already been evaluated, it returns the evaluation + * promise. Otherwise, it transitively evaluates all dependences of this module + * and then evaluates this module. + * + * ModuleLink must have completed prior to calling this. + */ +extern JS_PUBLIC_API bool ModuleEvaluate(JSContext* cx, + Handle<JSObject*> moduleRecord, + MutableHandleValue rval); + +enum ModuleErrorBehaviour { + // Report module evaluation errors asynchronously when the evaluation promise + // is rejected. This is used for web content. + ReportModuleErrorsAsync, + + // Throw module evaluation errors synchronously by setting an exception on the + // context. Does not support modules that use top-level await. + ThrowModuleErrorsSync +}; + +/* + * If a module evaluation fails, unwrap the resulting evaluation promise + * and rethrow. + * + * This does nothing if this module succeeds in evaluation. Otherwise, it + * takes the reason for the module throwing, unwraps it and throws it as a + * regular error rather than as an uncaught promise. + * + * ModuleEvaluate must have completed prior to calling this. + */ +extern JS_PUBLIC_API bool ThrowOnModuleEvaluationFailure( + JSContext* cx, Handle<JSObject*> evaluationPromise, + ModuleErrorBehaviour errorBehaviour = ReportModuleErrorsAsync); + +/* + * Functions to access the module specifiers of a source text module record used + * to request module imports. + * + * Clients can use GetRequestedModulesCount() to get the number of specifiers + * and GetRequestedModuleSpecifier() / GetRequestedModuleSourcePos() to get the + * individual elements. + */ +extern JS_PUBLIC_API uint32_t +GetRequestedModulesCount(JSContext* cx, Handle<JSObject*> moduleRecord); + +extern JS_PUBLIC_API JSString* GetRequestedModuleSpecifier( + JSContext* cx, Handle<JSObject*> moduleRecord, uint32_t index); + +extern JS_PUBLIC_API void GetRequestedModuleSourcePos( + JSContext* cx, Handle<JSObject*> moduleRecord, uint32_t index, + uint32_t* lineNumber, uint32_t* columnNumber); + +/* + * Get the top-level script for a module which has not yet been executed. + */ +extern JS_PUBLIC_API JSScript* GetModuleScript(Handle<JSObject*> moduleRecord); + +extern JS_PUBLIC_API JSObject* CreateModuleRequest( + JSContext* cx, Handle<JSString*> specifierArg); +extern JS_PUBLIC_API JSString* GetModuleRequestSpecifier( + JSContext* cx, Handle<JSObject*> moduleRequestArg); + +/* + * Get the module record for a module script. + */ +extern JS_PUBLIC_API JSObject* GetModuleObject(Handle<JSScript*> moduleScript); + +/* + * Get the namespace object for a module. + */ +extern JS_PUBLIC_API JSObject* GetModuleNamespace( + JSContext* cx, Handle<JSObject*> moduleRecord); + +extern JS_PUBLIC_API JSObject* GetModuleForNamespace( + JSContext* cx, Handle<JSObject*> moduleNamespace); + +extern JS_PUBLIC_API JSObject* GetModuleEnvironment( + JSContext* cx, Handle<JSObject*> moduleObj); + +/* + * Clear all bindings in a module's environment. Used during shutdown. + */ +extern JS_PUBLIC_API void ClearModuleEnvironment(JSObject* moduleObj); + +} // namespace JS + +#endif // js_Modules_h diff --git a/js/public/Object.h b/js/public/Object.h new file mode 100644 index 0000000000..c7c5e3c617 --- /dev/null +++ b/js/public/Object.h @@ -0,0 +1,147 @@ +/* -*- 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 js_public_Object_h +#define js_public_Object_h + +#include "js/shadow/Object.h" // JS::shadow::Object + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include <stddef.h> // size_t +#include <stdint.h> // uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Class.h" // js::ESClass, JSCLASS_RESERVED_SLOTS +#include "js/Realm.h" // JS::GetCompartmentForRealm +#include "js/RootingAPI.h" // JS::{,Mutable}Handle +#include "js/Value.h" // JS::Value + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; + +namespace JS { + +class JS_PUBLIC_API Compartment; + +/** + * Determine the ECMAScript "class" -- Date, String, RegExp, and all the other + * builtin object types (described in ECMAScript in terms of an objecting having + * "an [[ArrayBufferData]] internal slot" or similar language for other kinds of + * object -- of the provided object. + * + * If this function is passed a wrapper that can be unwrapped, the determination + * is performed on that object. If the wrapper can't be unwrapped, and it's not + * a wrapper that prefers to treat this operation as a failure, this function + * will indicate that the object is |js::ESClass::Other|. + */ +extern JS_PUBLIC_API bool GetBuiltinClass(JSContext* cx, Handle<JSObject*> obj, + js::ESClass* cls); + +/** Get the |JSClass| of an object. */ +inline const JSClass* GetClass(const JSObject* obj) { + return reinterpret_cast<const shadow::Object*>(obj)->shape->base->clasp; +} + +/** + * Get the |JS::Compartment*| of an object. + * + * Note that the compartment of an object in this realm, that is a + * cross-compartment wrapper around an object from another realm, is the + * compartment of this realm. + */ +static MOZ_ALWAYS_INLINE Compartment* GetCompartment(JSObject* obj) { + Realm* realm = reinterpret_cast<shadow::Object*>(obj)->shape->base->realm; + return GetCompartmentForRealm(realm); +} + +/** + * Get the value stored in a reserved slot in an object. + * + * If |obj| is known to be a proxy and you're willing to use friend APIs, + * |js::GetProxyReservedSlot| in "js/Proxy.h" is very slightly more efficient. + */ +inline const Value& GetReservedSlot(JSObject* obj, size_t slot) { + MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(GetClass(obj))); + return reinterpret_cast<const shadow::Object*>(obj)->slotRef(slot); +} + +namespace detail { + +extern JS_PUBLIC_API void SetReservedSlotWithBarrier(JSObject* obj, size_t slot, + const Value& value); + +} // namespace detail + +/** + * Store a value in an object's reserved slot. + * + * This can be used with both native objects and proxies. However, if |obj| is + * known to be a proxy, |js::SetProxyReservedSlot| in "js/Proxy.h" is very + * slightly more efficient. + */ +inline void SetReservedSlot(JSObject* obj, size_t slot, const Value& value) { + MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(GetClass(obj))); + auto* sobj = reinterpret_cast<shadow::Object*>(obj); + if (sobj->slotRef(slot).isGCThing() || value.isGCThing()) { + detail::SetReservedSlotWithBarrier(obj, slot, value); + } else { + sobj->slotRef(slot) = value; + } +} + +/** + * Helper function to get the pointer value (or nullptr if not set) from an + * object's reserved slot. The slot must contain either a PrivateValue(T*) or + * UndefinedValue. + */ +template <typename T> +inline T* GetMaybePtrFromReservedSlot(JSObject* obj, size_t slot) { + Value v = GetReservedSlot(obj, slot); + return v.isUndefined() ? nullptr : static_cast<T*>(v.toPrivate()); +} + +/** + * Helper function to get the pointer value (or nullptr if not set) from the + * object's first reserved slot. Must only be used for objects with a JSClass + * that has the JSCLASS_SLOT0_IS_NSISUPPORTS flag. + */ +template <typename T> +inline T* GetObjectISupports(JSObject* obj) { + MOZ_ASSERT(GetClass(obj)->slot0IsISupports()); + return GetMaybePtrFromReservedSlot<T>(obj, 0); +} + +/** + * Helper function to store |PrivateValue(nsISupportsValue)| in the object's + * first reserved slot. Must only be used for objects with a JSClass that has + * the JSCLASS_SLOT0_IS_NSISUPPORTS flag. + * + * Note: the pointer is opaque to the JS engine (including the GC) so it's the + * embedding's responsibility to trace or free this value. + */ +inline void SetObjectISupports(JSObject* obj, void* nsISupportsValue) { + MOZ_ASSERT(GetClass(obj)->slot0IsISupports()); + SetReservedSlot(obj, 0, PrivateValue(nsISupportsValue)); +} + +} // namespace JS + +// JSObject* is an aligned pointer, but this information isn't available in the +// public header. We specialize HasFreeLSB here so that JS::Result<JSObject*> +// compiles. + +namespace mozilla { +namespace detail { +template <> +struct HasFreeLSB<JSObject*> { + static constexpr bool value = true; +}; +} // namespace detail +} // namespace mozilla + +#endif // js_public_Object_h diff --git a/js/public/OffThreadScriptCompilation.h b/js/public/OffThreadScriptCompilation.h new file mode 100644 index 0000000000..3ae3c9e459 --- /dev/null +++ b/js/public/OffThreadScriptCompilation.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * Types and functions related to the compilation of JavaScript off the + * direct JSAPI-using thread. + */ + +#ifndef js_OffThreadScriptCompilation_h +#define js_OffThreadScriptCompilation_h + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/CompileOptions.h" // JS::DecodeOptions, JS::ReadOnlyCompileOptions + +struct JS_PUBLIC_API JSContext; + +namespace JS { + +class OffThreadToken; + +using OffThreadCompileCallback = void (*)(OffThreadToken* token, + void* callbackData); + +/* + * Off thread compilation control flow. + * + * (See also js/public/experimental/JSStencil.h) + * + * After successfully triggering an off thread compile of a script, the + * callback will eventually be invoked with the specified data and a token + * for the compilation. The callback will be invoked while off thread, + * so must ensure that its operations are thread safe. Afterwards, one of the + * following functions must be invoked on the runtime's main thread: + * + * - FinishCompileToStencilOffThread, to get the result stencil (or nullptr on + * failure). + * - CancelCompileToStencilOffThread, to free the resources without creating a + * stencil. + * + * The characters passed in to CompileToStencilOffThread must remain live until + * the callback is invoked. + */ + +extern JS_PUBLIC_API bool CanCompileOffThread( + JSContext* cx, const ReadOnlyCompileOptions& options, size_t length); + +extern JS_PUBLIC_API bool CanDecodeOffThread(JSContext* cx, + const DecodeOptions& options, + size_t length); + +} // namespace JS + +#endif /* js_OffThreadScriptCompilation_h */ diff --git a/js/public/Principals.h b/js/public/Principals.h new file mode 100644 index 0000000000..609b1ecff6 --- /dev/null +++ b/js/public/Principals.h @@ -0,0 +1,173 @@ +/* -*- 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/. */ + +/* JSPrincipals and related interfaces. */ + +#ifndef js_Principals_h +#define js_Principals_h + +#include "mozilla/Atomics.h" + +#include <stdint.h> + +#include "jstypes.h" + +#include "js/TypeDecls.h" + +struct JSStructuredCloneReader; +struct JSStructuredCloneWriter; + +struct JSPrincipals { + /* Don't call "destroy"; use reference counting macros below. */ + mozilla::Atomic<int32_t, mozilla::SequentiallyConsistent> refcount{0}; + +#ifdef JS_DEBUG + /* A helper to facilitate principals debugging. */ + uint32_t debugToken; +#endif + + JSPrincipals() = default; + + void setDebugToken(uint32_t token) { +#ifdef JS_DEBUG + debugToken = token; +#endif + } + + /* + * Write the principals with the given |writer|. Return false on failure, + * true on success. + */ + virtual bool write(JSContext* cx, JSStructuredCloneWriter* writer) = 0; + + /* + * Whether the principal corresponds to a System or AddOn Principal. + * Technically this also checks for an ExpandedAddonPrincipal. + */ + virtual bool isSystemOrAddonPrincipal() = 0; + + /* + * This is not defined by the JS engine but should be provided by the + * embedding. + */ + JS_PUBLIC_API void dump(); +}; + +extern JS_PUBLIC_API void JS_HoldPrincipals(JSPrincipals* principals); + +extern JS_PUBLIC_API void JS_DropPrincipals(JSContext* cx, + JSPrincipals* principals); + +// Return whether the first principal subsumes the second. The exact meaning of +// 'subsumes' is left up to the browser. Subsumption is checked inside the JS +// engine when determining, e.g., which stack frames to display in a backtrace. +typedef bool (*JSSubsumesOp)(JSPrincipals* first, JSPrincipals* second); + +namespace JS { +enum class RuntimeCode { JS, WASM }; +} // namespace JS + +/* + * Used to check if a CSP instance wants to disable eval() and friends. + * See JSContext::isRuntimeCodeGenEnabled() in vm/JSContext.cpp. + * + * `code` is the JavaScript source code passed to eval/Function, but nullptr + * for Wasm. + * + * Returning `false` from this callback will prevent the execution/compilation + * of the code. + */ +typedef bool (*JSCSPEvalChecker)(JSContext* cx, JS::RuntimeCode kind, + JS::HandleString code); + +struct JSSecurityCallbacks { + JSCSPEvalChecker contentSecurityPolicyAllows; + JSSubsumesOp subsumes; +}; + +extern JS_PUBLIC_API void JS_SetSecurityCallbacks( + JSContext* cx, const JSSecurityCallbacks* callbacks); + +extern JS_PUBLIC_API const JSSecurityCallbacks* JS_GetSecurityCallbacks( + JSContext* cx); + +/* + * Code running with "trusted" principals will be given a deeper stack + * allocation than ordinary scripts. This allows trusted script to run after + * untrusted script has exhausted the stack. This function sets the + * runtime-wide trusted principal. + * + * This principals is not held (via JS_HoldPrincipals/JS_DropPrincipals). + * Instead, the caller must ensure that the given principals stays valid for as + * long as 'cx' may point to it. If the principals would be destroyed before + * 'cx', JS_SetTrustedPrincipals must be called again, passing nullptr for + * 'prin'. + */ +extern JS_PUBLIC_API void JS_SetTrustedPrincipals(JSContext* cx, + JSPrincipals* prin); + +typedef void (*JSDestroyPrincipalsOp)(JSPrincipals* principals); + +/* + * Initialize the callback that is called to destroy JSPrincipals instance + * when its reference counter drops to zero. The initialization can be done + * only once per JS runtime. + */ +extern JS_PUBLIC_API void JS_InitDestroyPrincipalsCallback( + JSContext* cx, JSDestroyPrincipalsOp destroyPrincipals); + +/* + * Read a JSPrincipals instance from the given |reader| and initialize the out + * paratemer |outPrincipals| to the JSPrincipals instance read. + * + * Return false on failure, true on success. The |outPrincipals| parameter + * should not be modified if false is returned. + * + * The caller is not responsible for calling JS_HoldPrincipals on the resulting + * JSPrincipals instance, the JSReadPrincipalsOp must increment the refcount of + * the resulting JSPrincipals on behalf of the caller. + */ +using JSReadPrincipalsOp = bool (*)(JSContext* cx, + JSStructuredCloneReader* reader, + JSPrincipals** outPrincipals); + +/* + * Initialize the callback that is called to read JSPrincipals instances from a + * buffer. The initialization can be done only once per JS runtime. + */ +extern JS_PUBLIC_API void JS_InitReadPrincipalsCallback( + JSContext* cx, JSReadPrincipalsOp read); + +namespace JS { + +class MOZ_RAII AutoHoldPrincipals { + JSContext* cx_; + JSPrincipals* principals_ = nullptr; + + public: + explicit AutoHoldPrincipals(JSContext* cx, JSPrincipals* principals = nullptr) + : cx_(cx) { + reset(principals); + } + + ~AutoHoldPrincipals() { reset(nullptr); } + + void reset(JSPrincipals* principals) { + if (principals) { + JS_HoldPrincipals(principals); + } + if (principals_) { + JS_DropPrincipals(cx_, principals_); + } + principals_ = principals; + } + + JSPrincipals* get() const { return principals_; } +}; + +} // namespace JS + +#endif /* js_Principals_h */ diff --git a/js/public/Printer.h b/js/public/Printer.h new file mode 100644 index 0000000000..f9fbe0aeb2 --- /dev/null +++ b/js/public/Printer.h @@ -0,0 +1,230 @@ +/* -*- 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 js_Printer_h +#define js_Printer_h + +#include "mozilla/Attributes.h" +#include "mozilla/Range.h" + +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include "js/TypeDecls.h" +#include "js/Utility.h" + +namespace js { + +class LifoAlloc; + +// Generic printf interface, similar to an ostream in the standard library. +// +// This class is useful to make generic printers which can work either with a +// file backend, with a buffer allocated with an JSContext or a link-list +// of chunks allocated with a LifoAlloc. +class JS_PUBLIC_API GenericPrinter { + protected: + bool hadOOM_; // whether reportOutOfMemory() has been called. + + constexpr GenericPrinter() : hadOOM_(false) {} + + public: + // Puts |len| characters from |s| at the current position and + // return true on success, false on failure. + virtual bool put(const char* s, size_t len) = 0; + virtual void flush() { /* Do nothing */ + } + + inline bool put(const char* s) { return put(s, strlen(s)); } + inline bool putChar(const char c) { return put(&c, 1); } + + // Prints a formatted string into the buffer. + bool printf(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3); + bool vprintf(const char* fmt, va_list ap) MOZ_FORMAT_PRINTF(2, 0); + + // Report that a string operation failed to get the memory it requested. + virtual void reportOutOfMemory(); + + // Return true if this Sprinter ran out of memory. + virtual bool hadOutOfMemory() const; +}; + +// Sprintf, but with unlimited and automatically allocated buffering. +class JS_PUBLIC_API Sprinter final : public GenericPrinter { + public: + struct InvariantChecker { + const Sprinter* parent; + + explicit InvariantChecker(const Sprinter* p) : parent(p) { + parent->checkInvariants(); + } + + ~InvariantChecker() { parent->checkInvariants(); } + }; + + JSContext* maybeCx; // context executing the decompiler + + private: + static const size_t DefaultSize; +#ifdef DEBUG + bool initialized; // true if this is initialized, use for debug builds +#endif + bool shouldReportOOM; // whether to report OOM to the maybeCx + char* base; // malloc'd buffer address + size_t size; // size of buffer allocated at base + ptrdiff_t offset; // offset of next free char in buffer + + [[nodiscard]] bool realloc_(size_t newSize); + + public: + // JSContext* parameter is optional and can be omitted if the following + // are not used. + // * putString method with JSString + // * QuoteString function with JSString + // * JSONQuoteString function with JSString + // + // If JSContext* parameter is not provided, or shouldReportOOM is false, + // the consumer should manually report OOM on any failure. + explicit Sprinter(JSContext* maybeCx = nullptr, bool shouldReportOOM = true); + ~Sprinter(); + + // Initialize this sprinter, returns false on error. + [[nodiscard]] bool init(); + + void checkInvariants() const; + + const char* string() const { return base; } + const char* stringEnd() const { return base + offset; } + JS::UniqueChars release(); + + // Returns the string at offset |off|. + char* stringAt(ptrdiff_t off) const; + // Returns the char at offset |off|. + char& operator[](size_t off); + + // Attempt to reserve len + 1 space (for a trailing nullptr byte). If the + // attempt succeeds, return a pointer to the start of that space and adjust + // the internal content. The caller *must* completely fill this space on + // success. + char* reserve(size_t len); + + // Puts |len| characters from |s| at the current position and + // return true on success, false on failure. + virtual bool put(const char* s, size_t len) override; + using GenericPrinter::put; // pick up |inline bool put(const char* s);| + + // Format the given format/arguments as if by JS_vsmprintf, then put it. + // Return true on success, else return false and report an error (typically + // OOM). + [[nodiscard]] bool jsprintf(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3); + + bool putString(JSString* str); + + ptrdiff_t getOffset() const; + + // Report that a string operation failed to get the memory it requested. The + // first call to this function calls JS_ReportOutOfMemory, and sets this + // Sprinter's outOfMemory flag; subsequent calls do nothing. + virtual void reportOutOfMemory() override; +}; + +// Fprinter, print a string directly into a file. +class JS_PUBLIC_API Fprinter final : public GenericPrinter { + private: + FILE* file_; + bool init_; + + public: + explicit Fprinter(FILE* fp); + + constexpr Fprinter() : file_(nullptr), init_(false) {} + +#ifdef DEBUG + ~Fprinter(); +#endif + + // Initialize this printer, returns false on error. + [[nodiscard]] bool init(const char* path); + void init(FILE* fp); + bool isInitialized() const { return file_ != nullptr; } + void flush() override; + void finish(); + + // Puts |len| characters from |s| at the current position and + // return true on success, false on failure. + virtual bool put(const char* s, size_t len) override; + using GenericPrinter::put; // pick up |inline bool put(const char* s);| +}; + +// LSprinter, is similar to Sprinter except that instead of using an +// JSContext to allocate strings, it use a LifoAlloc as a backend for the +// allocation of the chunk of the string. +class JS_PUBLIC_API LSprinter final : public GenericPrinter { + private: + struct Chunk { + Chunk* next; + size_t length; + + char* chars() { return reinterpret_cast<char*>(this + 1); } + char* end() { return chars() + length; } + }; + + private: + LifoAlloc* alloc_; // LifoAlloc used as a backend of chunk allocations. + Chunk* head_; + Chunk* tail_; + size_t unused_; + + public: + explicit LSprinter(LifoAlloc* lifoAlloc); + ~LSprinter(); + + // Copy the content of the chunks into another printer, such that we can + // flush the content of this printer to a file. + void exportInto(GenericPrinter& out) const; + + // Drop the current string, and let them be free with the LifoAlloc. + void clear(); + + // Puts |len| characters from |s| at the current position and + // return true on success, false on failure. + virtual bool put(const char* s, size_t len) override; + using GenericPrinter::put; // pick up |inline bool put(const char* s);| +}; + +// Map escaped code to the letter/symbol escaped with a backslash. +extern const char js_EscapeMap[]; + +// Return a C-string containing the chars in str, with any non-printing chars +// escaped. If the optional quote parameter is present and is not '\0', quotes +// (as specified by the quote argument) are also escaped, and the quote +// character is appended at the beginning and end of the result string. +// The returned string is guaranteed to contain only ASCII characters. +extern JS_PUBLIC_API JS::UniqueChars QuoteString(JSContext* cx, JSString* str, + char quote = '\0'); + +// Appends the quoted string to the given Sprinter. Follows the same semantics +// as QuoteString from above. +extern JS_PUBLIC_API bool QuoteString(Sprinter* sp, JSString* str, + char quote = '\0'); + +// Appends the quoted string to the given Sprinter. Follows the same +// Appends the JSON quoted string to the given Sprinter. +extern JS_PUBLIC_API bool JSONQuoteString(Sprinter* sp, JSString* str); + +// Internal implementation code for QuoteString methods above. +enum class QuoteTarget { String, JSON }; + +template <QuoteTarget target, typename CharT> +bool JS_PUBLIC_API QuoteString(Sprinter* sp, + const mozilla::Range<const CharT> chars, + char quote = '\0'); + +} // namespace js + +#endif // js_Printer_h diff --git a/js/public/Printf.h b/js/public/Printf.h new file mode 100644 index 0000000000..aca542d796 --- /dev/null +++ b/js/public/Printf.h @@ -0,0 +1,34 @@ +/* -*- 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 js_Printf_h +#define js_Printf_h + +#include "mozilla/Printf.h" + +#include <stdarg.h> + +#include "jstypes.h" +#include "js/Utility.h" + +/* Wrappers for mozilla::Smprintf and friends that are used throughout + JS. */ + +extern JS_PUBLIC_API JS::UniqueChars JS_smprintf(const char* fmt, ...) + MOZ_FORMAT_PRINTF(1, 2); + +extern JS_PUBLIC_API JS::UniqueChars JS_sprintf_append(JS::UniqueChars&& last, + const char* fmt, ...) + MOZ_FORMAT_PRINTF(2, 3); + +extern JS_PUBLIC_API JS::UniqueChars JS_vsmprintf(const char* fmt, va_list ap) + MOZ_FORMAT_PRINTF(1, 0); +extern JS_PUBLIC_API JS::UniqueChars JS_vsprintf_append(JS::UniqueChars&& last, + const char* fmt, + va_list ap) + MOZ_FORMAT_PRINTF(2, 0); + +#endif /* js_Printf_h */ diff --git a/js/public/ProfilingCategory.h b/js/public/ProfilingCategory.h new file mode 100644 index 0000000000..5cf247abc1 --- /dev/null +++ b/js/public/ProfilingCategory.h @@ -0,0 +1,65 @@ +/* -*- 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_ProfilingCategory_h +#define js_ProfilingCategory_h + +#include "jstypes.h" // JS_PUBLIC_API + +// The source lives in mozglue/baseprofiler/public/ProfilingCategoryList.h +#include "js/ProfilingCategoryList.h" // MOZ_PROFILING_CATEGORY_LIST + +namespace JS { + +// clang-format off + +// An enum that lists all possible category pairs in one list. +// This is the enum that is used in profiler stack labels. Having one list that +// includes subcategories from all categories in one list allows assigning the +// category pair to a stack label with just one number. +#define CATEGORY_ENUM_BEGIN_CATEGORY(name, labelAsString, color) +#define CATEGORY_ENUM_SUBCATEGORY(supercategory, name, labelAsString) name, +#define CATEGORY_ENUM_END_CATEGORY +enum class ProfilingCategoryPair : uint32_t { + MOZ_PROFILING_CATEGORY_LIST(CATEGORY_ENUM_BEGIN_CATEGORY, + CATEGORY_ENUM_SUBCATEGORY, + CATEGORY_ENUM_END_CATEGORY) + COUNT, + LAST = COUNT - 1, +}; +#undef CATEGORY_ENUM_BEGIN_CATEGORY +#undef CATEGORY_ENUM_SUBCATEGORY +#undef CATEGORY_ENUM_END_CATEGORY + +// An enum that lists just the categories without their subcategories. +#define SUPERCATEGORY_ENUM_BEGIN_CATEGORY(name, labelAsString, color) name, +#define SUPERCATEGORY_ENUM_SUBCATEGORY(supercategory, name, labelAsString) +#define SUPERCATEGORY_ENUM_END_CATEGORY +enum class ProfilingCategory : uint32_t { + MOZ_PROFILING_CATEGORY_LIST(SUPERCATEGORY_ENUM_BEGIN_CATEGORY, + SUPERCATEGORY_ENUM_SUBCATEGORY, + SUPERCATEGORY_ENUM_END_CATEGORY) + COUNT, + LAST = COUNT - 1, +}; +#undef SUPERCATEGORY_ENUM_BEGIN_CATEGORY +#undef SUPERCATEGORY_ENUM_SUBCATEGORY +#undef SUPERCATEGORY_ENUM_END_CATEGORY + +// clang-format on + +struct ProfilingCategoryPairInfo { + ProfilingCategory mCategory; + uint32_t mSubcategoryIndex; + const char* mLabel; +}; + +JS_PUBLIC_API const ProfilingCategoryPairInfo& GetProfilingCategoryPairInfo( + ProfilingCategoryPair aCategoryPair); + +} // namespace JS + +#endif /* js_ProfilingCategory_h */ diff --git a/js/public/ProfilingFrameIterator.h b/js/public/ProfilingFrameIterator.h new file mode 100644 index 0000000000..afae8a1da1 --- /dev/null +++ b/js/public/ProfilingFrameIterator.h @@ -0,0 +1,261 @@ +/* -*- 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 js_ProfilingFrameIterator_h +#define js_ProfilingFrameIterator_h + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include "jstypes.h" + +#include "js/GCAnnotations.h" +#include "js/TypeDecls.h" + +namespace js { +class Activation; +namespace jit { +class JitActivation; +class JSJitProfilingFrameIterator; +class JitcodeGlobalEntry; +} // namespace jit +namespace wasm { +class ProfilingFrameIterator; +} // namespace wasm +} // namespace js + +namespace JS { + +// This iterator can be used to walk the stack of a thread suspended at an +// arbitrary pc. To provide accurate results, profiling must have been enabled +// (via EnableRuntimeProfilingStack) before executing the callstack being +// unwound. +// +// Note that the caller must not do anything that could cause GC to happen while +// the iterator is alive, since this could invalidate Ion code and cause its +// contents to become out of date. +class MOZ_NON_PARAM JS_PUBLIC_API ProfilingFrameIterator { + public: + enum class Kind : bool { JSJit, Wasm }; + + private: + JSContext* cx_; + mozilla::Maybe<uint64_t> samplePositionInProfilerBuffer_; + js::Activation* activation_; + // For each JitActivation, this records the lowest (most recent) stack + // address. This will usually be either the exitFP of the activation or the + // frame or stack pointer of currently executing JIT/Wasm code. The Gecko + // profiler uses this to skip native frames between the activation and + // endStackAddress_. + void* endStackAddress_ = nullptr; + Kind kind_; + + static const unsigned StorageSpace = 8 * sizeof(void*); + alignas(void*) unsigned char storage_[StorageSpace]; + + void* storage() { return storage_; } + const void* storage() const { return storage_; } + + js::wasm::ProfilingFrameIterator& wasmIter() { + MOZ_ASSERT(!done()); + MOZ_ASSERT(isWasm()); + return *static_cast<js::wasm::ProfilingFrameIterator*>(storage()); + } + const js::wasm::ProfilingFrameIterator& wasmIter() const { + MOZ_ASSERT(!done()); + MOZ_ASSERT(isWasm()); + return *static_cast<const js::wasm::ProfilingFrameIterator*>(storage()); + } + + js::jit::JSJitProfilingFrameIterator& jsJitIter() { + MOZ_ASSERT(!done()); + MOZ_ASSERT(isJSJit()); + return *static_cast<js::jit::JSJitProfilingFrameIterator*>(storage()); + } + + const js::jit::JSJitProfilingFrameIterator& jsJitIter() const { + MOZ_ASSERT(!done()); + MOZ_ASSERT(isJSJit()); + return *static_cast<const js::jit::JSJitProfilingFrameIterator*>(storage()); + } + + void maybeSetEndStackAddress(void* addr) { + // If endStackAddress_ has already been set, don't change it because we + // want this to correspond to the most recent frame. + if (!endStackAddress_) { + endStackAddress_ = addr; + } + } + + void settleFrames(); + void settle(); + + public: + struct RegisterState { + RegisterState() : pc(nullptr), sp(nullptr), fp(nullptr), lr(nullptr) {} + void* pc; + void* sp; + void* fp; + void* lr; + }; + + ProfilingFrameIterator( + JSContext* cx, const RegisterState& state, + const mozilla::Maybe<uint64_t>& samplePositionInProfilerBuffer = + mozilla::Nothing()); + ~ProfilingFrameIterator(); + void operator++(); + bool done() const { return !activation_; } + + // Assuming the stack grows down (we do), the return value: + // - always points into the stack + // - is weakly monotonically increasing (may be equal for successive frames) + // - will compare greater than newer native and psuedo-stack frame addresses + // and less than older native and psuedo-stack frame addresses + void* stackAddress() const; + + enum FrameKind { + Frame_BaselineInterpreter, + Frame_Baseline, + Frame_Ion, + Frame_Wasm + }; + + struct Frame { + FrameKind kind; + void* stackAddress; + union { + void* returnAddress_; + jsbytecode* interpreterPC_; + }; + void* activation; + void* endStackAddress; + const char* label; + JSScript* interpreterScript; + uint64_t realmID; + + public: + void* returnAddress() const { + MOZ_ASSERT(kind != Frame_BaselineInterpreter); + return returnAddress_; + } + jsbytecode* interpreterPC() const { + MOZ_ASSERT(kind == Frame_BaselineInterpreter); + return interpreterPC_; + } + } JS_HAZ_GC_INVALIDATED; + + bool isWasm() const; + bool isJSJit() const; + + uint32_t extractStack(Frame* frames, uint32_t offset, uint32_t end) const; + + mozilla::Maybe<Frame> getPhysicalFrameWithoutLabel() const; + + // Return the registers from the native caller frame. + // Nothing{} if this iterator is NOT pointing at a native-to-JIT entry frame, + // or if the information is not accessible/implemented on this platform. + mozilla::Maybe<RegisterState> getCppEntryRegisters() const; + + private: + mozilla::Maybe<Frame> getPhysicalFrameAndEntry( + const js::jit::JitcodeGlobalEntry** entry) const; + + void iteratorConstruct(const RegisterState& state); + void iteratorConstruct(); + void iteratorDestroy(); + bool iteratorDone(); +} JS_HAZ_GC_INVALIDATED; + +JS_PUBLIC_API bool IsProfilingEnabledForContext(JSContext* cx); + +/** + * After each sample run, this method should be called with the current buffer + * position at which the buffer contents start. This will update the + * corresponding field on the JSRuntime. + * + * See the field |profilerSampleBufferRangeStart| on JSRuntime for documentation + * about what this value is used for. + */ +JS_PUBLIC_API void SetJSContextProfilerSampleBufferRangeStart( + JSContext* cx, uint64_t rangeStart); + +class ProfiledFrameRange; + +// A handle to the underlying JitcodeGlobalEntry, so as to avoid repeated +// lookups on JitcodeGlobalTable. +class MOZ_STACK_CLASS ProfiledFrameHandle { + friend class ProfiledFrameRange; + + JSRuntime* rt_; + js::jit::JitcodeGlobalEntry& entry_; + void* addr_; + void* canonicalAddr_; + const char* label_; + uint32_t depth_; + + ProfiledFrameHandle(JSRuntime* rt, js::jit::JitcodeGlobalEntry& entry, + void* addr, const char* label, uint32_t depth); + + public: + const char* label() const { return label_; } + uint32_t depth() const { return depth_; } + void* canonicalAddress() const { return canonicalAddr_; } + + JS_PUBLIC_API ProfilingFrameIterator::FrameKind frameKind() const; + + JS_PUBLIC_API uint64_t realmID() const; +}; + +class ProfiledFrameRange { + public: + class Iter final { + public: + Iter(const ProfiledFrameRange& range, uint32_t index) + : range_(range), index_(index) {} + + JS_PUBLIC_API ProfiledFrameHandle operator*() const; + + // Provide the bare minimum of iterator methods that are needed for + // C++ ranged for loops. + Iter& operator++() { + ++index_; + return *this; + } + bool operator==(const Iter& rhs) const { return index_ == rhs.index_; } + bool operator!=(const Iter& rhs) const { return !(*this == rhs); } + + private: + const ProfiledFrameRange& range_; + uint32_t index_; + }; + + Iter begin() const { return Iter(*this, 0); } + Iter end() const { return Iter(*this, depth_); } + + private: + friend JS_PUBLIC_API ProfiledFrameRange GetProfiledFrames(JSContext* cx, + void* addr); + + ProfiledFrameRange(JSRuntime* rt, void* addr, + js::jit::JitcodeGlobalEntry* entry) + : rt_(rt), addr_(addr), entry_(entry), depth_(0) {} + + JSRuntime* rt_; + void* addr_; + js::jit::JitcodeGlobalEntry* entry_; + // Assume maximum inlining depth is <64 + const char* labels_[64]; + uint32_t depth_; +}; + +// Returns a range that can be iterated over using C++ ranged for loops. +JS_PUBLIC_API ProfiledFrameRange GetProfiledFrames(JSContext* cx, void* addr); + +} // namespace JS + +#endif /* js_ProfilingFrameIterator_h */ diff --git a/js/public/ProfilingStack.h b/js/public/ProfilingStack.h new file mode 100644 index 0000000000..b9b96c27dc --- /dev/null +++ b/js/public/ProfilingStack.h @@ -0,0 +1,578 @@ +/* -*- 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 js_ProfilingStack_h +#define js_ProfilingStack_h + +#include "mozilla/Atomics.h" + +#include <stdint.h> + +#include "jstypes.h" + +#include "js/ProfilingCategory.h" +#include "js/TypeDecls.h" + +class JS_PUBLIC_API JSTracer; +class JS_PUBLIC_API ProfilingStack; + +// This file defines the classes ProfilingStack and ProfilingStackFrame. +// The ProfilingStack manages an array of ProfilingStackFrames. +// It keeps track of the "label stack" and the JS interpreter stack. +// The two stack types are interleaved. +// +// Usage: +// +// ProfilingStack* profilingStack = ...; +// +// // For label frames: +// profilingStack->pushLabelFrame(...); +// // Execute some code. When finished, pop the frame: +// profilingStack->pop(); +// +// // For JS stack frames: +// profilingStack->pushJSFrame(...); +// // Execute some code. When finished, pop the frame: +// profilingStack->pop(); +// +// +// Concurrency considerations +// +// A thread's profiling stack (and the frames inside it) is only modified by +// that thread. However, the profiling stack can be *read* by a different +// thread, the sampler thread: Whenever the profiler wants to sample a given +// thread A, the following happens: +// (1) Thread A is suspended. +// (2) The sampler thread (thread S) reads the ProfilingStack of thread A, +// including all ProfilingStackFrames that are currently in that stack +// (profilingStack->frames[0..profilingStack->stackSize()]). +// (3) Thread A is resumed. +// +// Thread suspension is achieved using platform-specific APIs; refer to each +// platform's Sampler::SuspendAndSampleAndResumeThread implementation in +// platform-*.cpp for details. +// +// When the thread is suspended, the values in profilingStack->stackPointer and +// in the stack frame range +// profilingStack->frames[0..profilingStack->stackPointer] need to be in a +// consistent state, so that thread S does not read partially- constructed stack +// frames. More specifically, we have two requirements: +// (1) When adding a new frame at the top of the stack, its ProfilingStackFrame +// data needs to be put in place *before* the stackPointer is incremented, +// and the compiler + CPU need to know that this order matters. +// (2) When popping an frame from the stack and then preparing the +// ProfilingStackFrame data for the next frame that is about to be pushed, +// the decrement of the stackPointer in pop() needs to happen *before* the +// ProfilingStackFrame for the new frame is being popuplated, and the +// compiler + CPU need to know that this order matters. +// +// We can express the relevance of these orderings in multiple ways. +// Option A is to make stackPointer an atomic with SequentiallyConsistent +// memory ordering. This would ensure that no writes in thread A would be +// reordered across any writes to stackPointer, which satisfies requirements +// (1) and (2) at the same time. Option A is the simplest. +// Option B is to use ReleaseAcquire memory ordering both for writes to +// stackPointer *and* for writes to ProfilingStackFrame fields. Release-stores +// ensure that all writes that happened *before this write in program order* are +// not reordered to happen after this write. ReleaseAcquire ordering places no +// requirements on the ordering of writes that happen *after* this write in +// program order. +// Using release-stores for writes to stackPointer expresses requirement (1), +// and using release-stores for writes to the ProfilingStackFrame fields +// expresses requirement (2). +// +// Option B is more complicated than option A, but has much better performance +// on x86/64: In a microbenchmark run on a Macbook Pro from 2017, switching +// from option A to option B reduced the overhead of pushing+popping a +// ProfilingStackFrame by 10 nanoseconds. +// On x86/64, release-stores require no explicit hardware barriers or lock +// instructions. +// On ARM/64, option B may be slower than option A, because the compiler will +// generate hardware barriers for every single release-store instead of just +// for the writes to stackPointer. However, the actual performance impact of +// this has not yet been measured on ARM, so we're currently using option B +// everywhere. This is something that we may want to change in the future once +// we've done measurements. + +namespace js { + +// A call stack can be specified to the JS engine such that all JS entry/exits +// to functions push/pop a stack frame to/from the specified stack. +// +// For more detailed information, see vm/GeckoProfiler.h. +// +class ProfilingStackFrame { + // A ProfilingStackFrame represents either a label frame or a JS frame. + + // WARNING WARNING WARNING + // + // All the fields below are Atomic<...,ReleaseAcquire>. This is needed so + // that writes to these fields are release-writes, which ensures that + // earlier writes in this thread don't get reordered after the writes to + // these fields. In particular, the decrement of the stack pointer in + // ProfilingStack::pop() is a write that *must* happen before the values in + // this ProfilingStackFrame are changed. Otherwise, the sampler thread might + // see an inconsistent state where the stack pointer still points to a + // ProfilingStackFrame which has already been popped off the stack and whose + // fields have now been partially repopulated with new values. + // See the "Concurrency considerations" paragraph at the top of this file + // for more details. + + // Descriptive label for this stack frame. Must be a static string! Can be + // an empty string, but not a null pointer. + mozilla::Atomic<const char*, mozilla::ReleaseAcquire> label_; + + // An additional descriptive string of this frame which is combined with + // |label_| in profiler output. Need not be (and usually isn't) static. Can + // be null. + mozilla::Atomic<const char*, mozilla::ReleaseAcquire> dynamicString_; + + // Stack pointer for non-JS stack frames, the script pointer otherwise. + mozilla::Atomic<void*, mozilla::ReleaseAcquire> spOrScript; + + // ID of the JS Realm for JS stack frames. + // Must not be used on non-JS frames; it'll contain either the default 0, + // or a leftover value from a previous JS stack frame that was using this + // ProfilingStackFrame object. + mozilla::Atomic<uint64_t, mozilla::ReleaseAcquire> realmID_; + + // The bytecode offset for JS stack frames. + // Must not be used on non-JS frames; it'll contain either the default 0, + // or a leftover value from a previous JS stack frame that was using this + // ProfilingStackFrame object. + mozilla::Atomic<int32_t, mozilla::ReleaseAcquire> pcOffsetIfJS_; + + // Bits 0...8 hold the Flags. Bits 9...31 hold the category pair. + mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> flagsAndCategoryPair_; + + static int32_t pcToOffset(JSScript* aScript, jsbytecode* aPc); + + public: + ProfilingStackFrame() = default; + ProfilingStackFrame& operator=(const ProfilingStackFrame& other) { + label_ = other.label(); + dynamicString_ = other.dynamicString(); + void* spScript = other.spOrScript; + spOrScript = spScript; + int32_t offsetIfJS = other.pcOffsetIfJS_; + pcOffsetIfJS_ = offsetIfJS; + uint64_t realmID = other.realmID_; + realmID_ = realmID; + uint32_t flagsAndCategory = other.flagsAndCategoryPair_; + flagsAndCategoryPair_ = flagsAndCategory; + return *this; + } + + // Reserve up to 16 bits for flags, and 16 for category pair. + enum class Flags : uint32_t { + // The first three flags describe the kind of the frame and are + // mutually exclusive. (We still give them individual bits for + // simplicity.) + + // A regular label frame. These usually come from AutoProfilerLabel. + IS_LABEL_FRAME = 1 << 0, + + // A special frame indicating the start of a run of JS profiling stack + // frames. IS_SP_MARKER_FRAME frames are ignored, except for the sp + // field. These frames are needed to get correct ordering between JS + // and LABEL frames because JS frames don't carry sp information. + // SP is short for "stack pointer". + IS_SP_MARKER_FRAME = 1 << 1, + + // A JS frame. + IS_JS_FRAME = 1 << 2, + + // An interpreter JS frame that has OSR-ed into baseline. IS_JS_FRAME + // frames can have this flag set and unset during their lifetime. + // JS_OSR frames are ignored. + JS_OSR = 1 << 3, + + // The next three are mutually exclusive. + // By default, for profiling stack frames that have both a label and a + // dynamic string, the two strings are combined into one string of the + // form "<label> <dynamicString>" during JSON serialization. The + // following flags can be used to change this preset. + STRING_TEMPLATE_METHOD = 1 << 4, // "<label>.<dynamicString>" + STRING_TEMPLATE_GETTER = 1 << 5, // "get <label>.<dynamicString>" + STRING_TEMPLATE_SETTER = 1 << 6, // "set <label>.<dynamicString>" + + // If set, causes this stack frame to be marked as "relevantForJS" in + // the profile JSON, which will make it show up in the "JS only" call + // tree view. + RELEVANT_FOR_JS = 1 << 7, + + // If set, causes the label on this ProfilingStackFrame to be ignored + // and to be replaced by the subcategory's label. + LABEL_DETERMINED_BY_CATEGORY_PAIR = 1 << 8, + + // Frame dynamic string does not contain user data. + NONSENSITIVE = 1 << 9, + + // A JS Baseline Interpreter frame. + IS_BLINTERP_FRAME = 1 << 10, + + FLAGS_BITCOUNT = 16, + FLAGS_MASK = (1 << FLAGS_BITCOUNT) - 1 + }; + + static_assert( + uint32_t(JS::ProfilingCategoryPair::LAST) <= + (UINT32_MAX >> uint32_t(Flags::FLAGS_BITCOUNT)), + "Too many category pairs to fit into u32 with together with the " + "reserved bits for the flags"); + + bool isLabelFrame() const { + return uint32_t(flagsAndCategoryPair_) & uint32_t(Flags::IS_LABEL_FRAME); + } + + bool isNonsensitive() const { + return uint32_t(flagsAndCategoryPair_) & uint32_t(Flags::NONSENSITIVE); + } + + bool isSpMarkerFrame() const { + return uint32_t(flagsAndCategoryPair_) & + uint32_t(Flags::IS_SP_MARKER_FRAME); + } + + bool isJsFrame() const { + return uint32_t(flagsAndCategoryPair_) & uint32_t(Flags::IS_JS_FRAME); + } + + bool isJsBlinterpFrame() const { + return uint32_t(flagsAndCategoryPair_) & uint32_t(Flags::IS_BLINTERP_FRAME); + } + + bool isOSRFrame() const { + return uint32_t(flagsAndCategoryPair_) & uint32_t(Flags::JS_OSR); + } + + void setIsOSRFrame(bool isOSR) { + if (isOSR) { + flagsAndCategoryPair_ = + uint32_t(flagsAndCategoryPair_) | uint32_t(Flags::JS_OSR); + } else { + flagsAndCategoryPair_ = + uint32_t(flagsAndCategoryPair_) & ~uint32_t(Flags::JS_OSR); + } + } + + void setLabelCategory(JS::ProfilingCategoryPair aCategoryPair) { + MOZ_ASSERT(isLabelFrame()); + flagsAndCategoryPair_ = + (uint32_t(aCategoryPair) << uint32_t(Flags::FLAGS_BITCOUNT)) | flags(); + } + + const char* label() const { + uint32_t flagsAndCategoryPair = flagsAndCategoryPair_; + if (flagsAndCategoryPair & + uint32_t(Flags::LABEL_DETERMINED_BY_CATEGORY_PAIR)) { + auto categoryPair = JS::ProfilingCategoryPair( + flagsAndCategoryPair >> uint32_t(Flags::FLAGS_BITCOUNT)); + return JS::GetProfilingCategoryPairInfo(categoryPair).mLabel; + } + return label_; + } + + const char* dynamicString() const { return dynamicString_; } + + void initLabelFrame(const char* aLabel, const char* aDynamicString, void* sp, + JS::ProfilingCategoryPair aCategoryPair, + uint32_t aFlags) { + label_ = aLabel; + dynamicString_ = aDynamicString; + spOrScript = sp; + // pcOffsetIfJS_ is not set and must not be used on label frames. + flagsAndCategoryPair_ = + uint32_t(Flags::IS_LABEL_FRAME) | + (uint32_t(aCategoryPair) << uint32_t(Flags::FLAGS_BITCOUNT)) | aFlags; + MOZ_ASSERT(isLabelFrame()); + } + + void initSpMarkerFrame(void* sp) { + label_ = ""; + dynamicString_ = nullptr; + spOrScript = sp; + // pcOffsetIfJS_ is not set and must not be used on sp marker frames. + flagsAndCategoryPair_ = uint32_t(Flags::IS_SP_MARKER_FRAME) | + (uint32_t(JS::ProfilingCategoryPair::OTHER) + << uint32_t(Flags::FLAGS_BITCOUNT)); + MOZ_ASSERT(isSpMarkerFrame()); + } + + template <JS::ProfilingCategoryPair Category, uint32_t ExtraFlags = 0> + void initJsFrame(const char* aLabel, const char* aDynamicString, + JSScript* aScript, jsbytecode* aPc, uint64_t aRealmID) { + label_ = aLabel; + dynamicString_ = aDynamicString; + spOrScript = aScript; + pcOffsetIfJS_ = pcToOffset(aScript, aPc); + realmID_ = aRealmID; + flagsAndCategoryPair_ = + (uint32_t(Category) << uint32_t(Flags::FLAGS_BITCOUNT)) | + uint32_t(Flags::IS_JS_FRAME) | ExtraFlags; + MOZ_ASSERT(isJsFrame()); + } + + uint32_t flags() const { + return uint32_t(flagsAndCategoryPair_) & uint32_t(Flags::FLAGS_MASK); + } + + JS::ProfilingCategoryPair categoryPair() const { + return JS::ProfilingCategoryPair(flagsAndCategoryPair_ >> + uint32_t(Flags::FLAGS_BITCOUNT)); + } + + uint64_t realmID() const { return realmID_; } + + void* stackAddress() const { + MOZ_ASSERT(!isJsFrame()); + return spOrScript; + } + + JS_PUBLIC_API JSScript* script() const; + + JS_PUBLIC_API JSFunction* function() const; + + // Note that the pointer returned might be invalid. + JSScript* rawScript() const { + MOZ_ASSERT(isJsFrame()); + void* script = spOrScript; + return static_cast<JSScript*>(script); + } + + // We can't know the layout of JSScript, so look in vm/GeckoProfiler.cpp. + JS_PUBLIC_API jsbytecode* pc() const; + void setPC(jsbytecode* pc); + + void trace(JSTracer* trc); + + // The offset of a pc into a script's code can actually be 0, so to + // signify a nullptr pc, use a -1 index. This is checked against in + // pc() and setPC() to set/get the right pc. + static const int32_t NullPCOffset = -1; +}; + +JS_PUBLIC_API void SetContextProfilingStack(JSContext* cx, + ProfilingStack* profilingStack); + +// GetContextProfilingStack also exists, but it's defined in RootingAPI.h. + +JS_PUBLIC_API void EnableContextProfilingStack(JSContext* cx, bool enabled); + +JS_PUBLIC_API void RegisterContextProfilingEventMarker(JSContext* cx, + void (*fn)(const char*, + const char*)); + +} // namespace js + +namespace JS { + +typedef ProfilingStack* (*RegisterThreadCallback)(const char* threadName, + void* stackBase); + +typedef void (*UnregisterThreadCallback)(); + +// regiserThread and unregisterThread callbacks are functions which are called +// by other threads without any locking mechanism. +JS_PUBLIC_API void SetProfilingThreadCallbacks( + RegisterThreadCallback registerThread, + UnregisterThreadCallback unregisterThread); + +} // namespace JS + +// Each thread has its own ProfilingStack. That thread modifies the +// ProfilingStack, pushing and popping elements as necessary. +// +// The ProfilingStack is also read periodically by the profiler's sampler +// thread. This happens only when the thread that owns the ProfilingStack is +// suspended. So there are no genuine parallel accesses. +// +// However, it is possible for pushing/popping to be interrupted by a periodic +// sample. Because of this, we need pushing/popping to be effectively atomic. +// +// - When pushing a new frame, we increment the stack pointer -- making the new +// frame visible to the sampler thread -- only after the new frame has been +// fully written. The stack pointer is Atomic<uint32_t,ReleaseAcquire>, so +// the increment is a release-store, which ensures that this store is not +// reordered before the writes of the frame. +// +// - When popping an old frame, the only operation is the decrementing of the +// stack pointer, which is obviously atomic. +// +class JS_PUBLIC_API ProfilingStack final { + public: + ProfilingStack() = default; + + ~ProfilingStack(); + + void pushLabelFrame(const char* label, const char* dynamicString, void* sp, + JS::ProfilingCategoryPair categoryPair, + uint32_t flags = 0) { + // This thread is the only one that ever changes the value of + // stackPointer. + // Store the value of the atomic in a non-atomic local variable so that + // the compiler won't generate two separate loads from the atomic for + // the size check and the frames[] array indexing operation. + uint32_t stackPointerVal = stackPointer; + + if (MOZ_UNLIKELY(stackPointerVal >= capacity)) { + ensureCapacitySlow(); + } + frames[stackPointerVal].initLabelFrame(label, dynamicString, sp, + categoryPair, flags); + + // This must happen at the end! The compiler will not reorder this + // update because stackPointer is Atomic<..., ReleaseAcquire>, so any + // the writes above will not be reordered below the stackPointer store. + // Do the read and the write as two separate statements, in order to + // make it clear that we don't need an atomic increment, which would be + // more expensive on x86 than the separate operations done here. + // However, don't use stackPointerVal here; instead, allow the compiler + // to turn this store into a non-atomic increment instruction which + // takes up less code size. + stackPointer = stackPointer + 1; + } + + void pushSpMarkerFrame(void* sp) { + uint32_t oldStackPointer = stackPointer; + + if (MOZ_UNLIKELY(oldStackPointer >= capacity)) { + ensureCapacitySlow(); + } + frames[oldStackPointer].initSpMarkerFrame(sp); + + // This must happen at the end, see the comment in pushLabelFrame. + stackPointer = oldStackPointer + 1; + } + + void pushJsFrame(const char* label, const char* dynamicString, + JSScript* script, jsbytecode* pc, uint64_t aRealmID) { + // This thread is the only one that ever changes the value of + // stackPointer. Only load the atomic once. + uint32_t oldStackPointer = stackPointer; + + if (MOZ_UNLIKELY(oldStackPointer >= capacity)) { + ensureCapacitySlow(); + } + frames[oldStackPointer] + .initJsFrame<JS::ProfilingCategoryPair::JS_Interpreter>( + label, dynamicString, script, pc, aRealmID); + + // This must happen at the end, see the comment in pushLabelFrame. + stackPointer = stackPointer + 1; + } + + void pop() { + MOZ_ASSERT(stackPointer > 0); + // Do the read and the write as two separate statements, in order to + // make it clear that we don't need an atomic decrement, which would be + // more expensive on x86 than the separate operations done here. + // This thread is the only one that ever changes the value of + // stackPointer. + uint32_t oldStackPointer = stackPointer; + stackPointer = oldStackPointer - 1; + } + + uint32_t stackSize() const { return stackPointer; } + uint32_t stackCapacity() const { return capacity; } + + private: + // Out of line path for expanding the buffer, since otherwise this would get + // inlined in every DOM WebIDL call. + MOZ_COLD void ensureCapacitySlow(); + + // No copying. + ProfilingStack(const ProfilingStack&) = delete; + void operator=(const ProfilingStack&) = delete; + + // No moving either. + ProfilingStack(ProfilingStack&&) = delete; + void operator=(ProfilingStack&&) = delete; + + uint32_t capacity = 0; + + public: + // The pointer to the stack frames, this is read from the profiler thread and + // written from the current thread. + // + // This is effectively a unique pointer. + mozilla::Atomic<js::ProfilingStackFrame*, mozilla::SequentiallyConsistent> + frames{nullptr}; + + // This may exceed the capacity, so instead use the stackSize() method to + // determine the number of valid frames in stackFrames. When this is less + // than stackCapacity(), it refers to the first free stackframe past the top + // of the in-use stack (i.e. frames[stackPointer - 1] is the top stack + // frame). + // + // WARNING WARNING WARNING + // + // This is an atomic variable that uses ReleaseAcquire memory ordering. + // See the "Concurrency considerations" paragraph at the top of this file + // for more details. + mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> stackPointer{0}; +}; + +namespace js { + +class AutoGeckoProfilerEntry; +class GeckoProfilerEntryMarker; +class GeckoProfilerBaselineOSRMarker; + +class GeckoProfilerThread { + friend class AutoGeckoProfilerEntry; + friend class GeckoProfilerEntryMarker; + friend class GeckoProfilerBaselineOSRMarker; + + ProfilingStack* profilingStack_; + + // Same as profilingStack_ if the profiler is currently active, otherwise + // null. + ProfilingStack* profilingStackIfEnabled_; + + public: + GeckoProfilerThread(); + + uint32_t stackPointer() { + MOZ_ASSERT(infraInstalled()); + return profilingStack_->stackPointer; + } + ProfilingStackFrame* stack() { return profilingStack_->frames; } + ProfilingStack* getProfilingStack() { return profilingStack_; } + ProfilingStack* getProfilingStackIfEnabled() { + return profilingStackIfEnabled_; + } + + /* + * True if the profiler infrastructure is setup. Should be true in builds + * that include profiler support except during early startup or late + * shutdown. Unrelated to the presence of the Gecko Profiler addon. + */ + bool infraInstalled() { return profilingStack_ != nullptr; } + + void setProfilingStack(ProfilingStack* profilingStack, bool enabled); + void enable(bool enable) { + profilingStackIfEnabled_ = enable ? profilingStack_ : nullptr; + } + void trace(JSTracer* trc); + + /* + * Functions which are the actual instrumentation to track run information + * + * - enter: a function has started to execute + * - updatePC: updates the pc information about where a function + * is currently executing + * - exit: this function has ceased execution, and no further + * entries/exits will be made + */ + bool enter(JSContext* cx, JSScript* script); + void exit(JSContext* cx, JSScript* script); + inline void updatePC(JSContext* cx, JSScript* script, jsbytecode* pc); +}; + +} // namespace js + +#endif /* js_ProfilingStack_h */ diff --git a/js/public/Promise.h b/js/public/Promise.h new file mode 100644 index 0000000000..221d6fdfee --- /dev/null +++ b/js/public/Promise.h @@ -0,0 +1,599 @@ +/* -*- 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_Promise_h +#define js_Promise_h + +#include "mozilla/Attributes.h" + +#include "jstypes.h" + +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" +#include "js/UniquePtr.h" + +namespace JS { + +class JS_PUBLIC_API AutoDebuggerJobQueueInterruption; + +/** + * Abstract base class for an ECMAScript Job Queue: + * https://www.ecma-international.org/ecma-262/9.0/index.html#sec-jobs-and-job-queues + * + * SpiderMonkey doesn't schedule Promise resolution jobs itself; instead, the + * embedding can provide an instance of this class SpiderMonkey can use to do + * that scheduling. + * + * The JavaScript shell includes a simple implementation adequate for running + * tests. Browsers need to augment job handling to meet their own additional + * requirements, so they can provide their own implementation. + */ +class JS_PUBLIC_API JobQueue { + public: + virtual ~JobQueue() = default; + + /** + * Ask the embedding for the incumbent global. + * + * SpiderMonkey doesn't itself have a notion of incumbent globals as defined + * by the HTML spec, so we need the embedding to provide this. See + * dom/script/ScriptSettings.h for details. + */ + virtual JSObject* getIncumbentGlobal(JSContext* cx) = 0; + + /** + * Enqueue a reaction job `job` for `promise`, which was allocated at + * `allocationSite`. Provide `incumbentGlobal` as the incumbent global for + * the reaction job's execution. + * + * `promise` can be null if the promise is optimized out. + * `promise` is guaranteed not to be optimized out if the promise has + * non-default user-interaction flag. + */ + virtual bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise, + JS::HandleObject job, + JS::HandleObject allocationSite, + JS::HandleObject incumbentGlobal) = 0; + + /** + * Run all jobs in the queue. Running one job may enqueue others; continue to + * run jobs until the queue is empty. + * + * Calling this method at the wrong time can break the web. The HTML spec + * indicates exactly when the job queue should be drained (in HTML jargon, + * when it should "perform a microtask checkpoint"), and doing so at other + * times can incompatibly change the semantics of programs that use promises + * or other microtask-based features. + * + * This method is called only via AutoDebuggerJobQueueInterruption, used by + * the Debugger API implementation to ensure that the debuggee's job queue is + * protected from the debugger's own activity. See the comments on + * AutoDebuggerJobQueueInterruption. + */ + virtual void runJobs(JSContext* cx) = 0; + + /** + * Return true if the job queue is empty, false otherwise. + */ + virtual bool empty() const = 0; + + protected: + friend class AutoDebuggerJobQueueInterruption; + + /** + * A saved job queue, represented however the JobQueue implementation pleases. + * Use AutoDebuggerJobQueueInterruption rather than trying to construct one of + * these directly; see documentation there. + * + * Destructing an instance of this class should assert that the current queue + * is empty, and then restore the queue the instance captured. + */ + class SavedJobQueue { + public: + virtual ~SavedJobQueue() = default; + }; + + /** + * Capture this JobQueue's current job queue as a SavedJobQueue and return it, + * leaving the JobQueue's job queue empty. Destroying the returned object + * should assert that this JobQueue's current job queue is empty, and restore + * the original queue. + * + * On OOM, this should call JS_ReportOutOfMemory on the given JSContext, + * and return a null UniquePtr. + */ + virtual js::UniquePtr<SavedJobQueue> saveJobQueue(JSContext*) = 0; +}; + +/** + * Tell SpiderMonkey to use `queue` to schedule promise reactions. + * + * SpiderMonkey does not take ownership of the queue; it is the embedding's + * responsibility to clean it up after the runtime is destroyed. + */ +extern JS_PUBLIC_API void SetJobQueue(JSContext* cx, JobQueue* queue); + +/** + * [SMDOC] Protecting the debuggee's job/microtask queue from debugger activity. + * + * When the JavaScript debugger interrupts the execution of some debuggee code + * (for a breakpoint, for example), the debuggee's execution must be paused + * while the developer takes time to look at it. During this interruption, other + * tabs should remain active and usable. If the debuggee shares a main thread + * with non-debuggee tabs, that means that the thread will have to process + * non-debuggee HTML tasks and microtasks as usual, even as the debuggee's are + * on hold until the debugger lets it continue execution. (Letting debuggee + * microtasks run during the interruption would mean that, from the debuggee's + * point of view, their side effects would take place wherever the breakpoint + * was set - in general, not a place other code should ever run, and a violation + * of the run-to-completion rule.) + * + * This means that, even though the timing and ordering of microtasks is + * carefully specified by the standard - and important to preserve for + * compatibility and predictability - debugger use may, correctly, have the + * effect of reordering microtasks. During the interruption, microtasks enqueued + * by non-debuggee tabs must run immediately alongside their HTML tasks as + * usual, whereas any debuggee microtasks that were in the queue when the + * interruption began must wait for the debuggee to be continued - and thus run + * after microtasks enqueued after they were. + * + * Fortunately, this reordering is visible only at the global level: when + * implemented correctly, it is not detectable by an individual debuggee. Note + * that a debuggee should generally be a complete unit of similar-origin related + * browsing contexts. Since non-debuggee activity falls outside that unit, it + * should never be visible to the debuggee (except via mechanisms that are + * already asynchronous, like events), so the debuggee should be unable to + * detect non-debuggee microtasks running when they normally would not. As long + * as behavior *visible to the debuggee* is unaffected by the interruption, we + * have respected the spirit of the rule. + * + * Of course, even as we accept the general principle that interrupting the + * debuggee should have as little detectable effect as possible, we still permit + * the developer to do things like evaluate expressions at the console that have + * arbitrary effects on the debuggee's state—effects that could never occur + * naturally at that point in the program. But since these are explicitly + * requested by the developer, who presumably knows what they're doing, we + * support this as best we can. If the developer evaluates an expression in the + * console that resolves a promise, it seems most natural for the promise's + * reaction microtasks to run immediately, within the interruption. This is an + * 'unnatural' time for the microtasks to run, but no more unnatural than the + * evaluation that triggered them. + * + * So the overall behavior we need is as follows: + * + * - When the debugger interrupts a debuggee, the debuggee's microtask queue + * must be saved. + * + * - When debuggee execution resumes, the debuggee's microtask queue must be + * restored exactly as it was when the interruption occurred. + * + * - Non-debuggee task and microtask execution must take place normally during + * the interruption. + * + * Since each HTML task begins with an empty microtask queue, and it should not + * be possible for a task to mix debuggee and non-debuggee code, interrupting a + * debuggee should always find a microtask queue containing exclusively debuggee + * microtasks, if any. So saving and restoring the microtask queue should affect + * only the debuggee, not any non-debuggee content. + * + * AutoDebuggerJobQueueInterruption + * -------------------------------- + * + * AutoDebuggerJobQueueInterruption is an RAII class, meant for use by the + * Debugger API implementation, that takes care of saving and restoring the + * queue. + * + * Constructing and initializing an instance of AutoDebuggerJobQueueInterruption + * sets aside the given JSContext's job queue, leaving the JSContext's queue + * empty. When the AutoDebuggerJobQueueInterruption instance is destroyed, it + * asserts that the JSContext's current job queue (holding jobs enqueued while + * the AutoDebuggerJobQueueInterruption was alive) is empty, and restores the + * saved queue to the JSContext. + * + * Since the Debugger API's behavior is up to us, we can specify that Debugger + * hooks begin execution with an empty job queue, and that we drain the queue + * after each hook function has run. This drain will be visible to debugger + * hooks, and makes hook calls resemble HTML tasks, with their own automatic + * microtask checkpoint. But, the drain will be invisible to the debuggee, as + * its queue is preserved across the hook invocation. + * + * To protect the debuggee's job queue, Debugger takes care to invoke callback + * functions only within the scope of an AutoDebuggerJobQueueInterruption + * instance. + * + * Why not let the hook functions themselves take care of this? + * ------------------------------------------------------------ + * + * Certainly, we could leave responsibility for saving and restoring the job + * queue to the Debugger hook functions themselves. + * + * In fact, early versions of this change tried making the devtools server save + * and restore the queue explicitly, but because hooks are set and changed in + * numerous places, it was hard to be confident that every case had been + * covered, and it seemed that future changes could easily introduce new holes. + * + * Later versions of this change modified the accessor properties on the + * Debugger objects' prototypes to automatically protect the job queue when + * calling hooks, but the effect was essentially a monkeypatch applied to an API + * we defined and control, which doesn't make sense. + * + * In the end, since promises have become such a pervasive part of JavaScript + * programming, almost any imaginable use of Debugger would need to provide some + * kind of protection for the debuggee's job queue, so it makes sense to simply + * handle it once, carefully, in the implementation of Debugger itself. + */ +class MOZ_RAII JS_PUBLIC_API AutoDebuggerJobQueueInterruption { + public: + explicit AutoDebuggerJobQueueInterruption(); + ~AutoDebuggerJobQueueInterruption(); + + bool init(JSContext* cx); + bool initialized() const { return !!saved; } + + /** + * Drain the job queue. (In HTML terminology, perform a microtask checkpoint.) + * + * To make Debugger hook calls more like HTML tasks or ECMAScript jobs, + * Debugger promises that each hook begins execution with a clean microtask + * queue, and that a microtask checkpoint (queue drain) takes place after each + * hook returns, successfully or otherwise. + * + * To ensure these debugger-introduced microtask checkpoints serve only the + * hook's microtasks, and never affect the debuggee's, the Debugger API + * implementation uses only this method to perform the checkpoints, thereby + * statically ensuring that an AutoDebuggerJobQueueInterruption is in scope to + * protect the debuggee. + * + * SavedJobQueue implementations are required to assert that the queue is + * empty before restoring the debuggee's queue. If the Debugger API ever fails + * to perform a microtask checkpoint after calling a hook, that assertion will + * fail, catching the mistake. + */ + void runJobs(); + + private: + JSContext* cx; + js::UniquePtr<JobQueue::SavedJobQueue> saved; +}; + +enum class PromiseRejectionHandlingState { Unhandled, Handled }; + +typedef void (*PromiseRejectionTrackerCallback)( + JSContext* cx, bool mutedErrors, JS::HandleObject promise, + JS::PromiseRejectionHandlingState state, void* data); + +/** + * Sets the callback that's invoked whenever a Promise is rejected without + * a rejection handler, and when a Promise that was previously rejected + * without a handler gets a handler attached. + */ +extern JS_PUBLIC_API void SetPromiseRejectionTrackerCallback( + JSContext* cx, PromiseRejectionTrackerCallback callback, + void* data = nullptr); + +/** + * Inform the runtime that the job queue is empty and the embedding is going to + * execute its last promise job. The runtime may now choose to skip creating + * promise jobs for asynchronous execution and instead continue execution + * synchronously. More specifically, this optimization is used to skip the + * standard job queuing behavior for `await` operations in async functions. + * + * This function may be called before executing the last job in the job queue. + * When it was called, JobQueueMayNotBeEmpty must be called in order to restore + * the default job queuing behavior before the embedding enqueues its next job + * into the job queue. + */ +extern JS_PUBLIC_API void JobQueueIsEmpty(JSContext* cx); + +/** + * Inform the runtime that job queue is no longer empty. The runtime can now no + * longer skip creating promise jobs for asynchronous execution, because + * pending jobs in the job queue must be executed first to preserve the FIFO + * (first in - first out) property of the queue. This effectively undoes + * JobQueueIsEmpty and re-enables the standard job queuing behavior. + * + * This function must be called whenever enqueuing a job to the job queue when + * JobQueueIsEmpty was called previously. + */ +extern JS_PUBLIC_API void JobQueueMayNotBeEmpty(JSContext* cx); + +/** + * Returns a new instance of the Promise builtin class in the current + * compartment, with the right slot layout. + * + * The `executor` can be a `nullptr`. In that case, the only way to resolve or + * reject the returned promise is via the `JS::ResolvePromise` and + * `JS::RejectPromise` JSAPI functions. + */ +extern JS_PUBLIC_API JSObject* NewPromiseObject(JSContext* cx, + JS::HandleObject executor); + +/** + * Returns true if the given object is an unwrapped PromiseObject, false + * otherwise. + */ +extern JS_PUBLIC_API bool IsPromiseObject(JS::HandleObject obj); + +/** + * Returns the current compartment's original Promise constructor. + */ +extern JS_PUBLIC_API JSObject* GetPromiseConstructor(JSContext* cx); + +/** + * Returns the current compartment's original Promise.prototype. + */ +extern JS_PUBLIC_API JSObject* GetPromisePrototype(JSContext* cx); + +// Keep this in sync with the PROMISE_STATE defines in SelfHostingDefines.h. +enum class PromiseState { Pending, Fulfilled, Rejected }; + +/** + * Returns the given Promise's state as a JS::PromiseState enum value. + * + * Returns JS::PromiseState::Pending if the given object is a wrapper that + * can't safely be unwrapped. + */ +extern JS_PUBLIC_API PromiseState GetPromiseState(JS::HandleObject promise); + +/** + * Returns the given Promise's process-unique ID. + */ +JS_PUBLIC_API uint64_t GetPromiseID(JS::HandleObject promise); + +/** + * Returns the given Promise's result: either the resolution value for + * fulfilled promises, or the rejection reason for rejected ones. + */ +extern JS_PUBLIC_API JS::Value GetPromiseResult(JS::HandleObject promise); + +/** + * Returns whether the given promise's rejection is already handled or not. + * + * The caller must check the given promise is rejected before checking it's + * handled or not. + */ +extern JS_PUBLIC_API bool GetPromiseIsHandled(JS::HandleObject promise); + +/* + * Given a settled (i.e. fulfilled or rejected, not pending) promise, sets + * |promise.[[PromiseIsHandled]]| to true and removes it from the list of + * unhandled rejected promises. + */ +extern JS_PUBLIC_API bool SetSettledPromiseIsHandled(JSContext* cx, + JS::HandleObject promise); + +/* + * Given a promise (settled or not), sets |promise.[[PromiseIsHandled]]| to true + * and removes it from the list of unhandled rejected promises if it's settled. + */ +[[nodiscard]] extern JS_PUBLIC_API bool SetAnyPromiseIsHandled( + JSContext* cx, JS::HandleObject promise); + +/** + * Returns a js::SavedFrame linked list of the stack that lead to the given + * Promise's allocation. + */ +extern JS_PUBLIC_API JSObject* GetPromiseAllocationSite( + JS::HandleObject promise); + +extern JS_PUBLIC_API JSObject* GetPromiseResolutionSite( + JS::HandleObject promise); + +#ifdef DEBUG +extern JS_PUBLIC_API void DumpPromiseAllocationSite(JSContext* cx, + JS::HandleObject promise); + +extern JS_PUBLIC_API void DumpPromiseResolutionSite(JSContext* cx, + JS::HandleObject promise); +#endif + +/** + * Calls the current compartment's original Promise.resolve on the original + * Promise constructor, with `resolutionValue` passed as an argument. + */ +extern JS_PUBLIC_API JSObject* CallOriginalPromiseResolve( + JSContext* cx, JS::HandleValue resolutionValue); + +/** + * Calls the current compartment's original Promise.reject on the original + * Promise constructor, with `resolutionValue` passed as an argument. + */ +extern JS_PUBLIC_API JSObject* CallOriginalPromiseReject( + JSContext* cx, JS::HandleValue rejectionValue); + +/** + * Resolves the given Promise with the given `resolutionValue`. + * + * Calls the `resolve` function that was passed to the executor function when + * the Promise was created. + */ +extern JS_PUBLIC_API bool ResolvePromise(JSContext* cx, + JS::HandleObject promiseObj, + JS::HandleValue resolutionValue); + +/** + * Rejects the given `promise` with the given `rejectionValue`. + * + * Calls the `reject` function that was passed to the executor function when + * the Promise was created. + */ +extern JS_PUBLIC_API bool RejectPromise(JSContext* cx, + JS::HandleObject promiseObj, + JS::HandleValue rejectionValue); + +/** + * Create a Promise with the given fulfill/reject handlers, that will be + * fulfilled/rejected with the value/reason that the promise `promise` is + * fulfilled/rejected with. + * + * This function basically acts like `promise.then(onFulfilled, onRejected)`, + * except that its behavior is unaffected by changes to `Promise`, + * `Promise[Symbol.species]`, `Promise.prototype.then`, `promise.constructor`, + * `promise.then`, and so on. + * + * This function throws if `promise` is not a Promise from this or another + * realm. + * + * This function will assert if `onFulfilled` or `onRejected` is non-null and + * also not IsCallable. + */ +extern JS_PUBLIC_API JSObject* CallOriginalPromiseThen( + JSContext* cx, JS::HandleObject promise, JS::HandleObject onFulfilled, + JS::HandleObject onRejected); + +/** + * Unforgeable, optimized version of the JS builtin Promise.prototype.then. + * + * Takes a Promise instance and nullable `onFulfilled`/`onRejected` callables to + * enqueue as reactions for that promise. In contrast to Promise.prototype.then, + * this doesn't create and return a new Promise instance. + * + * Throws a TypeError if `promise` isn't a Promise (or possibly a different + * error if it's a security wrapper or dead object proxy). + */ +extern JS_PUBLIC_API bool AddPromiseReactions(JSContext* cx, + JS::HandleObject promise, + JS::HandleObject onFulfilled, + JS::HandleObject onRejected); + +/** + * Unforgeable, optimized version of the JS builtin Promise.prototype.then. + * + * Takes a Promise instance and nullable `onFulfilled`/`onRejected` callables to + * enqueue as reactions for that promise. In contrast to Promise.prototype.then, + * this doesn't create and return a new Promise instance. + * + * Throws a TypeError if `promise` isn't a Promise (or possibly a different + * error if it's a security wrapper or dead object proxy). + * + * If `onRejected` is null and `promise` is rejected, this function -- unlike + * the function above -- will not report an unhandled rejection. + */ +extern JS_PUBLIC_API bool AddPromiseReactionsIgnoringUnhandledRejection( + JSContext* cx, JS::HandleObject promise, JS::HandleObject onFulfilled, + JS::HandleObject onRejected); + +// This enum specifies whether a promise is expected to keep track of +// information that is useful for embedders to implement user activation +// behavior handling as specified in the HTML spec: +// https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation +// By default, promises created by SpiderMonkey do not make any attempt to keep +// track of information about whether an activation behavior was being processed +// when the original promise in a promise chain was created. If the embedder +// sets either of the HadUserInteractionAtCreation or +// DidntHaveUserInteractionAtCreation flags on a promise after creating it, +// SpiderMonkey will propagate that flag to newly created promises when +// processing Promise#then and will make it possible to query this flag off of a +// promise further down the chain later using the +// GetPromiseUserInputEventHandlingState() API. +enum class PromiseUserInputEventHandlingState { + // Don't keep track of this state (default for all promises) + DontCare, + // Keep track of this state, the original promise in the chain was created + // while an activation behavior was being processed. + HadUserInteractionAtCreation, + // Keep track of this state, the original promise in the chain was created + // while an activation behavior was not being processed. + DidntHaveUserInteractionAtCreation +}; + +/** + * Returns the given Promise's activation behavior state flag per above as a + * JS::PromiseUserInputEventHandlingState value. All promises are created with + * the DontCare state by default. + * + * Returns JS::PromiseUserInputEventHandlingState::DontCare if the given object + * is a wrapper that can't safely be unwrapped. + */ +extern JS_PUBLIC_API PromiseUserInputEventHandlingState +GetPromiseUserInputEventHandlingState(JS::HandleObject promise); + +/** + * Sets the given Promise's activation behavior state flag per above as a + * JS::PromiseUserInputEventHandlingState value. + * + * Returns false if the given object is a wrapper that can't safely be + * unwrapped. + */ +extern JS_PUBLIC_API bool SetPromiseUserInputEventHandlingState( + JS::HandleObject promise, JS::PromiseUserInputEventHandlingState state); + +/** + * Unforgeable version of the JS builtin Promise.all. + * + * Takes a HandleObjectVector of Promise objects and returns a promise that's + * resolved with an array of resolution values when all those promises have + * been resolved, or rejected with the rejection value of the first rejected + * promise. + * + * Asserts that all objects in the `promises` vector are, maybe wrapped, + * instances of `Promise` or a subclass of `Promise`. + */ +extern JS_PUBLIC_API JSObject* GetWaitForAllPromise( + JSContext* cx, JS::HandleObjectVector promises); + +/** + * The Dispatchable interface allows the embedding to call SpiderMonkey + * on a JSContext thread when requested via DispatchToEventLoopCallback. + */ +class JS_PUBLIC_API Dispatchable { + protected: + // Dispatchables are created and destroyed by SpiderMonkey. + Dispatchable() = default; + virtual ~Dispatchable() = default; + + public: + // ShuttingDown indicates that SpiderMonkey should abort async tasks to + // expedite shutdown. + enum MaybeShuttingDown { NotShuttingDown, ShuttingDown }; + + // Called by the embedding after DispatchToEventLoopCallback succeeds. + virtual void run(JSContext* cx, MaybeShuttingDown maybeShuttingDown) = 0; +}; + +/** + * Callback to dispatch a JS::Dispatchable to a JSContext's thread's event loop. + * + * The DispatchToEventLoopCallback set on a particular JSContext must accept + * JS::Dispatchable instances and arrange for their `run` methods to be called + * eventually on the JSContext's thread. This is used for cross-thread dispatch, + * so the callback itself must be safe to call from any thread. + * + * If the callback returns `true`, it must eventually run the given + * Dispatchable; otherwise, SpiderMonkey may leak memory or hang. + * + * The callback may return `false` to indicate that the JSContext's thread is + * shutting down and is no longer accepting runnables. Shutting down is a + * one-way transition: once the callback has rejected a runnable, it must reject + * all subsequently submitted runnables as well. + * + * To establish a DispatchToEventLoopCallback, the embedding may either call + * InitDispatchToEventLoop to provide its own, or call js::UseInternalJobQueues + * to select a default implementation built into SpiderMonkey. This latter + * depends on the embedding to call js::RunJobs on the JavaScript thread to + * process queued Dispatchables at appropriate times. + */ + +typedef bool (*DispatchToEventLoopCallback)(void* closure, + Dispatchable* dispatchable); + +extern JS_PUBLIC_API void InitDispatchToEventLoop( + JSContext* cx, DispatchToEventLoopCallback callback, void* closure); + +/** + * When a JSRuntime is destroyed it implicitly cancels all async tasks in + * progress, releasing any roots held by the task. However, this is not soon + * enough for cycle collection, which needs to have roots dropped earlier so + * that the cycle collector can transitively remove roots for a future GC. For + * these and other cases, the set of pending async tasks can be canceled + * with this call earlier than JSRuntime destruction. + */ + +extern JS_PUBLIC_API void ShutdownAsyncTasks(JSContext* cx); + +} // namespace JS + +#endif // js_Promise_h diff --git a/js/public/PropertyAndElement.h b/js/public/PropertyAndElement.h new file mode 100644 index 0000000000..f2e9dd9617 --- /dev/null +++ b/js/public/PropertyAndElement.h @@ -0,0 +1,486 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* Property and element API. */ + +#ifndef js_PropertyAndElement_h +#define js_PropertyAndElement_h + +#include <stddef.h> // size_t +#include <stdint.h> // uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/CallArgs.h" // JSNative +#include "js/GCVector.h" // JS::GCVector +#include "js/Id.h" // jsid +#include "js/RootingAPI.h" // JS::Handle, JS::MutableHandle + +struct JSContext; +class JSFunction; +class JSObject; +class JSString; + +namespace JS { + +class ObjectOpResult; +class JS_PUBLIC_API PropertyDescriptor; + +using IdVector = JS::GCVector<jsid>; + +} /* namespace JS */ + +/** + * Define a property on obj. + * + * This function uses JS::ObjectOpResult to indicate conditions that ES6 + * specifies as non-error failures. This is inconvenient at best, so use this + * function only if you are implementing a proxy handler's defineProperty() + * method. For all other purposes, use one of the many DefineProperty functions + * below that throw an exception in all failure cases. + * + * Implements: ES6 [[DefineOwnProperty]] internal method. + */ +extern JS_PUBLIC_API bool JS_DefinePropertyById( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::Handle<JS::PropertyDescriptor> desc, JS::ObjectOpResult& result); + +/** + * Define a property on obj, throwing a TypeError if the attempt fails. + * This is the C++ equivalent of `Object.defineProperty(obj, id, desc)`. + */ +extern JS_PUBLIC_API bool JS_DefinePropertyById( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::Handle<JS::PropertyDescriptor> desc); + +extern JS_PUBLIC_API bool JS_DefinePropertyById(JSContext* cx, + JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, + JS::Handle<JS::Value> value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefinePropertyById( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JSNative getter, JSNative setter, unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefinePropertyById( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::Handle<JSObject*> getter, JS::Handle<JSObject*> setter, unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefinePropertyById(JSContext* cx, + JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, + JS::Handle<JSObject*> value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefinePropertyById(JSContext* cx, + JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, + JS::Handle<JSString*> value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefinePropertyById(JSContext* cx, + JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, + int32_t value, unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefinePropertyById(JSContext* cx, + JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, + uint32_t value, unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefinePropertyById(JSContext* cx, + JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, + double value, unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, + JS::Handle<JS::Value> value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, JSNative getter, + JSNative setter, unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineProperty( + JSContext* cx, JS::Handle<JSObject*> obj, const char* name, + JS::Handle<JSObject*> getter, JS::Handle<JSObject*> setter, unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, + JS::Handle<JSObject*> value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, + JS::Handle<JSString*> value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, int32_t value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, uint32_t value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, double value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineUCProperty( + JSContext* cx, JS::Handle<JSObject*> obj, const char16_t* name, + size_t namelen, JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult& result); + +extern JS_PUBLIC_API bool JS_DefineUCProperty( + JSContext* cx, JS::Handle<JSObject*> obj, const char16_t* name, + size_t namelen, JS::Handle<JS::PropertyDescriptor> desc); + +extern JS_PUBLIC_API bool JS_DefineUCProperty( + JSContext* cx, JS::Handle<JSObject*> obj, const char16_t* name, + size_t namelen, JS::Handle<JS::Value> value, unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineUCProperty( + JSContext* cx, JS::Handle<JSObject*> obj, const char16_t* name, + size_t namelen, JS::Handle<JSObject*> getter, JS::Handle<JSObject*> setter, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineUCProperty( + JSContext* cx, JS::Handle<JSObject*> obj, const char16_t* name, + size_t namelen, JS::Handle<JSObject*> value, unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineUCProperty( + JSContext* cx, JS::Handle<JSObject*> obj, const char16_t* name, + size_t namelen, JS::Handle<JSString*> value, unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineUCProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char16_t* name, + size_t namelen, int32_t value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineUCProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char16_t* name, + size_t namelen, uint32_t value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineUCProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char16_t* name, + size_t namelen, double value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, + JS::Handle<JS::Value> value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineElement( + JSContext* cx, JS::Handle<JSObject*> obj, uint32_t index, + JS::Handle<JSObject*> getter, JS::Handle<JSObject*> setter, unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, + JS::Handle<JSObject*> value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, + JS::Handle<JSString*> value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, int32_t value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, uint32_t value, + unsigned attrs); + +extern JS_PUBLIC_API bool JS_DefineElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, double value, + unsigned attrs); + +/** + * Compute the expression `id in obj`. + * + * If obj has an own or inherited property obj[id], set *foundp = true and + * return true. If not, set *foundp = false and return true. On error, return + * false with an exception pending. + * + * Implements: ES6 [[Has]] internal method. + */ +extern JS_PUBLIC_API bool JS_HasPropertyById(JSContext* cx, + JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, bool* foundp); + +extern JS_PUBLIC_API bool JS_HasProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, bool* foundp); + +extern JS_PUBLIC_API bool JS_HasUCProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char16_t* name, size_t namelen, + bool* vp); + +extern JS_PUBLIC_API bool JS_HasElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, bool* foundp); + +/** + * Determine whether obj has an own property with the key `id`. + * + * Implements: ES6 7.3.11 HasOwnProperty(O, P). + */ +extern JS_PUBLIC_API bool JS_HasOwnPropertyById(JSContext* cx, + JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, + bool* foundp); + +extern JS_PUBLIC_API bool JS_HasOwnProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, bool* foundp); + +/** + * Get the value of the property `obj[id]`, or undefined if no such property + * exists. This is the C++ equivalent of `vp = Reflect.get(obj, id, receiver)`. + * + * Most callers don't need the `receiver` argument. Consider using + * JS_GetProperty instead. (But if you're implementing a proxy handler's set() + * method, it's often correct to call this function and pass the receiver + * through.) + * + * Implements: ES6 [[Get]] internal method. + */ +extern JS_PUBLIC_API bool JS_ForwardGetPropertyTo( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::Handle<JS::Value> receiver, JS::MutableHandleValue vp); + +extern JS_PUBLIC_API bool JS_ForwardGetElementTo(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, + JS::Handle<JSObject*> receiver, + JS::MutableHandleValue vp); + +/** + * Get the value of the property `obj[id]`, or undefined if no such property + * exists. The result is stored in vp. + * + * Implements: ES6 7.3.1 Get(O, P). + */ +extern JS_PUBLIC_API bool JS_GetPropertyById(JSContext* cx, + JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, + JS::MutableHandleValue vp); + +extern JS_PUBLIC_API bool JS_GetProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, + JS::MutableHandleValue vp); + +extern JS_PUBLIC_API bool JS_GetUCProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char16_t* name, size_t namelen, + JS::MutableHandleValue vp); + +extern JS_PUBLIC_API bool JS_GetElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, + JS::MutableHandleValue vp); + +/** + * Perform the same property assignment as `Reflect.set(obj, id, v, receiver)`. + * + * This function has a `receiver` argument that most callers don't need. + * Consider using JS_SetProperty instead. + * + * Implements: ES6 [[Set]] internal method. + */ +extern JS_PUBLIC_API bool JS_ForwardSetPropertyTo( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver, + JS::ObjectOpResult& result); + +/** + * Perform the assignment `obj[id] = v`. + * + * This function performs non-strict assignment, so if the property is + * read-only, nothing happens and no error is thrown. + */ +extern JS_PUBLIC_API bool JS_SetPropertyById(JSContext* cx, + JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, + JS::Handle<JS::Value> v); + +extern JS_PUBLIC_API bool JS_SetProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, + JS::Handle<JS::Value> v); + +extern JS_PUBLIC_API bool JS_SetUCProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char16_t* name, size_t namelen, + JS::Handle<JS::Value> v); + +extern JS_PUBLIC_API bool JS_SetElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, + JS::Handle<JS::Value> v); + +extern JS_PUBLIC_API bool JS_SetElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, + JS::Handle<JSObject*> v); + +extern JS_PUBLIC_API bool JS_SetElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, + JS::Handle<JSString*> v); + +extern JS_PUBLIC_API bool JS_SetElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, int32_t v); + +extern JS_PUBLIC_API bool JS_SetElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, uint32_t v); + +extern JS_PUBLIC_API bool JS_SetElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, double v); + +/** + * Delete a property. This is the C++ equivalent of + * `result = Reflect.deleteProperty(obj, id)`. + * + * This function has a `result` out parameter that most callers don't need. + * Unless you can pass through an ObjectOpResult provided by your caller, it's + * probably best to use the JS_DeletePropertyById signature with just 3 + * arguments. + * + * Implements: ES6 [[Delete]] internal method. + */ +extern JS_PUBLIC_API bool JS_DeletePropertyById(JSContext* cx, + JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, + JS::ObjectOpResult& result); + +extern JS_PUBLIC_API bool JS_DeleteProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, + JS::ObjectOpResult& result); + +extern JS_PUBLIC_API bool JS_DeleteUCProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char16_t* name, + size_t namelen, + JS::ObjectOpResult& result); + +extern JS_PUBLIC_API bool JS_DeleteElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, + JS::ObjectOpResult& result); + +/** + * Delete a property, ignoring strict failures. This is the C++ equivalent of + * the JS `delete obj[id]` in non-strict mode code. + */ +extern JS_PUBLIC_API bool JS_DeletePropertyById(JSContext* cx, + JS::Handle<JSObject*> obj, + jsid id); + +extern JS_PUBLIC_API bool JS_DeleteProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name); + +extern JS_PUBLIC_API bool JS_DeleteElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index); + +/** + * Get an array of the non-symbol enumerable properties of obj. + * This function is roughly equivalent to: + * + * var result = []; + * for (key in obj) { + * result.push(key); + * } + * return result; + * + * This is the closest thing we currently have to the ES6 [[Enumerate]] + * internal method. + * + * The array of ids returned by JS_Enumerate must be rooted to protect its + * contents from garbage collection. Use JS::Rooted<JS::IdVector>. + */ +extern JS_PUBLIC_API bool JS_Enumerate(JSContext* cx, JS::Handle<JSObject*> obj, + JS::MutableHandle<JS::IdVector> props); + +/*** Other property-defining functions **************************************/ + +extern JS_PUBLIC_API JSObject* JS_DefineObject(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, + const JSClass* clasp = nullptr, + unsigned attrs = 0); + +extern JS_PUBLIC_API bool JS_DefineProperties(JSContext* cx, + JS::Handle<JSObject*> obj, + const JSPropertySpec* ps); + +/* * */ + +extern JS_PUBLIC_API bool JS_AlreadyHasOwnPropertyById( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + bool* foundp); + +extern JS_PUBLIC_API bool JS_AlreadyHasOwnProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char* name, + bool* foundp); + +extern JS_PUBLIC_API bool JS_AlreadyHasOwnUCProperty(JSContext* cx, + JS::Handle<JSObject*> obj, + const char16_t* name, + size_t namelen, + bool* foundp); + +extern JS_PUBLIC_API bool JS_AlreadyHasOwnElement(JSContext* cx, + JS::Handle<JSObject*> obj, + uint32_t index, bool* foundp); + +extern JS_PUBLIC_API bool JS_DefineFunctions(JSContext* cx, + JS::Handle<JSObject*> obj, + const JSFunctionSpec* fs); + +extern JS_PUBLIC_API JSFunction* JS_DefineFunction( + JSContext* cx, JS::Handle<JSObject*> obj, const char* name, JSNative call, + unsigned nargs, unsigned attrs); + +extern JS_PUBLIC_API JSFunction* JS_DefineUCFunction( + JSContext* cx, JS::Handle<JSObject*> obj, const char16_t* name, + size_t namelen, JSNative call, unsigned nargs, unsigned attrs); + +extern JS_PUBLIC_API JSFunction* JS_DefineFunctionById( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JSNative call, unsigned nargs, unsigned attrs); + +#endif /* js_PropertyAndElement_h */ diff --git a/js/public/PropertyDescriptor.h b/js/public/PropertyDescriptor.h new file mode 100644 index 0000000000..4b56e51616 --- /dev/null +++ b/js/public/PropertyDescriptor.h @@ -0,0 +1,498 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* Property descriptors and flags. */ + +#ifndef js_PropertyDescriptor_h +#define js_PropertyDescriptor_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_ASSERT_IF +#include "mozilla/EnumSet.h" // mozilla::EnumSet +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include <stddef.h> // size_t +#include <stdint.h> // uint8_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Id.h" // jsid +#include "js/RootingAPI.h" // JS::Handle, js::{,Mutable}WrappedPtrOperations +#include "js/Value.h" // JS::Value + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; +class JS_PUBLIC_API JSTracer; + +/* Property attributes, set in JSPropertySpec and passed to API functions. + * + * The data structure in which some of these values are stored only uses a + * uint8_t to store the relevant information. Proceed with caution if trying to + * reorder or change the the first byte worth of flags. + */ + +/** The property is visible in for/in loops. */ +static constexpr uint8_t JSPROP_ENUMERATE = 0x01; + +/** + * The property is non-writable. This flag is only valid for data properties. + */ +static constexpr uint8_t JSPROP_READONLY = 0x02; + +/** + * The property is non-configurable: it can't be deleted, and if it's an + * accessor descriptor, its getter and setter can't be changed. + */ +static constexpr uint8_t JSPROP_PERMANENT = 0x04; + +/** + * Resolve hooks and enumerate hooks must pass this flag when calling + * JS_Define* APIs to reify lazily-defined properties. + * + * JSPROP_RESOLVING is used only with property-defining APIs. It tells the + * engine to skip the resolve hook when performing the lookup at the beginning + * of property definition. This keeps the resolve hook from accidentally + * triggering itself: unchecked recursion. + * + * For enumerate hooks, triggering the resolve hook would be merely silly, not + * fatal, except in some cases involving non-configurable properties. + */ +static constexpr unsigned JSPROP_RESOLVING = 0x08; + +/* (higher flags are unused; add to JSPROP_FLAGS_MASK if ever defined) */ + +static constexpr unsigned JSPROP_FLAGS_MASK = + JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_RESOLVING; + +namespace JS { + +// 6.1.7.1 Property Attributes +enum class PropertyAttribute : uint8_t { + // The descriptor is [[Configurable]] := true. + Configurable, + + // The descriptor is [[Enumerable]] := true. + Enumerable, + + // The descriptor is [[Writable]] := true. Only valid for data descriptors. + Writable +}; + +class PropertyAttributes : public mozilla::EnumSet<PropertyAttribute> { + // Re-use all EnumSet constructors. + using mozilla::EnumSet<PropertyAttribute>::EnumSet; + + public: + bool configurable() const { + return contains(PropertyAttribute::Configurable); + } + bool enumerable() const { return contains(PropertyAttribute::Enumerable); } + bool writable() const { return contains(PropertyAttribute::Writable); } +}; + +/** + * A structure that represents a property on an object, or the absence of a + * property. Use {,Mutable}Handle<PropertyDescriptor> to interact with + * instances of this structure rather than interacting directly with member + * fields. + */ +class JS_PUBLIC_API PropertyDescriptor { + private: + bool hasConfigurable_ : 1; + bool configurable_ : 1; + + bool hasEnumerable_ : 1; + bool enumerable_ : 1; + + bool hasWritable_ : 1; + bool writable_ : 1; + + bool hasValue_ : 1; + bool hasGetter_ : 1; + bool hasSetter_ : 1; + + bool resolving_ : 1; + + JSObject* getter_; + JSObject* setter_; + Value value_; + + public: + PropertyDescriptor() + : hasConfigurable_(false), + configurable_(false), + hasEnumerable_(false), + enumerable_(false), + hasWritable_(false), + writable_(false), + hasValue_(false), + hasGetter_(false), + hasSetter_(false), + resolving_(false), + getter_(nullptr), + setter_(nullptr), + value_(UndefinedValue()) {} + + void trace(JSTracer* trc); + + // Construct a new complete DataDescriptor. + static PropertyDescriptor Data(const Value& value, + PropertyAttributes attributes = {}) { + PropertyDescriptor desc; + desc.setConfigurable(attributes.configurable()); + desc.setEnumerable(attributes.enumerable()); + desc.setWritable(attributes.writable()); + desc.setValue(value); + desc.assertComplete(); + return desc; + } + + // This constructor is only provided for legacy code! + static PropertyDescriptor Data(const Value& value, unsigned attrs) { + MOZ_ASSERT((attrs & ~(JSPROP_PERMANENT | JSPROP_ENUMERATE | + JSPROP_READONLY | JSPROP_RESOLVING)) == 0); + + PropertyDescriptor desc; + desc.setConfigurable(!(attrs & JSPROP_PERMANENT)); + desc.setEnumerable(attrs & JSPROP_ENUMERATE); + desc.setWritable(!(attrs & JSPROP_READONLY)); + desc.setValue(value); + desc.setResolving(attrs & JSPROP_RESOLVING); + desc.assertComplete(); + return desc; + } + + // Construct a new complete AccessorDescriptor. + // Note: This means JSPROP_GETTER and JSPROP_SETTER are always set. + static PropertyDescriptor Accessor(JSObject* getter, JSObject* setter, + PropertyAttributes attributes = {}) { + MOZ_ASSERT(!attributes.writable()); + + PropertyDescriptor desc; + desc.setConfigurable(attributes.configurable()); + desc.setEnumerable(attributes.enumerable()); + desc.setGetter(getter); + desc.setSetter(setter); + desc.assertComplete(); + return desc; + } + + // This constructor is only provided for legacy code! + static PropertyDescriptor Accessor(JSObject* getter, JSObject* setter, + unsigned attrs) { + MOZ_ASSERT((attrs & ~(JSPROP_PERMANENT | JSPROP_ENUMERATE | + JSPROP_RESOLVING)) == 0); + + PropertyDescriptor desc; + desc.setConfigurable(!(attrs & JSPROP_PERMANENT)); + desc.setEnumerable(attrs & JSPROP_ENUMERATE); + desc.setGetter(getter); + desc.setSetter(setter); + desc.setResolving(attrs & JSPROP_RESOLVING); + desc.assertComplete(); + return desc; + } + + static PropertyDescriptor Accessor(mozilla::Maybe<JSObject*> getter, + mozilla::Maybe<JSObject*> setter, + unsigned attrs) { + MOZ_ASSERT((attrs & ~(JSPROP_PERMANENT | JSPROP_ENUMERATE | + JSPROP_RESOLVING)) == 0); + + PropertyDescriptor desc; + desc.setConfigurable(!(attrs & JSPROP_PERMANENT)); + desc.setEnumerable(attrs & JSPROP_ENUMERATE); + if (getter) { + desc.setGetter(*getter); + } + if (setter) { + desc.setSetter(*setter); + } + desc.setResolving(attrs & JSPROP_RESOLVING); + desc.assertValid(); + return desc; + } + + // Construct a new incomplete empty PropertyDescriptor. + // Using the spec syntax this would be { }. Specific fields like [[Value]] + // can be added with e.g., setValue. + static PropertyDescriptor Empty() { + PropertyDescriptor desc; + desc.assertValid(); + MOZ_ASSERT(!desc.hasConfigurable() && !desc.hasEnumerable() && + !desc.hasWritable() && !desc.hasValue() && !desc.hasGetter() && + !desc.hasSetter()); + return desc; + } + + public: + bool isAccessorDescriptor() const { + MOZ_ASSERT_IF(hasGetter_ || hasSetter_, !isDataDescriptor()); + return hasGetter_ || hasSetter_; + } + bool isGenericDescriptor() const { + return !isAccessorDescriptor() && !isDataDescriptor(); + } + bool isDataDescriptor() const { + MOZ_ASSERT_IF(hasWritable_ || hasValue_, !isAccessorDescriptor()); + return hasWritable_ || hasValue_; + } + + bool hasConfigurable() const { return hasConfigurable_; } + bool configurable() const { + MOZ_ASSERT(hasConfigurable()); + return configurable_; + } + void setConfigurable(bool configurable) { + hasConfigurable_ = true; + configurable_ = configurable; + } + + bool hasEnumerable() const { return hasEnumerable_; } + bool enumerable() const { + MOZ_ASSERT(hasEnumerable()); + return enumerable_; + } + void setEnumerable(bool enumerable) { + hasEnumerable_ = true; + enumerable_ = enumerable; + } + + bool hasValue() const { return hasValue_; } + Value value() const { + MOZ_ASSERT(hasValue()); + return value_; + } + void setValue(const Value& v) { + MOZ_ASSERT(!isAccessorDescriptor()); + hasValue_ = true; + value_ = v; + } + + bool hasWritable() const { return hasWritable_; } + bool writable() const { + MOZ_ASSERT(hasWritable()); + return writable_; + } + void setWritable(bool writable) { + MOZ_ASSERT(!isAccessorDescriptor()); + hasWritable_ = true; + writable_ = writable; + } + + bool hasGetter() const { return hasGetter_; } + JSObject* getter() const { + MOZ_ASSERT(hasGetter()); + return getter_; + } + void setGetter(JSObject* obj) { + MOZ_ASSERT(!isDataDescriptor()); + hasGetter_ = true; + getter_ = obj; + } + + bool hasSetter() const { return hasSetter_; } + JSObject* setter() const { + MOZ_ASSERT(hasSetter()); + return setter_; + } + void setSetter(JSObject* obj) { + MOZ_ASSERT(!isDataDescriptor()); + hasSetter_ = true; + setter_ = obj; + } + + // Non-standard flag, which is set when defining properties in a resolve hook. + bool resolving() const { return resolving_; } + void setResolving(bool resolving) { resolving_ = resolving; } + + Value* valueDoNotUse() { return &value_; } + Value const* valueDoNotUse() const { return &value_; } + JSObject** getterDoNotUse() { return &getter_; } + JSObject* const* getterDoNotUse() const { return &getter_; } + void setGetterDoNotUse(JSObject* obj) { getter_ = obj; } + JSObject** setterDoNotUse() { return &setter_; } + JSObject* const* setterDoNotUse() const { return &setter_; } + void setSetterDoNotUse(JSObject* obj) { setter_ = obj; } + + void assertValid() const { +#ifdef DEBUG + if (isAccessorDescriptor()) { + MOZ_ASSERT(!hasWritable_); + MOZ_ASSERT(!hasValue_); + } else { + MOZ_ASSERT(isGenericDescriptor() || isDataDescriptor()); + MOZ_ASSERT(!hasGetter_); + MOZ_ASSERT(!hasSetter_); + } + + MOZ_ASSERT_IF(!hasConfigurable_, !configurable_); + MOZ_ASSERT_IF(!hasEnumerable_, !enumerable_); + MOZ_ASSERT_IF(!hasWritable_, !writable_); + MOZ_ASSERT_IF(!hasValue_, value_.isUndefined()); + MOZ_ASSERT_IF(!hasGetter_, !getter_); + MOZ_ASSERT_IF(!hasSetter_, !setter_); + + MOZ_ASSERT_IF(resolving_, !isGenericDescriptor()); +#endif + } + + void assertComplete() const { +#ifdef DEBUG + assertValid(); + MOZ_ASSERT(hasConfigurable()); + MOZ_ASSERT(hasEnumerable()); + MOZ_ASSERT(!isGenericDescriptor()); + MOZ_ASSERT_IF(isDataDescriptor(), hasValue() && hasWritable()); + MOZ_ASSERT_IF(isAccessorDescriptor(), hasGetter() && hasSetter()); +#endif + } +}; + +} // namespace JS + +namespace js { + +template <typename Wrapper> +class WrappedPtrOperations<JS::PropertyDescriptor, Wrapper> { + const JS::PropertyDescriptor& desc() const { + return static_cast<const Wrapper*>(this)->get(); + } + + public: + bool isAccessorDescriptor() const { return desc().isAccessorDescriptor(); } + bool isGenericDescriptor() const { return desc().isGenericDescriptor(); } + bool isDataDescriptor() const { return desc().isDataDescriptor(); } + + bool hasConfigurable() const { return desc().hasConfigurable(); } + bool configurable() const { return desc().configurable(); } + + bool hasEnumerable() const { return desc().hasEnumerable(); } + bool enumerable() const { return desc().enumerable(); } + + bool hasValue() const { return desc().hasValue(); } + JS::Handle<JS::Value> value() const { + MOZ_ASSERT(hasValue()); + return JS::Handle<JS::Value>::fromMarkedLocation(desc().valueDoNotUse()); + } + + bool hasWritable() const { return desc().hasWritable(); } + bool writable() const { return desc().writable(); } + + bool hasGetter() const { return desc().hasGetter(); } + JS::Handle<JSObject*> getter() const { + MOZ_ASSERT(hasGetter()); + return JS::Handle<JSObject*>::fromMarkedLocation(desc().getterDoNotUse()); + } + bool hasSetter() const { return desc().hasSetter(); } + JS::Handle<JSObject*> setter() const { + MOZ_ASSERT(hasSetter()); + return JS::Handle<JSObject*>::fromMarkedLocation(desc().setterDoNotUse()); + } + + bool resolving() const { return desc().resolving(); } + + void assertValid() const { desc().assertValid(); } + void assertComplete() const { desc().assertComplete(); } +}; + +template <typename Wrapper> +class MutableWrappedPtrOperations<JS::PropertyDescriptor, Wrapper> + : public js::WrappedPtrOperations<JS::PropertyDescriptor, Wrapper> { + JS::PropertyDescriptor& desc() { return static_cast<Wrapper*>(this)->get(); } + + public: + JS::MutableHandle<JS::Value> value() { + MOZ_ASSERT(desc().hasValue()); + return JS::MutableHandle<JS::Value>::fromMarkedLocation( + desc().valueDoNotUse()); + } + void setValue(JS::Handle<JS::Value> v) { desc().setValue(v); } + + void setConfigurable(bool configurable) { + desc().setConfigurable(configurable); + } + void setEnumerable(bool enumerable) { desc().setEnumerable(enumerable); } + void setWritable(bool writable) { desc().setWritable(writable); } + + void setGetter(JSObject* obj) { desc().setGetter(obj); } + void setSetter(JSObject* obj) { desc().setSetter(obj); } + + JS::MutableHandle<JSObject*> getter() { + MOZ_ASSERT(desc().hasGetter()); + return JS::MutableHandle<JSObject*>::fromMarkedLocation( + desc().getterDoNotUse()); + } + JS::MutableHandle<JSObject*> setter() { + MOZ_ASSERT(desc().hasSetter()); + return JS::MutableHandle<JSObject*>::fromMarkedLocation( + desc().setterDoNotUse()); + } + + void setResolving(bool resolving) { desc().setResolving(resolving); } +}; + +} // namespace js + +/** + * Get a description of one of obj's own properties. If no such property exists + * on obj, return true with desc.object() set to null. + * + * Implements: ES6 [[GetOwnProperty]] internal method. + */ +extern JS_PUBLIC_API bool JS_GetOwnPropertyDescriptorById( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); + +extern JS_PUBLIC_API bool JS_GetOwnPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> obj, const char* name, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); + +extern JS_PUBLIC_API bool JS_GetOwnUCPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> obj, const char16_t* name, + size_t namelen, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); + +/** + * DEPRECATED + * + * Like JS_GetOwnPropertyDescriptorById, but also searches the prototype chain + * if no own property is found directly on obj. The object on which the + * property is found is returned in holder. If the property is not found + * on the prototype chain, then desc is Nothing. + */ +extern JS_PUBLIC_API bool JS_GetPropertyDescriptorById( + JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc, + JS::MutableHandle<JSObject*> holder); + +extern JS_PUBLIC_API bool JS_GetPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> obj, const char* name, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc, + JS::MutableHandle<JSObject*> holder); + +extern JS_PUBLIC_API bool JS_GetUCPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> obj, const char16_t* name, + size_t namelen, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc, + JS::MutableHandle<JSObject*> holder); + +namespace JS { + +extern JS_PUBLIC_API bool ObjectToCompletePropertyDescriptor( + JSContext* cx, Handle<JSObject*> obj, Handle<Value> descriptor, + MutableHandle<PropertyDescriptor> desc); + +/* + * ES6 draft rev 32 (2015 Feb 2) 6.2.4.4 FromPropertyDescriptor(Desc). + * + * If desc.isNothing(), then vp is set to undefined. + */ +extern JS_PUBLIC_API bool FromPropertyDescriptor( + JSContext* cx, Handle<mozilla::Maybe<PropertyDescriptor>> desc, + MutableHandle<Value> vp); + +} // namespace JS + +#endif /* js_PropertyDescriptor_h */ diff --git a/js/public/PropertySpec.h b/js/public/PropertySpec.h new file mode 100644 index 0000000000..9d8115508d --- /dev/null +++ b/js/public/PropertySpec.h @@ -0,0 +1,447 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* Property descriptors and flags. */ + +#ifndef js_PropertySpec_h +#define js_PropertySpec_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF} + +#include <stddef.h> // size_t +#include <stdint.h> // uint8_t, uint16_t, int32_t, uint32_t, uintptr_t +#include <type_traits> // std::enable_if + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/CallArgs.h" // JSNative +#include "js/PropertyDescriptor.h" // JSPROP_* +#include "js/RootingAPI.h" // JS::MutableHandle +#include "js/Symbol.h" // JS::SymbolCode, PropertySpecNameIsSymbol +#include "js/Value.h" // JS::Value + +struct JS_PUBLIC_API JSContext; +class JSJitInfo; + +/** + * Wrapper to relace JSNative for JSPropertySpecs and JSFunctionSpecs. This will + * allow us to pass one JSJitInfo per function with the property/function spec, + * without additional field overhead. + */ +struct JSNativeWrapper { + JSNative op = nullptr; + const JSJitInfo* info = nullptr; + + JSNativeWrapper() = default; + + JSNativeWrapper(const JSNativeWrapper& other) = default; + + constexpr JSNativeWrapper(JSNative op, const JSJitInfo* info) + : op(op), info(info) {} +}; + +/** + * Description of a property. JS_DefineProperties and JS_InitClass take arrays + * of these and define many properties at once. JS_PSG, JS_PSGS and JS_PS_END + * are helper macros for defining such arrays. + */ +struct JSPropertySpec { + struct SelfHostedWrapper { + // The same type as JSNativeWrapper's first field, so that the access in + // JSPropertySpec::checkAccessorsAreSelfHosted become valid. + JSNative unused = nullptr; + + const char* funname; + + SelfHostedWrapper() = delete; + + explicit constexpr SelfHostedWrapper(const char* funname) + : funname(funname) {} + }; + + struct ValueWrapper { + enum class Type : uint8_t { String, Int32, Double }; + Type type; + union { + const char* string; + int32_t int32; + double double_; + }; + + private: + ValueWrapper() = delete; + + explicit constexpr ValueWrapper(int32_t n) : type(Type::Int32), int32(n) {} + + explicit constexpr ValueWrapper(const char* s) + : type(Type::String), string(s) {} + + explicit constexpr ValueWrapper(double d) + : type(Type::Double), double_(d) {} + + public: + ValueWrapper(const ValueWrapper& other) = default; + + static constexpr ValueWrapper int32Value(int32_t n) { + return ValueWrapper(n); + } + + static constexpr ValueWrapper stringValue(const char* s) { + return ValueWrapper(s); + } + + static constexpr ValueWrapper doubleValue(double d) { + return ValueWrapper(d); + } + }; + + union Accessor { + JSNativeWrapper native; + SelfHostedWrapper selfHosted; + + private: + Accessor() = delete; + + constexpr Accessor(JSNative op, const JSJitInfo* info) : native(op, info) {} + + explicit constexpr Accessor(const char* funname) : selfHosted(funname) {} + + public: + Accessor(const Accessor& other) = default; + + static constexpr Accessor nativeAccessor(JSNative op, + const JSJitInfo* info = nullptr) { + return Accessor(op, info); + } + + static constexpr Accessor selfHostedAccessor(const char* funname) { + return Accessor(funname); + } + + static constexpr Accessor noAccessor() { + return Accessor(nullptr, nullptr); + } + }; + + union AccessorsOrValue { + struct Accessors { + Accessor getter; + Accessor setter; + + constexpr Accessors(Accessor getter, Accessor setter) + : getter(getter), setter(setter) {} + } accessors; + ValueWrapper value; + + private: + AccessorsOrValue() = delete; + + constexpr AccessorsOrValue(Accessor getter, Accessor setter) + : accessors(getter, setter) {} + + explicit constexpr AccessorsOrValue(ValueWrapper value) : value(value) {} + + public: + AccessorsOrValue(const AccessorsOrValue& other) = default; + + static constexpr AccessorsOrValue fromAccessors(Accessor getter, + Accessor setter) { + return AccessorsOrValue(getter, setter); + } + + static constexpr AccessorsOrValue fromValue(ValueWrapper value) { + return AccessorsOrValue(value); + } + }; + + union Name { + private: + const char* string_; + uintptr_t symbol_; + + public: + Name() = delete; + + explicit constexpr Name(const char* str) : string_(str) {} + explicit constexpr Name(JS::SymbolCode symbol) + : symbol_(uint32_t(symbol) + 1) {} + + explicit operator bool() const { return !!symbol_; } + + bool isSymbol() const { return JS::PropertySpecNameIsSymbol(symbol_); } + JS::SymbolCode symbol() const { + MOZ_ASSERT(isSymbol()); + return JS::SymbolCode(symbol_ - 1); + } + + bool isString() const { return !isSymbol(); } + const char* string() const { + MOZ_ASSERT(isString()); + return string_; + } + }; + + Name name; + + private: + // JSPROP_* property attributes as defined in PropertyDescriptor.h. + uint8_t attributes_; + + // Whether AccessorsOrValue below stores a value, JSNative accessors, or + // self-hosted accessors. + enum class Kind : uint8_t { Value, SelfHostedAccessor, NativeAccessor }; + Kind kind_; + + public: + AccessorsOrValue u; + + private: + JSPropertySpec() = delete; + + constexpr JSPropertySpec(const char* name, uint8_t attributes, Kind kind, + AccessorsOrValue u) + : name(name), attributes_(attributes), kind_(kind), u(u) {} + constexpr JSPropertySpec(JS::SymbolCode name, uint8_t attributes, Kind kind, + AccessorsOrValue u) + : name(name), attributes_(attributes), kind_(kind), u(u) {} + + public: + JSPropertySpec(const JSPropertySpec& other) = default; + + static constexpr JSPropertySpec nativeAccessors( + const char* name, uint8_t attributes, JSNative getter, + const JSJitInfo* getterInfo, JSNative setter = nullptr, + const JSJitInfo* setterInfo = nullptr) { + return JSPropertySpec( + name, attributes, Kind::NativeAccessor, + AccessorsOrValue::fromAccessors( + JSPropertySpec::Accessor::nativeAccessor(getter, getterInfo), + JSPropertySpec::Accessor::nativeAccessor(setter, setterInfo))); + } + + static constexpr JSPropertySpec nativeAccessors( + JS::SymbolCode name, uint8_t attributes, JSNative getter, + const JSJitInfo* getterInfo, JSNative setter = nullptr, + const JSJitInfo* setterInfo = nullptr) { + return JSPropertySpec( + name, attributes, Kind::NativeAccessor, + AccessorsOrValue::fromAccessors( + JSPropertySpec::Accessor::nativeAccessor(getter, getterInfo), + JSPropertySpec::Accessor::nativeAccessor(setter, setterInfo))); + } + + static constexpr JSPropertySpec selfHostedAccessors( + const char* name, uint8_t attributes, const char* getterName, + const char* setterName = nullptr) { + return JSPropertySpec( + name, attributes, Kind::SelfHostedAccessor, + AccessorsOrValue::fromAccessors( + JSPropertySpec::Accessor::selfHostedAccessor(getterName), + setterName + ? JSPropertySpec::Accessor::selfHostedAccessor(setterName) + : JSPropertySpec::Accessor::noAccessor())); + } + + static constexpr JSPropertySpec selfHostedAccessors( + JS::SymbolCode name, uint8_t attributes, const char* getterName, + const char* setterName = nullptr) { + return JSPropertySpec( + name, attributes, Kind::SelfHostedAccessor, + AccessorsOrValue::fromAccessors( + JSPropertySpec::Accessor::selfHostedAccessor(getterName), + setterName + ? JSPropertySpec::Accessor::selfHostedAccessor(setterName) + : JSPropertySpec::Accessor::noAccessor())); + } + + static constexpr JSPropertySpec int32Value(const char* name, + uint8_t attributes, int32_t n) { + return JSPropertySpec(name, attributes, Kind::Value, + AccessorsOrValue::fromValue( + JSPropertySpec::ValueWrapper::int32Value(n))); + } + + static constexpr JSPropertySpec int32Value(JS::SymbolCode name, + uint8_t attributes, int32_t n) { + return JSPropertySpec(name, attributes, Kind::Value, + AccessorsOrValue::fromValue( + JSPropertySpec::ValueWrapper::int32Value(n))); + } + + static constexpr JSPropertySpec stringValue(const char* name, + uint8_t attributes, + const char* s) { + return JSPropertySpec(name, attributes, Kind::Value, + AccessorsOrValue::fromValue( + JSPropertySpec::ValueWrapper::stringValue(s))); + } + + static constexpr JSPropertySpec stringValue(JS::SymbolCode name, + uint8_t attributes, + const char* s) { + return JSPropertySpec(name, attributes, Kind::Value, + AccessorsOrValue::fromValue( + JSPropertySpec::ValueWrapper::stringValue(s))); + } + + static constexpr JSPropertySpec doubleValue(const char* name, + uint8_t attributes, double d) { + return JSPropertySpec(name, attributes, Kind::Value, + AccessorsOrValue::fromValue( + JSPropertySpec::ValueWrapper::doubleValue(d))); + } + + static constexpr JSPropertySpec sentinel() { + return JSPropertySpec(nullptr, 0, Kind::NativeAccessor, + AccessorsOrValue::fromAccessors( + JSPropertySpec::Accessor::noAccessor(), + JSPropertySpec::Accessor::noAccessor())); + } + + unsigned attributes() const { return attributes_; } + + bool isAccessor() const { + return (kind_ == Kind::NativeAccessor || kind_ == Kind::SelfHostedAccessor); + } + + JS_PUBLIC_API bool getValue(JSContext* cx, + JS::MutableHandle<JS::Value> value) const; + + bool isSelfHosted() const { + MOZ_ASSERT(isAccessor()); +#ifdef DEBUG + // Verify that our accessors match our Kind. + if (kind_ == Kind::SelfHostedAccessor) { + checkAccessorsAreSelfHosted(); + } else { + checkAccessorsAreNative(); + } +#endif + return kind_ == Kind::SelfHostedAccessor; + } + + static_assert(sizeof(SelfHostedWrapper) == sizeof(JSNativeWrapper), + "JSPropertySpec::getter/setter must be compact"); + static_assert(offsetof(SelfHostedWrapper, unused) == + offsetof(JSNativeWrapper, op) && + offsetof(SelfHostedWrapper, funname) == + offsetof(JSNativeWrapper, info), + "checkAccessorsAreNative below require that " + "SelfHostedWrapper::funname overlay " + "JSNativeWrapper::info and " + "SelfHostedWrapper::unused overlay " + "JSNativeWrapper::op"); + + private: + void checkAccessorsAreNative() const { + // We may have a getter or a setter or both. And whichever ones we have + // should not have a SelfHostedWrapper for the accessor. + MOZ_ASSERT_IF(u.accessors.getter.native.info, u.accessors.getter.native.op); + MOZ_ASSERT_IF(u.accessors.setter.native.info, u.accessors.setter.native.op); + } + + void checkAccessorsAreSelfHosted() const { + MOZ_ASSERT(!u.accessors.getter.selfHosted.unused); + MOZ_ASSERT(!u.accessors.setter.selfHosted.unused); + } +}; + +// There can be many JSPropertySpec instances so verify the size is what we +// expect: +// +// - Name (1 word) +// - attributes_ + isAccessor_ (1 word) +// - AccessorsOrValue (4 words, native + JSJitInfo for both getter and setter) +static_assert(sizeof(JSPropertySpec) == 6 * sizeof(uintptr_t)); + +template <unsigned Attributes> +constexpr uint8_t CheckAccessorAttrs() { + static_assert((Attributes & ~(JSPROP_ENUMERATE | JSPROP_PERMANENT)) == 0, + "Unexpected flag (not JSPROP_ENUMERATE or JSPROP_PERMANENT)"); + return uint8_t(Attributes); +} + +#define JS_PSG(name, getter, attributes) \ + JSPropertySpec::nativeAccessors(name, CheckAccessorAttrs<attributes>(), \ + getter, nullptr) +#define JS_PSGS(name, getter, setter, attributes) \ + JSPropertySpec::nativeAccessors(name, CheckAccessorAttrs<attributes>(), \ + getter, nullptr, setter, nullptr) +#define JS_SYM_GET(symbol, getter, attributes) \ + JSPropertySpec::nativeAccessors(::JS::SymbolCode::symbol, \ + CheckAccessorAttrs<attributes>(), getter, \ + nullptr) +#define JS_SELF_HOSTED_GET(name, getterName, attributes) \ + JSPropertySpec::selfHostedAccessors(name, CheckAccessorAttrs<attributes>(), \ + getterName) +#define JS_SELF_HOSTED_GETSET(name, getterName, setterName, attributes) \ + JSPropertySpec::selfHostedAccessors(name, CheckAccessorAttrs<attributes>(), \ + getterName, setterName) +#define JS_SELF_HOSTED_SYM_GET(symbol, getterName, attributes) \ + JSPropertySpec::selfHostedAccessors( \ + ::JS::SymbolCode::symbol, CheckAccessorAttrs<attributes>(), getterName) +#define JS_STRING_PS(name, string, attributes) \ + JSPropertySpec::stringValue(name, attributes, string) +#define JS_STRING_SYM_PS(symbol, string, attributes) \ + JSPropertySpec::stringValue(::JS::SymbolCode::symbol, attributes, string) +#define JS_INT32_PS(name, value, attributes) \ + JSPropertySpec::int32Value(name, attributes, value) +#define JS_DOUBLE_PS(name, value, attributes) \ + JSPropertySpec::doubleValue(name, attributes, value) +#define JS_PS_END JSPropertySpec::sentinel() + +/** + * To define a native function, set call to a JSNativeWrapper. To define a + * self-hosted function, set selfHostedName to the name of a function + * compiled during JSRuntime::initSelfHosting. + */ +struct JSFunctionSpec { + using Name = JSPropertySpec::Name; + + Name name; + JSNativeWrapper call; + uint16_t nargs; + uint16_t flags; + const char* selfHostedName; + + // JSPROP_* property attributes as defined in PropertyDescriptor.h + unsigned attributes() const { return flags; } +}; + +/* + * Terminating sentinel initializer to put at the end of a JSFunctionSpec array + * that's passed to JS_DefineFunctions or JS_InitClass. + */ +#define JS_FS_END JS_FN(nullptr, nullptr, 0, 0) + +/* + * Initializer macros for a JSFunctionSpec array element. JS_FNINFO allows the + * simple adding of JSJitInfos. JS_SELF_HOSTED_FN declares a self-hosted + * function. JS_INLINABLE_FN allows specifying an InlinableNative enum value for + * natives inlined or specialized by the JIT. Finally JS_FNSPEC has slots for + * all the fields. + * + * The _SYM variants allow defining a function with a symbol key rather than a + * string key. For example, use JS_SYM_FN(iterator, ...) to define an + * @@iterator method. + */ +#define JS_FN(name, call, nargs, flags) \ + JS_FNSPEC(name, call, nullptr, nargs, flags, nullptr) +#define JS_INLINABLE_FN(name, call, nargs, flags, native) \ + JS_FNSPEC(name, call, &js::jit::JitInfo_##native, nargs, flags, nullptr) +#define JS_SYM_FN(symbol, call, nargs, flags) \ + JS_SYM_FNSPEC(symbol, call, nullptr, nargs, flags, nullptr) +#define JS_FNINFO(name, call, info, nargs, flags) \ + JS_FNSPEC(name, call, info, nargs, flags, nullptr) +#define JS_SELF_HOSTED_FN(name, selfHostedName, nargs, flags) \ + JS_FNSPEC(name, nullptr, nullptr, nargs, flags, selfHostedName) +#define JS_SELF_HOSTED_SYM_FN(symbol, selfHostedName, nargs, flags) \ + JS_SYM_FNSPEC(symbol, nullptr, nullptr, nargs, flags, selfHostedName) +#define JS_SYM_FNSPEC(symbol, call, info, nargs, flags, selfHostedName) \ + JS_FNSPEC(::JS::SymbolCode::symbol, call, info, nargs, flags, selfHostedName) +#define JS_FNSPEC(name, call, info, nargs, flags, selfHostedName) \ + { JSFunctionSpec::Name(name), {call, info}, nargs, flags, selfHostedName } + +#endif // js_PropertySpec_h diff --git a/js/public/ProtoKey.h b/js/public/ProtoKey.h new file mode 100644 index 0000000000..398e1f03f1 --- /dev/null +++ b/js/public/ProtoKey.h @@ -0,0 +1,136 @@ +/* -*- 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 js_ProtoKey_h +#define js_ProtoKey_h + +/* A higher-order macro for enumerating all JSProtoKey values. */ +/* + * Consumers define macros as follows: + * MACRO(name, clasp) + * name: The canonical name of the class. + * clasp: The JSClass for this object, or "dummy" if it doesn't exist. + * + * + * Consumers wishing to iterate over all the JSProtoKey values, can use + * JS_FOR_EACH_PROTOTYPE. However, there are certain values that don't + * correspond to real constructors, like Null or constructors that are disabled + * via preprocessor directives. We still need to include these in the JSProtoKey + * list in order to maintain binary XDR compatibility, but we need to provide a + * tool to handle them differently. JS_FOR_PROTOTYPES fills this niche. + * + * Consumers pass two macros to JS_FOR_PROTOTYPES - |REAL| and |IMAGINARY|. The + * former is invoked for entries that have real client-exposed constructors, and + * the latter is called for the rest. Consumers that don't care about this + * distinction can simply pass the same macro to both, which is exactly what + * JS_FOR_EACH_PROTOTYPE does. + */ + +#define CLASP(NAME) (&NAME##Class) +#define OCLASP(NAME) (&NAME##Object::class_) +#define TYPED_ARRAY_CLASP(TYPE) (&TypedArrayObject::classes[JS::Scalar::TYPE]) +#define ERROR_CLASP(TYPE) (&ErrorObject::classes[TYPE]) + +#ifdef JS_HAS_INTL_API +# define IF_INTL(REAL, IMAGINARY) REAL +#else +# define IF_INTL(REAL, IMAGINARY) IMAGINARY +#endif + +#ifdef ENABLE_WASM_TYPE_REFLECTIONS +# define IF_WASM_TYPE(REAL, IMAGINARY) REAL +#else +# define IF_WASM_TYPE(REAL, IMAGINARY) IMAGINARY +#endif + +#define JS_FOR_PROTOTYPES_(REAL, IMAGINARY, REAL_IF_INTL, REAL_IF_WASM_TYPE) \ + IMAGINARY(Null, dummy) \ + REAL(Object, OCLASP(Plain)) \ + REAL(Function, &FunctionClass) \ + IMAGINARY(BoundFunction, OCLASP(BoundFunction)) \ + REAL(Array, OCLASP(Array)) \ + REAL(Boolean, OCLASP(Boolean)) \ + REAL(JSON, CLASP(JSON)) \ + REAL(Date, OCLASP(Date)) \ + REAL(Math, CLASP(Math)) \ + REAL(Number, OCLASP(Number)) \ + REAL(String, OCLASP(String)) \ + REAL(RegExp, OCLASP(RegExp)) \ + REAL(Error, ERROR_CLASP(JSEXN_ERR)) \ + REAL(InternalError, ERROR_CLASP(JSEXN_INTERNALERR)) \ + REAL(AggregateError, ERROR_CLASP(JSEXN_AGGREGATEERR)) \ + REAL(EvalError, ERROR_CLASP(JSEXN_EVALERR)) \ + REAL(RangeError, ERROR_CLASP(JSEXN_RANGEERR)) \ + REAL(ReferenceError, ERROR_CLASP(JSEXN_REFERENCEERR)) \ + REAL(SyntaxError, ERROR_CLASP(JSEXN_SYNTAXERR)) \ + REAL(TypeError, ERROR_CLASP(JSEXN_TYPEERR)) \ + REAL(URIError, ERROR_CLASP(JSEXN_URIERR)) \ + REAL(DebuggeeWouldRun, ERROR_CLASP(JSEXN_DEBUGGEEWOULDRUN)) \ + REAL(CompileError, ERROR_CLASP(JSEXN_WASMCOMPILEERROR)) \ + REAL(LinkError, ERROR_CLASP(JSEXN_WASMLINKERROR)) \ + REAL(RuntimeError, ERROR_CLASP(JSEXN_WASMRUNTIMEERROR)) \ + REAL(ArrayBuffer, OCLASP(ArrayBuffer)) \ + REAL(Int8Array, TYPED_ARRAY_CLASP(Int8)) \ + REAL(Uint8Array, TYPED_ARRAY_CLASP(Uint8)) \ + REAL(Int16Array, TYPED_ARRAY_CLASP(Int16)) \ + REAL(Uint16Array, TYPED_ARRAY_CLASP(Uint16)) \ + REAL(Int32Array, TYPED_ARRAY_CLASP(Int32)) \ + REAL(Uint32Array, TYPED_ARRAY_CLASP(Uint32)) \ + REAL(Float32Array, TYPED_ARRAY_CLASP(Float32)) \ + REAL(Float64Array, TYPED_ARRAY_CLASP(Float64)) \ + REAL(Uint8ClampedArray, TYPED_ARRAY_CLASP(Uint8Clamped)) \ + REAL(BigInt64Array, TYPED_ARRAY_CLASP(BigInt64)) \ + REAL(BigUint64Array, TYPED_ARRAY_CLASP(BigUint64)) \ + REAL(BigInt, OCLASP(BigInt)) \ + REAL(Proxy, CLASP(Proxy)) \ + REAL(WeakMap, OCLASP(WeakMap)) \ + REAL(Map, OCLASP(Map)) \ + REAL(Set, OCLASP(Set)) \ + REAL(DataView, OCLASP(DataView)) \ + REAL(Symbol, OCLASP(Symbol)) \ + REAL(ShadowRealm, OCLASP(ShadowRealm)) \ + REAL(SharedArrayBuffer, OCLASP(SharedArrayBuffer)) \ + REAL_IF_INTL(Intl, CLASP(Intl)) \ + REAL_IF_INTL(Collator, OCLASP(Collator)) \ + REAL_IF_INTL(DateTimeFormat, OCLASP(DateTimeFormat)) \ + REAL_IF_INTL(DisplayNames, OCLASP(DisplayNames)) \ + REAL_IF_INTL(ListFormat, OCLASP(ListFormat)) \ + REAL_IF_INTL(Locale, OCLASP(Locale)) \ + REAL_IF_INTL(NumberFormat, OCLASP(NumberFormat)) \ + REAL_IF_INTL(PluralRules, OCLASP(PluralRules)) \ + REAL_IF_INTL(RelativeTimeFormat, OCLASP(RelativeTimeFormat)) \ + REAL(Reflect, CLASP(Reflect)) \ + REAL(WeakSet, OCLASP(WeakSet)) \ + REAL(TypedArray, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \ + REAL(Atomics, OCLASP(Atomics)) \ + REAL(SavedFrame, &js::SavedFrame::class_) \ + REAL(Promise, OCLASP(Promise)) \ + REAL(AsyncFunction, CLASP(AsyncFunction)) \ + REAL(GeneratorFunction, CLASP(GeneratorFunction)) \ + REAL(AsyncGeneratorFunction, CLASP(AsyncGeneratorFunction)) \ + REAL(WebAssembly, OCLASP(WasmNamespace)) \ + REAL(WasmModule, OCLASP(WasmModule)) \ + REAL(WasmInstance, OCLASP(WasmInstance)) \ + REAL(WasmMemory, OCLASP(WasmMemory)) \ + REAL(WasmTable, OCLASP(WasmTable)) \ + REAL(WasmGlobal, OCLASP(WasmGlobal)) \ + REAL(WasmTag, OCLASP(WasmTag)) \ + REAL_IF_WASM_TYPE(WasmFunction, CLASP(WasmFunction)) \ + REAL(WasmException, OCLASP(WasmException)) \ + REAL(FinalizationRegistry, OCLASP(FinalizationRegistry)) \ + REAL(WeakRef, OCLASP(WeakRef)) \ + REAL(Iterator, OCLASP(Iterator)) \ + REAL(AsyncIterator, OCLASP(AsyncIterator)) \ + IF_RECORD_TUPLE(REAL(Record, (&RecordType::class_))) \ + IF_RECORD_TUPLE(REAL(Tuple, (&TupleType::class_))) + +#define JS_FOR_PROTOTYPES(REAL, IMAGINARY) \ + JS_FOR_PROTOTYPES_(REAL, IMAGINARY, IF_INTL(REAL, IMAGINARY), \ + IF_WASM_TYPE(REAL, IMAGINARY)) + +#define JS_FOR_EACH_PROTOTYPE(MACRO) JS_FOR_PROTOTYPES(MACRO, MACRO) + +#endif /* js_ProtoKey_h */ diff --git a/js/public/Proxy.h b/js/public/Proxy.h new file mode 100644 index 0000000000..b56bf8140b --- /dev/null +++ b/js/public/Proxy.h @@ -0,0 +1,768 @@ +/* -*- 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 js_Proxy_h +#define js_Proxy_h + +#include "mozilla/Maybe.h" + +#include "jstypes.h" // for JS_PUBLIC_API, JS_PUBLIC_DATA + +#include "js/Array.h" // JS::IsArrayAnswer +#include "js/CallNonGenericMethod.h" +#include "js/Class.h" +#include "js/HeapAPI.h" // for ObjectIsMarkedBlack +#include "js/Id.h" // for jsid +#include "js/Object.h" // JS::GetClass +#include "js/RootingAPI.h" // for Handle, MutableHandle (ptr only) +#include "js/shadow/Object.h" // JS::shadow::Object +#include "js/TypeDecls.h" // for HandleObject, HandleId, HandleValue, MutableHandleIdVector, MutableHandleValue, MutableHand... +#include "js/Value.h" // for Value, AssertValueIsNotGray, UndefinedValue, ObjectOrNullValue + +namespace js { + +class RegExpShared; + +class JS_PUBLIC_API Wrapper; + +/* + * [SMDOC] Proxy Objects + * + * A proxy is a JSObject with highly customizable behavior. ES6 specifies a + * single kind of proxy, but the customization mechanisms we use to implement + * ES6 Proxy objects are also useful wherever an object with weird behavior is + * wanted. Proxies are used to implement: + * + * - the scope objects used by the Debugger's frame.eval() method + * (see js::GetDebugEnvironment) + * + * - the khuey hack, whereby a whole compartment can be blown away + * even if other compartments hold references to objects in it + * (see js::NukeCrossCompartmentWrappers) + * + * - XPConnect security wrappers, which protect chrome from malicious content + * (js/xpconnect/wrappers) + * + * - DOM objects with special property behavior, like named getters + * (dom/bindings/Codegen.py generates these proxies from WebIDL) + * + * ### Proxies and internal methods + * + * ES2019 specifies 13 internal methods. The runtime semantics of just about + * everything a script can do to an object is specified in terms of these + * internal methods. For example: + * + * JS code ES6 internal method that gets called + * --------------------------- -------------------------------- + * obj.prop obj.[[Get]](obj, "prop") + * "prop" in obj obj.[[HasProperty]]("prop") + * new obj() obj.[[Construct]](<empty argument List>) + * + * With regard to the implementation of these internal methods, there are three + * very different kinds of object in SpiderMonkey. + * + * 1. Native objects cover most objects and contain both internal slots and + * properties. JSClassOps and ObjectOps may be used to override certain + * default behaviors. + * + * 2. Proxy objects are composed of internal slots and a ProxyHandler. The + * handler contains C++ methods that can implement these standard (and + * non-standard) internal methods. JSClassOps and ObjectOps for the base + * ProxyObject invoke the handler methods as appropriate. + * + * 3. Objects with custom layouts like TypedObjects. These rely on JSClassOps + * and ObjectOps to implement internal methods. + * + * Native objects with custom JSClassOps / ObjectOps are used when the object + * behaves very similar to a normal object such as the ArrayObject and it's + * length property. Most usages wrapping a C++ or other type should prefer + * using a Proxy. Using the proxy approach makes it much easier to create an + * ECMAScript and JIT compatible object, particularly if using an appropriate + * base class. + * + * Just about anything you do to a proxy will end up going through a C++ + * virtual method call. Possibly several. There's no reason the JITs and ICs + * can't specialize for particular proxies, based on the handler; but currently + * we don't do much of this, so the virtual method overhead typically is + * actually incurred. + * + * ### The proxy handler hierarchy + * + * A major use case for proxies is to forward each internal method call to + * another object, known as its target. The target can be an arbitrary JS + * object. Not every proxy has the notion of a target, however. + * + * To minimize code duplication, a set of abstract proxy handler classes is + * provided, from which other handlers may inherit. These abstract classes are + * organized in the following hierarchy: + * + * BaseProxyHandler + * | + * ForwardingProxyHandler // has a target and forwards internal methods + * | + * Wrapper // can be unwrapped to reveal target + * | // (see js::CheckedUnwrap) + * | + * CrossCompartmentWrapper // target is in another compartment; + * // implements membrane between compartments + * + * Example: Some DOM objects (including all the arraylike DOM objects) are + * implemented as proxies. Since these objects don't need to forward operations + * to any underlying JS object, BaseDOMProxyHandler directly subclasses + * BaseProxyHandler. + * + * Gecko's security wrappers are examples of cross-compartment wrappers. + * + * ### Proxy prototype chains + * + * While most ECMAScript internal methods are handled by simply calling the + * handler method, the [[GetPrototypeOf]] / [[SetPrototypeOf]] behaviors may + * follow one of two models: + * + * 1. A concrete prototype object (or null) is passed to object construction + * and ordinary prototype read and write applies. The prototype-related + * handler hooks are never called in this case. The [[Prototype]] slot is + * used to store the current prototype value. + * + * 2. TaggedProto::LazyProto is passed to NewProxyObject (or the + * ProxyOptions::lazyProto flag is set). Each read or write of the + * prototype will invoke the handler. This dynamic prototype behavior may + * be useful for wrapper-like objects. If this mode is used the + * getPrototype handler at a minimum must be implemented. + * + * NOTE: In this mode the [[Prototype]] internal slot is unavailable and + * must be simulated if needed. This is non-standard, but an + * appropriate handler can hide this implementation detail. + * + * One subtlety here is that ECMAScript has a notion of "ordinary" prototypes. + * An object that doesn't override [[GetPrototypeOf]] is considered to have an + * ordinary prototype. The getPrototypeIfOrdinary handler must be implemented + * by you or your base class. Typically model 1 will be considered "ordinary" + * and model 2 will not. + */ + +/* + * BaseProxyHandler is the most generic kind of proxy handler. It does not make + * any assumptions about the target. Consequently, it does not provide any + * default implementation for most methods. As a convenience, a few high-level + * methods, like get() and set(), are given default implementations that work by + * calling the low-level methods, like getOwnPropertyDescriptor(). + * + * Important: If you add a method here, you should probably also add a + * Proxy::foo entry point with an AutoEnterPolicy. If you don't, you need an + * explicit override for the method in SecurityWrapper. See bug 945826 comment + * 0. + */ +class JS_PUBLIC_API BaseProxyHandler { + /* + * Sometimes it's desirable to designate groups of proxy handlers as + * "similar". For this, we use the notion of a "family": A consumer-provided + * opaque pointer that designates the larger group to which this proxy + * belongs. + * + * If it will never be important to differentiate this proxy from others as + * part of a distinct group, nullptr may be used instead. + */ + const void* mFamily; + + /* + * Proxy handlers can use mHasPrototype to request the following special + * treatment from the JS engine: + * + * - When mHasPrototype is true, the engine never calls these methods: + * has, set, enumerate, iterate. Instead, for these operations, + * it calls the "own" methods like getOwnPropertyDescriptor, hasOwn, + * defineProperty, getOwnEnumerablePropertyKeys, etc., + * and consults the prototype chain if needed. + * + * - When mHasPrototype is true, the engine calls handler->get() only if + * handler->hasOwn() says an own property exists on the proxy. If not, + * it consults the prototype chain. + * + * This is useful because it frees the ProxyHandler from having to implement + * any behavior having to do with the prototype chain. + */ + bool mHasPrototype; + + /* + * All proxies indicate whether they have any sort of interesting security + * policy that might prevent the caller from doing something it wants to + * the object. In the case of wrappers, this distinction is used to + * determine whether the caller may strip off the wrapper if it so desires. + */ + bool mHasSecurityPolicy; + + public: + explicit constexpr BaseProxyHandler(const void* aFamily, + bool aHasPrototype = false, + bool aHasSecurityPolicy = false) + : mFamily(aFamily), + mHasPrototype(aHasPrototype), + mHasSecurityPolicy(aHasSecurityPolicy) {} + + bool hasPrototype() const { return mHasPrototype; } + + bool hasSecurityPolicy() const { return mHasSecurityPolicy; } + + inline const void* family() const { return mFamily; } + static size_t offsetOfFamily() { return offsetof(BaseProxyHandler, mFamily); } + + virtual bool finalizeInBackground(const JS::Value& priv) const { + /* + * Called on creation of a proxy to determine whether its finalize + * method can be finalized on the background thread. + */ + return true; + } + + virtual bool canNurseryAllocate() const { + /* + * Nursery allocation is allowed if and only if it is safe to not + * run |finalize| when the ProxyObject dies. + */ + return false; + } + + /* Policy enforcement methods. + * + * enter() allows the policy to specify whether the caller may perform |act| + * on the proxy's |id| property. In the case when |act| is CALL, |id| is + * generally JSID_VOID. The |mayThrow| parameter indicates whether a + * handler that wants to throw custom exceptions when denying should do so + * or not. + * + * The |act| parameter to enter() specifies the action being performed. + * If |bp| is false, the method suggests that the caller throw (though it + * may still decide to squelch the error). + * + * We make these OR-able so that assertEnteredPolicy can pass a union of them. + * For example, get{,Own}PropertyDescriptor is invoked by calls to ::get() + * ::set(), in addition to being invoked on its own, so there are several + * valid Actions that could have been entered. + */ + typedef uint32_t Action; + enum { + NONE = 0x00, + GET = 0x01, + SET = 0x02, + CALL = 0x04, + ENUMERATE = 0x08, + GET_PROPERTY_DESCRIPTOR = 0x10 + }; + + virtual bool enter(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + Action act, bool mayThrow, bool* bp) const; + + /* Standard internal methods. */ + virtual bool getOwnPropertyDescriptor( + JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) const = 0; + virtual bool defineProperty(JSContext* cx, JS::HandleObject proxy, + JS::HandleId id, + JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult& result) const = 0; + virtual bool ownPropertyKeys(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props) const = 0; + virtual bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::ObjectOpResult& result) const = 0; + + /* + * These methods are standard, but the engine does not normally call them. + * They're opt-in. See "Proxy prototype chains" above. + * + * getPrototype() crashes if called. setPrototype() throws a TypeError. + */ + virtual bool getPrototype(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleObject protop) const; + virtual bool setPrototype(JSContext* cx, JS::HandleObject proxy, + JS::HandleObject proto, + JS::ObjectOpResult& result) const; + + /* Non-standard but conceptual kin to {g,s}etPrototype, so these live here. */ + virtual bool getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject proxy, + bool* isOrdinary, + JS::MutableHandleObject protop) const = 0; + virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject proxy, + bool* succeeded) const; + + virtual bool preventExtensions(JSContext* cx, JS::HandleObject proxy, + JS::ObjectOpResult& result) const = 0; + virtual bool isExtensible(JSContext* cx, JS::HandleObject proxy, + bool* extensible) const = 0; + + /* + * These standard internal methods are implemented, as a convenience, so + * that ProxyHandler subclasses don't have to provide every single method. + * + * The base-class implementations work by calling getOwnPropertyDescriptor() + * and going up the [[Prototype]] chain if necessary. The algorithm for this + * follows what is defined for Ordinary Objects in the ES spec. + * They do not follow any standard. When in doubt, override them. + */ + virtual bool has(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + bool* bp) const; + virtual bool get(JSContext* cx, JS::HandleObject proxy, + JS::HandleValue receiver, JS::HandleId id, + JS::MutableHandleValue vp) const; + virtual bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::HandleValue v, JS::HandleValue receiver, + JS::ObjectOpResult& result) const; + + // Use the ProxyExpando object for private fields, rather than taking the + // normal get/set/defineField paths. + virtual bool useProxyExpandoObjectForPrivateFields() const { return true; } + + // For some exotic objects (WindowProxy, Location), we want to be able to + // throw rather than allow private fields on these objects. + // + // As a simplfying assumption, if throwOnPrivateFields returns true, + // we should also return true to useProxyExpandoObjectForPrivateFields. + virtual bool throwOnPrivateField() const { return false; } + + /* + * [[Call]] and [[Construct]] are standard internal methods but according + * to the spec, they are not present on every object. + * + * SpiderMonkey never calls a proxy's call()/construct() internal method + * unless isCallable()/isConstructor() returns true for that proxy. + * + * BaseProxyHandler::isCallable()/isConstructor() always return false, and + * BaseProxyHandler::call()/construct() crash if called. So if you're + * creating a kind of that is never callable, you don't have to override + * anything, but otherwise you probably want to override all four. + */ + virtual bool call(JSContext* cx, JS::HandleObject proxy, + const JS::CallArgs& args) const; + virtual bool construct(JSContext* cx, JS::HandleObject proxy, + const JS::CallArgs& args) const; + + /* SpiderMonkey extensions. */ + virtual bool enumerate(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props) const; + virtual bool hasOwn(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + bool* bp) const; + virtual bool getOwnEnumerablePropertyKeys( + JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props) const; + virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test, + JS::NativeImpl impl, const JS::CallArgs& args) const; + virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject proxy, + ESClass* cls) const; + virtual bool isArray(JSContext* cx, JS::HandleObject proxy, + JS::IsArrayAnswer* answer) const; + virtual const char* className(JSContext* cx, JS::HandleObject proxy) const; + virtual JSString* fun_toString(JSContext* cx, JS::HandleObject proxy, + bool isToSource) const; + virtual RegExpShared* regexp_toShared(JSContext* cx, + JS::HandleObject proxy) const; + virtual bool boxedValue_unbox(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleValue vp) const; + virtual void trace(JSTracer* trc, JSObject* proxy) const; + virtual void finalize(JS::GCContext* gcx, JSObject* proxy) const; + virtual size_t objectMoved(JSObject* proxy, JSObject* old) const; + + // Allow proxies, wrappers in particular, to specify callability at runtime. + // Note: These do not take const JSObject*, but they do in spirit. + // We are not prepared to do this, as there's little const correctness + // in the external APIs that handle proxies. + virtual bool isCallable(JSObject* obj) const; + virtual bool isConstructor(JSObject* obj) const; + + virtual bool getElements(JSContext* cx, JS::HandleObject proxy, + uint32_t begin, uint32_t end, + ElementAdder* adder) const; + + virtual bool isScripted() const { return false; } +}; + +extern JS_PUBLIC_DATA const JSClass ProxyClass; + +inline bool IsProxy(const JSObject* obj) { + return reinterpret_cast<const JS::shadow::Object*>(obj)->shape->isProxy(); +} + +namespace detail { + +// Proxy slot layout +// ----------------- +// +// Every proxy has a ProxyValueArray that contains the following Values: +// +// - The expando slot. This is used to hold private fields should they be +// stamped into a non-forwarding proxy type. +// - The private slot. +// - The reserved slots. The number of slots is determined by the proxy's Class. +// +// Proxy objects store a pointer to the reserved slots (ProxyReservedSlots*). +// The ProxyValueArray and the private slot can be accessed using +// ProxyValueArray::fromReservedSlots or ProxyDataLayout::values. +// +// Storing a pointer to ProxyReservedSlots instead of ProxyValueArray has a +// number of advantages. In particular, it means JS::GetReservedSlot and +// JS::SetReservedSlot can be used with both proxies and native objects. This +// works because the ProxyReservedSlots* pointer is stored where native objects +// store their dynamic slots pointer. + +struct ProxyReservedSlots { + JS::Value slots[1]; + + static constexpr ptrdiff_t offsetOfPrivateSlot(); + + static inline int offsetOfSlot(size_t slot) { + return offsetof(ProxyReservedSlots, slots[0]) + slot * sizeof(JS::Value); + } + + void init(size_t nreserved) { + for (size_t i = 0; i < nreserved; i++) { + slots[i] = JS::UndefinedValue(); + } + } + + ProxyReservedSlots(const ProxyReservedSlots&) = delete; + void operator=(const ProxyReservedSlots&) = delete; +}; + +struct ProxyValueArray { + JS::Value expandoSlot; + JS::Value privateSlot; + ProxyReservedSlots reservedSlots; + + void init(size_t nreserved) { + expandoSlot = JS::ObjectOrNullValue(nullptr); + privateSlot = JS::UndefinedValue(); + reservedSlots.init(nreserved); + } + + static MOZ_ALWAYS_INLINE ProxyValueArray* fromReservedSlots( + ProxyReservedSlots* slots) { + uintptr_t p = reinterpret_cast<uintptr_t>(slots); + return reinterpret_cast<ProxyValueArray*>(p - offsetOfReservedSlots()); + } + static constexpr size_t offsetOfReservedSlots() { + return offsetof(ProxyValueArray, reservedSlots); + } + + static size_t allocCount(size_t nreserved) { + static_assert(offsetOfReservedSlots() % sizeof(JS::Value) == 0); + return offsetOfReservedSlots() / sizeof(JS::Value) + nreserved; + } + static size_t sizeOf(size_t nreserved) { + return allocCount(nreserved) * sizeof(JS::Value); + } + + ProxyValueArray(const ProxyValueArray&) = delete; + void operator=(const ProxyValueArray&) = delete; +}; + +/* static */ +constexpr ptrdiff_t ProxyReservedSlots::offsetOfPrivateSlot() { + return -ptrdiff_t(ProxyValueArray::offsetOfReservedSlots()) + + offsetof(ProxyValueArray, privateSlot); +} + +// All proxies share the same data layout. Following the object's shape and +// type, the proxy has a ProxyDataLayout structure with a pointer to an array +// of values and the proxy's handler. This is designed both so that proxies can +// be easily swapped with other objects (via RemapWrapper) and to mimic the +// layout of other objects (proxies and other objects have the same size) so +// that common code can access either type of object. +// +// See GetReservedOrProxyPrivateSlot below. +struct ProxyDataLayout { + ProxyReservedSlots* reservedSlots; + const BaseProxyHandler* handler; + + MOZ_ALWAYS_INLINE ProxyValueArray* values() const { + return ProxyValueArray::fromReservedSlots(reservedSlots); + } +}; + +#ifdef JS_64BIT +constexpr uint32_t ProxyDataOffset = 1 * sizeof(void*); +#else +constexpr uint32_t ProxyDataOffset = 2 * sizeof(void*); +#endif + +inline ProxyDataLayout* GetProxyDataLayout(JSObject* obj) { + MOZ_ASSERT(IsProxy(obj)); + return reinterpret_cast<ProxyDataLayout*>(reinterpret_cast<uint8_t*>(obj) + + ProxyDataOffset); +} + +inline const ProxyDataLayout* GetProxyDataLayout(const JSObject* obj) { + MOZ_ASSERT(IsProxy(obj)); + return reinterpret_cast<const ProxyDataLayout*>( + reinterpret_cast<const uint8_t*>(obj) + ProxyDataOffset); +} + +JS_PUBLIC_API void SetValueInProxy(JS::Value* slot, const JS::Value& value); + +inline void SetProxyReservedSlotUnchecked(JSObject* obj, size_t n, + const JS::Value& extra) { + MOZ_ASSERT(n < JSCLASS_RESERVED_SLOTS(JS::GetClass(obj))); + + JS::Value* vp = &GetProxyDataLayout(obj)->reservedSlots->slots[n]; + + // Trigger a barrier before writing the slot. + if (vp->isGCThing() || extra.isGCThing()) { + SetValueInProxy(vp, extra); + } else { + *vp = extra; + } +} + +} // namespace detail + +inline const BaseProxyHandler* GetProxyHandler(const JSObject* obj) { + return detail::GetProxyDataLayout(obj)->handler; +} + +inline const JS::Value& GetProxyPrivate(const JSObject* obj) { + return detail::GetProxyDataLayout(obj)->values()->privateSlot; +} + +inline const JS::Value& GetProxyExpando(const JSObject* obj) { + return detail::GetProxyDataLayout(obj)->values()->expandoSlot; +} + +inline JSObject* GetProxyTargetObject(const JSObject* obj) { + return GetProxyPrivate(obj).toObjectOrNull(); +} + +inline const JS::Value& GetProxyReservedSlot(const JSObject* obj, size_t n) { + MOZ_ASSERT(n < JSCLASS_RESERVED_SLOTS(JS::GetClass(obj))); + return detail::GetProxyDataLayout(obj)->reservedSlots->slots[n]; +} + +inline void SetProxyHandler(JSObject* obj, const BaseProxyHandler* handler) { + detail::GetProxyDataLayout(obj)->handler = handler; +} + +inline void SetProxyReservedSlot(JSObject* obj, size_t n, + const JS::Value& extra) { +#ifdef DEBUG + if (gc::detail::ObjectIsMarkedBlack(obj)) { + JS::AssertValueIsNotGray(extra); + } +#endif + + detail::SetProxyReservedSlotUnchecked(obj, n, extra); +} + +inline void SetProxyPrivate(JSObject* obj, const JS::Value& value) { +#ifdef DEBUG + if (gc::detail::ObjectIsMarkedBlack(obj)) { + JS::AssertValueIsNotGray(value); + } +#endif + + JS::Value* vp = &detail::GetProxyDataLayout(obj)->values()->privateSlot; + + // Trigger a barrier before writing the slot. + if (vp->isGCThing() || value.isGCThing()) { + detail::SetValueInProxy(vp, value); + } else { + *vp = value; + } +} + +inline bool IsScriptedProxy(const JSObject* obj) { + return IsProxy(obj) && GetProxyHandler(obj)->isScripted(); +} + +class MOZ_STACK_CLASS ProxyOptions { + protected: + /* protected constructor for subclass */ + explicit ProxyOptions(bool lazyProtoArg) + : lazyProto_(lazyProtoArg), clasp_(&ProxyClass) {} + + public: + ProxyOptions() : ProxyOptions(false) {} + + bool lazyProto() const { return lazyProto_; } + ProxyOptions& setLazyProto(bool flag) { + lazyProto_ = flag; + return *this; + } + + const JSClass* clasp() const { return clasp_; } + ProxyOptions& setClass(const JSClass* claspArg) { + clasp_ = claspArg; + return *this; + } + + private: + bool lazyProto_; + const JSClass* clasp_; +}; + +JS_PUBLIC_API JSObject* NewProxyObject( + JSContext* cx, const BaseProxyHandler* handler, JS::HandleValue priv, + JSObject* proto, const ProxyOptions& options = ProxyOptions()); + +JSObject* RenewProxyObject(JSContext* cx, JSObject* obj, + BaseProxyHandler* handler, const JS::Value& priv); + +class JS_PUBLIC_API AutoEnterPolicy { + public: + typedef BaseProxyHandler::Action Action; + AutoEnterPolicy(JSContext* cx, const BaseProxyHandler* handler, + JS::HandleObject wrapper, JS::HandleId id, Action act, + bool mayThrow) +#ifdef JS_DEBUG + : context(nullptr) +#endif + { + allow = handler->hasSecurityPolicy() + ? handler->enter(cx, wrapper, id, act, mayThrow, &rv) + : true; + recordEnter(cx, wrapper, id, act); + // We want to throw an exception if all of the following are true: + // * The policy disallowed access. + // * The policy set rv to false, indicating that we should throw. + // * The caller did not instruct us to ignore exceptions. + // * The policy did not throw itself. + if (!allow && !rv && mayThrow) { + reportErrorIfExceptionIsNotPending(cx, id); + } + } + + virtual ~AutoEnterPolicy() { recordLeave(); } + inline bool allowed() { return allow; } + inline bool returnValue() { + MOZ_ASSERT(!allowed()); + return rv; + } + + protected: + // no-op constructor for subclass + AutoEnterPolicy() +#ifdef JS_DEBUG + : context(nullptr), + enteredAction(BaseProxyHandler::NONE) +#endif + { + } + void reportErrorIfExceptionIsNotPending(JSContext* cx, JS::HandleId id); + bool allow; + bool rv; + +#ifdef JS_DEBUG + JSContext* context; + mozilla::Maybe<JS::HandleObject> enteredProxy; + mozilla::Maybe<JS::HandleId> enteredId; + Action enteredAction; + + // NB: We explicitly don't track the entered action here, because sometimes + // set() methods do an implicit get() during their implementation, leading + // to spurious assertions. + AutoEnterPolicy* prev; + void recordEnter(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + Action act); + void recordLeave(); + + friend JS_PUBLIC_API void assertEnteredPolicy(JSContext* cx, JSObject* proxy, + jsid id, Action act); +#else + inline void recordEnter(JSContext* cx, JSObject* proxy, jsid id, Action act) { + } + inline void recordLeave() {} +#endif + + private: + // This operator needs to be deleted explicitly, otherwise Visual C++ will + // create it automatically when it is part of the export JS API. In that + // case, compile would fail because HandleId is not allowed to be assigned + // and consequently instantiation of assign operator of mozilla::Maybe + // would fail. See bug 1325351 comment 16. Copy constructor is removed at + // the same time for consistency. + AutoEnterPolicy(const AutoEnterPolicy&) = delete; + AutoEnterPolicy& operator=(const AutoEnterPolicy&) = delete; +}; + +#ifdef JS_DEBUG +class JS_PUBLIC_API AutoWaivePolicy : public AutoEnterPolicy { + public: + AutoWaivePolicy(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + BaseProxyHandler::Action act) { + allow = true; + recordEnter(cx, proxy, id, act); + } +}; +#else +class JS_PUBLIC_API AutoWaivePolicy { + public: + AutoWaivePolicy(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + BaseProxyHandler::Action act) {} +}; +#endif + +#ifdef JS_DEBUG +extern JS_PUBLIC_API void assertEnteredPolicy(JSContext* cx, JSObject* obj, + jsid id, + BaseProxyHandler::Action act); +#else +inline void assertEnteredPolicy(JSContext* cx, JSObject* obj, jsid id, + BaseProxyHandler::Action act) {} +#endif + +extern JS_PUBLIC_DATA const JSClassOps ProxyClassOps; +extern JS_PUBLIC_DATA const js::ClassExtension ProxyClassExtension; +extern JS_PUBLIC_DATA const js::ObjectOps ProxyObjectOps; + +template <unsigned Flags> +constexpr unsigned CheckProxyFlags() { + constexpr size_t reservedSlots = + (Flags >> JSCLASS_RESERVED_SLOTS_SHIFT) & JSCLASS_RESERVED_SLOTS_MASK; + + // For now assert each Proxy Class has at least 1 reserved slot. This is + // not a hard requirement, but helps catch Classes that need an explicit + // JSCLASS_HAS_RESERVED_SLOTS since bug 1360523. + static_assert(reservedSlots > 0, + "Proxy Classes must have at least 1 reserved slot"); + + constexpr size_t numSlots = + offsetof(js::detail::ProxyValueArray, reservedSlots) / sizeof(JS::Value); + + // ProxyValueArray must fit inline in the object, so assert the number of + // slots does not exceed MAX_FIXED_SLOTS. + static_assert(numSlots + reservedSlots <= JS::shadow::Object::MAX_FIXED_SLOTS, + "ProxyValueArray size must not exceed max JSObject size"); + + // Proxies must not have the JSCLASS_SKIP_NURSERY_FINALIZE flag set: they + // always have finalizers, and whether they can be nursery allocated is + // controlled by the canNurseryAllocate() method on the proxy handler. + static_assert(!(Flags & JSCLASS_SKIP_NURSERY_FINALIZE), + "Proxies must not use JSCLASS_SKIP_NURSERY_FINALIZE; use " + "the canNurseryAllocate() proxy handler method instead."); + return Flags; +} + +#define PROXY_CLASS_DEF_WITH_CLASS_SPEC(name, flags, classSpec) \ + { \ + name, \ + JSClass::NON_NATIVE | JSCLASS_IS_PROXY | \ + JSCLASS_DELAY_METADATA_BUILDER | js::CheckProxyFlags<flags>(), \ + &js::ProxyClassOps, classSpec, &js::ProxyClassExtension, \ + &js::ProxyObjectOps \ + } + +#define PROXY_CLASS_DEF(name, flags) \ + PROXY_CLASS_DEF_WITH_CLASS_SPEC(name, flags, JS_NULL_CLASS_SPEC) + +// Converts a proxy into a DeadObjectProxy that will throw exceptions on all +// access. This will run the proxy's finalizer to perform clean-up before the +// conversion happens. +JS_PUBLIC_API void NukeNonCCWProxy(JSContext* cx, JS::HandleObject proxy); + +// This is a variant of js::NukeNonCCWProxy() for CCWs. It should only be called +// on CCWs that have been removed from CCW tables. +JS_PUBLIC_API void NukeRemovedCrossCompartmentWrapper(JSContext* cx, + JSObject* wrapper); + +} /* namespace js */ + +#endif /* js_Proxy_h */ diff --git a/js/public/Realm.h b/js/public/Realm.h new file mode 100644 index 0000000000..3421a9fed3 --- /dev/null +++ b/js/public/Realm.h @@ -0,0 +1,202 @@ +/* -*- 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 js_Realm_h +#define js_Realm_h + +#include "js/shadow/Realm.h" // JS::shadow::Realm + +#include "js/GCPolicyAPI.h" +#include "js/TypeDecls.h" // forward-declaration of JS::Realm + +/************************************************************************/ + +// [SMDOC] Realms +// +// Data associated with a global object. In the browser each frame has its +// own global/realm. + +namespace js { +namespace gc { +JS_PUBLIC_API void TraceRealm(JSTracer* trc, JS::Realm* realm, + const char* name); +} // namespace gc +} // namespace js + +namespace JS { +class JS_PUBLIC_API AutoRequireNoGC; + +// Each Realm holds a weak reference to its GlobalObject. +template <> +struct GCPolicy<Realm*> : public NonGCPointerPolicy<Realm*> { + static void trace(JSTracer* trc, Realm** vp, const char* name) { + if (*vp) { + ::js::gc::TraceRealm(trc, *vp, name); + } + } +}; + +// Get the current realm, if any. The ECMAScript spec calls this "the current +// Realm Record". +extern JS_PUBLIC_API Realm* GetCurrentRealmOrNull(JSContext* cx); + +// Return the compartment that contains a given realm. +inline JS::Compartment* GetCompartmentForRealm(Realm* realm) { + return shadow::Realm::get(realm)->compartment(); +} + +// Return an object's realm. All objects except cross-compartment wrappers are +// created in a particular realm, which never changes. Returns null if obj is +// a cross-compartment wrapper. +extern JS_PUBLIC_API Realm* GetObjectRealmOrNull(JSObject* obj); + +// Get the value of the "private data" internal field of the given Realm. +// This field is initially null and is set using SetRealmPrivate. +// It's a pointer to embeddding-specific data that SpiderMonkey never uses. +extern JS_PUBLIC_API void* GetRealmPrivate(Realm* realm); + +// Set the "private data" internal field of the given Realm. +extern JS_PUBLIC_API void SetRealmPrivate(Realm* realm, void* data); + +typedef void (*DestroyRealmCallback)(JS::GCContext* gcx, Realm* realm); + +// Set the callback SpiderMonkey calls just before garbage-collecting a realm. +// Embeddings can use this callback to free private data associated with the +// realm via SetRealmPrivate. +// +// By the time this is called, the global object for the realm has already been +// collected. +extern JS_PUBLIC_API void SetDestroyRealmCallback( + JSContext* cx, DestroyRealmCallback callback); + +using RealmNameCallback = void (*)(JSContext* cx, Realm* realm, char* buf, + size_t bufsize, + const JS::AutoRequireNoGC& nogc); + +// Set the callback SpiderMonkey calls to get the name of a realm, for +// diagnostic output. +extern JS_PUBLIC_API void SetRealmNameCallback(JSContext* cx, + RealmNameCallback callback); + +// Get the global object for the given realm. This only returns nullptr during +// GC, between collecting the global object and destroying the Realm. +extern JS_PUBLIC_API JSObject* GetRealmGlobalOrNull(Realm* realm); + +// Initialize standard JS class constructors, prototypes, and any top-level +// functions and constants associated with the standard classes (e.g. isNaN +// for Number). +extern JS_PUBLIC_API bool InitRealmStandardClasses(JSContext* cx); + +// If the current realm has the non-standard freezeBuiltins option set to true, +// freeze the constructor object and seal the prototype. +extern JS_PUBLIC_API bool MaybeFreezeCtorAndPrototype(JSContext* cx, + HandleObject ctor, + HandleObject maybeProto); + +/* + * Ways to get various per-Realm objects. All the getters declared below operate + * on the JSContext's current Realm. + */ + +extern JS_PUBLIC_API JSObject* GetRealmObjectPrototype(JSContext* cx); +extern JS_PUBLIC_API JS::Handle<JSObject*> GetRealmObjectPrototypeHandle( + JSContext* cx); + +extern JS_PUBLIC_API JSObject* GetRealmFunctionPrototype(JSContext* cx); + +extern JS_PUBLIC_API JSObject* GetRealmArrayPrototype(JSContext* cx); + +extern JS_PUBLIC_API JSObject* GetRealmErrorPrototype(JSContext* cx); + +extern JS_PUBLIC_API JSObject* GetRealmIteratorPrototype(JSContext* cx); + +extern JS_PUBLIC_API JSObject* GetRealmAsyncIteratorPrototype(JSContext* cx); + +// Returns an object that represents the realm, that can be referred from +// other realm/compartment. +// See the consumer in `MaybeCrossOriginObjectMixins::EnsureHolder` for details. +extern JS_PUBLIC_API JSObject* GetRealmKeyObject(JSContext* cx); + +// Implements https://tc39.github.io/ecma262/#sec-getfunctionrealm +// 7.3.22 GetFunctionRealm ( obj ) +// +// WARNING: may return a realm in a different compartment! +// +// Will throw an exception and return nullptr when a security wrapper or revoked +// proxy is encountered. +extern JS_PUBLIC_API Realm* GetFunctionRealm(JSContext* cx, + HandleObject objArg); + +/** NB: This API is infallible; a nullptr return value does not indicate error. + * + * |target| must not be a cross-compartment wrapper because CCWs are not + * associated with a single realm. + * + * Entering a realm roots the realm and its global object until the matching + * JS::LeaveRealm() call. + */ +extern JS_PUBLIC_API JS::Realm* EnterRealm(JSContext* cx, JSObject* target); + +extern JS_PUBLIC_API void LeaveRealm(JSContext* cx, JS::Realm* oldRealm); + +} // namespace JS + +/* + * At any time, a JSContext has a current (possibly-nullptr) realm. The + * preferred way to change the current realm is with JSAutoRealm: + * + * void foo(JSContext* cx, JSObject* obj) { + * // in some realm 'r' + * { + * JSAutoRealm ar(cx, obj); // constructor enters + * // in the realm of 'obj' + * } // destructor leaves + * // back in realm 'r' + * } + * + * The object passed to JSAutoRealm must *not* be a cross-compartment wrapper, + * because CCWs are not associated with a single realm. + * + * For more complicated uses that don't neatly fit in a C++ stack frame, the + * realm can be entered and left using separate function calls: + * + * void foo(JSContext* cx, JSObject* obj) { + * // in 'oldRealm' + * JS::Realm* oldRealm = JS::EnterRealm(cx, obj); + * // in the realm of 'obj' + * JS::LeaveRealm(cx, oldRealm); + * // back in 'oldRealm' + * } + * + * Note: these calls must still execute in a LIFO manner w.r.t all other + * enter/leave calls on the context. Furthermore, only the return value of a + * JS::EnterRealm call may be passed as the 'oldRealm' argument of + * the corresponding JS::LeaveRealm call. + * + * Entering a realm roots the realm and its global object for the lifetime of + * the JSAutoRealm. + */ + +class MOZ_RAII JS_PUBLIC_API JSAutoRealm { + JSContext* cx_; + JS::Realm* oldRealm_; + + public: + JSAutoRealm(JSContext* cx, JSObject* target); + JSAutoRealm(JSContext* cx, JSScript* target); + ~JSAutoRealm(); +}; + +class MOZ_RAII JS_PUBLIC_API JSAutoNullableRealm { + JSContext* cx_; + JS::Realm* oldRealm_; + + public: + explicit JSAutoNullableRealm(JSContext* cx, JSObject* targetOrNull); + ~JSAutoNullableRealm(); +}; + +#endif // js_Realm_h diff --git a/js/public/RealmIterators.h b/js/public/RealmIterators.h new file mode 100644 index 0000000000..13f19dbb8f --- /dev/null +++ b/js/public/RealmIterators.h @@ -0,0 +1,81 @@ +/* -*- 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/. */ + +/* + * Various interfaces to iterate over the Realms given various context such as + * principals, compartments and GC zones. + */ + +#ifndef js_RealmIterators_h +#define js_RealmIterators_h + +#include "js/GCAPI.h" +#include "js/TypeDecls.h" + +struct JSPrincipals; + +namespace JS { + +class JS_PUBLIC_API AutoRequireNoGC; + +using IterateRealmCallback = void (*)(JSContext* cx, void* data, Realm* realm, + const AutoRequireNoGC& nogc); + +/** + * This function calls |realmCallback| on every realm. Beware that there is no + * guarantee that the realm will survive after the callback returns. Also, + * barriers are disabled via the TraceSession. + */ +extern JS_PUBLIC_API void IterateRealms(JSContext* cx, void* data, + IterateRealmCallback realmCallback); + +/** + * Like IterateRealms, but only call the callback for realms using |principals|. + */ +extern JS_PUBLIC_API void IterateRealmsWithPrincipals( + JSContext* cx, JSPrincipals* principals, void* data, + IterateRealmCallback realmCallback); + +/** + * Like IterateRealms, but only iterates realms in |compartment|. + */ +extern JS_PUBLIC_API void IterateRealmsInCompartment( + JSContext* cx, JS::Compartment* compartment, void* data, + IterateRealmCallback realmCallback); + +/** + * An enum that JSIterateCompartmentCallback can return to indicate + * whether to keep iterating. + */ +enum class CompartmentIterResult { KeepGoing, Stop }; + +} // namespace JS + +using JSIterateCompartmentCallback = + JS::CompartmentIterResult (*)(JSContext*, void*, JS::Compartment*); + +/** + * This function calls |compartmentCallback| on every compartment until either + * all compartments have been iterated or CompartmentIterResult::Stop is + * returned. Beware that there is no guarantee that the compartment will survive + * after the callback returns. Also, barriers are disabled via the TraceSession. + */ +extern JS_PUBLIC_API void JS_IterateCompartments( + JSContext* cx, void* data, + JSIterateCompartmentCallback compartmentCallback); + +/** + * This function calls |compartmentCallback| on every compartment in the given + * zone until either all compartments have been iterated or + * CompartmentIterResult::Stop is returned. Beware that there is no guarantee + * that the compartment will survive after the callback returns. Also, barriers + * are disabled via the TraceSession. + */ +extern JS_PUBLIC_API void JS_IterateCompartmentsInZone( + JSContext* cx, JS::Zone* zone, void* data, + JSIterateCompartmentCallback compartmentCallback); + +#endif /* js_RealmIterators_h */ diff --git a/js/public/RealmOptions.h b/js/public/RealmOptions.h new file mode 100644 index 0000000000..2f5cef8d6c --- /dev/null +++ b/js/public/RealmOptions.h @@ -0,0 +1,414 @@ +/* -*- 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/. */ + +/* + * Options specified when creating a realm to determine its behavior, immutable + * options determining the behavior of an existing realm, and mutable options on + * an existing realm that may be changed when desired. + */ + +#ifndef js_RealmOptions_h +#define js_RealmOptions_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Class.h" // JSTraceOp + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; + +namespace JS { + +class JS_PUBLIC_API Compartment; +class JS_PUBLIC_API Realm; +class JS_PUBLIC_API Zone; + +} // namespace JS + +namespace JS { + +/** + * Specification for which compartment/zone a newly created realm should use. + */ +enum class CompartmentSpecifier { + // Create a new realm and compartment in the single runtime wide system + // zone. The meaning of this zone is left to the embedder. + NewCompartmentInSystemZone, + + // Create a new realm and compartment in a particular existing zone. + NewCompartmentInExistingZone, + + // Create a new zone/compartment. + NewCompartmentAndZone, + + // Create a new realm in an existing compartment. + ExistingCompartment, +}; + +/** + * Specification for whether weak refs should be enabled and if so whether the + * FinalizationRegistry.cleanupSome method should be present. + */ +enum class WeakRefSpecifier { + Disabled, + EnabledWithCleanupSome, + EnabledWithoutCleanupSome +}; + +/** + * RealmCreationOptions specifies options relevant to creating a new realm, that + * are either immutable characteristics of that realm or that are discarded + * after the realm has been created. + * + * Access to these options on an existing realm is read-only: if you need + * particular selections, you must make them before you create the realm. + */ +class JS_PUBLIC_API RealmCreationOptions { + public: + RealmCreationOptions() : comp_(nullptr) {} + + JSTraceOp getTrace() const { return traceGlobal_; } + RealmCreationOptions& setTrace(JSTraceOp op) { + traceGlobal_ = op; + return *this; + } + + Zone* zone() const { + MOZ_ASSERT(compSpec_ == CompartmentSpecifier::NewCompartmentInExistingZone); + return zone_; + } + Compartment* compartment() const { + MOZ_ASSERT(compSpec_ == CompartmentSpecifier::ExistingCompartment); + return comp_; + } + CompartmentSpecifier compartmentSpecifier() const { return compSpec_; } + + // Set the compartment/zone to use for the realm. See CompartmentSpecifier + // above. + RealmCreationOptions& setNewCompartmentInSystemZone(); + RealmCreationOptions& setNewCompartmentInExistingZone(JSObject* obj); + RealmCreationOptions& setNewCompartmentAndZone(); + RealmCreationOptions& setExistingCompartment(JSObject* obj); + RealmCreationOptions& setExistingCompartment(Compartment* compartment); + + // Certain compartments are implementation details of the embedding, and + // references to them should never leak out to script. This flag causes this + // realm to skip firing onNewGlobalObject and makes addDebuggee a no-op for + // this global. + // + // Debugger visibility is per-compartment, not per-realm (it's only practical + // to enforce visibility on compartment boundaries), so if a realm is being + // created in an extant compartment, its requested visibility must match that + // of the compartment. + bool invisibleToDebugger() const { return invisibleToDebugger_; } + RealmCreationOptions& setInvisibleToDebugger(bool flag) { + invisibleToDebugger_ = flag; + return *this; + } + + // Determines whether this realm should preserve JIT code on non-shrinking + // GCs. + bool preserveJitCode() const { return preserveJitCode_; } + RealmCreationOptions& setPreserveJitCode(bool flag) { + preserveJitCode_ = flag; + return *this; + } + + // Determines whether 1) the global Atomic property is defined and atomic + // operations are supported, and 2) whether shared-memory operations are + // supported. + bool getSharedMemoryAndAtomicsEnabled() const; + RealmCreationOptions& setSharedMemoryAndAtomicsEnabled(bool flag); + + // Determines (if getSharedMemoryAndAtomicsEnabled() is true) whether the + // global SharedArrayBuffer property is defined. If the property is not + // defined, shared array buffer functionality can only be invoked if the + // host/embedding specifically acts to expose it. + // + // This option defaults to true: embeddings unable to tolerate a global + // SharedAraryBuffer property must opt out of it. + bool defineSharedArrayBufferConstructor() const { + return defineSharedArrayBufferConstructor_; + } + RealmCreationOptions& setDefineSharedArrayBufferConstructor(bool flag) { + defineSharedArrayBufferConstructor_ = flag; + return *this; + } + + // Structured clone operations support the cloning of shared memory objects + // (SharedArrayBuffer or or a shared WASM Memory object) *optionally* -- at + // the discretion of the embedder code that performs the cloning. When a + // structured clone operation encounters a shared memory object and cloning + // shared memory objects has not been enabled, the clone fails and an + // error is thrown. + // + // In the web embedding context, shared memory object cloning is disabled + // either because + // + // 1) *no* way of supporting it is available (because the + // Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy HTTP + // headers are not respected to force the page into its own process), or + // 2) the aforementioned HTTP headers don't specify that the page should be + // opened in its own process. + // + // These two scenarios demand different error messages, and this option can be + // used to specify which scenario is in play. + // + // In the former case, if COOP/COEP support is not enabled, set this option to + // false. (This is the default.) + // + // In the latter case, if COOP/COEP weren't used to force this page into its + // own process, set this option to true. + // + // (Embeddings that are not the web and do not wish to support structured + // cloning of shared memory objects will get a "bad" web-centric error message + // no matter what. At present, SpiderMonkey does not offer a way for such + // embeddings to use an embedding-specific error message.) + bool getCoopAndCoepEnabled() const; + RealmCreationOptions& setCoopAndCoepEnabled(bool flag); + + WeakRefSpecifier getWeakRefsEnabled() const { return weakRefs_; } + RealmCreationOptions& setWeakRefsEnabled(WeakRefSpecifier spec) { + weakRefs_ = spec; + return *this; + } + + bool getToSourceEnabled() const { return toSource_; } + RealmCreationOptions& setToSourceEnabled(bool flag) { + toSource_ = flag; + return *this; + } + + bool getPropertyErrorMessageFixEnabled() const { + return propertyErrorMessageFix_; + } + RealmCreationOptions& setPropertyErrorMessageFixEnabled(bool flag) { + propertyErrorMessageFix_ = flag; + return *this; + } + + bool getIteratorHelpersEnabled() const { return iteratorHelpers_; } + RealmCreationOptions& setIteratorHelpersEnabled(bool flag) { + iteratorHelpers_ = flag; + return *this; + } + + bool getShadowRealmsEnabled() const { return shadowRealms_; } + RealmCreationOptions& setShadowRealmsEnabled(bool flag) { + shadowRealms_ = flag; + return *this; + } + +#ifdef NIGHTLY_BUILD + bool getArrayGroupingEnabled() const { return arrayGrouping_; } + RealmCreationOptions& setArrayGroupingEnabled(bool flag) { + arrayGrouping_ = flag; + return *this; + } + + bool getWellFormedUnicodeStringsEnabled() const { + return wellFormedUnicodeStrings_; + } + RealmCreationOptions& setWellFormedUnicodeStringsEnabled(bool flag) { + wellFormedUnicodeStrings_ = flag; + return *this; + } +#endif + + bool getArrayFromAsyncEnabled() const { return arrayFromAsync_; } + RealmCreationOptions& setArrayFromAsyncEnabled(bool flag) { + arrayFromAsync_ = flag; + return *this; + } + + bool getChangeArrayByCopyEnabled() const { return changeArrayByCopy_; } + RealmCreationOptions& setChangeArrayByCopyEnabled(bool flag) { + changeArrayByCopy_ = flag; + return *this; + } + +#ifdef ENABLE_NEW_SET_METHODS + bool getNewSetMethodsEnabled() const { return newSetMethods_; } + RealmCreationOptions& setNewSetMethodsEnabled(bool flag) { + newSetMethods_ = flag; + return *this; + } +#endif + + // This flag doesn't affect JS engine behavior. It is used by Gecko to + // mark whether content windows and workers are "Secure Context"s. See + // https://w3c.github.io/webappsec-secure-contexts/ + // https://bugzilla.mozilla.org/show_bug.cgi?id=1162772#c34 + bool secureContext() const { return secureContext_; } + RealmCreationOptions& setSecureContext(bool flag) { + secureContext_ = flag; + return *this; + } + + // Non-standard option to freeze certain builtin constructors and seal their + // prototypes. Also defines these constructors on the global as non-writable + // and non-configurable. + bool freezeBuiltins() const { return freezeBuiltins_; } + RealmCreationOptions& setFreezeBuiltins(bool flag) { + freezeBuiltins_ = flag; + return *this; + } + + uint64_t profilerRealmID() const { return profilerRealmID_; } + RealmCreationOptions& setProfilerRealmID(uint64_t id) { + profilerRealmID_ = id; + return *this; + } + + private: + JSTraceOp traceGlobal_ = nullptr; + CompartmentSpecifier compSpec_ = CompartmentSpecifier::NewCompartmentAndZone; + union { + Compartment* comp_; + Zone* zone_; + }; + uint64_t profilerRealmID_ = 0; + WeakRefSpecifier weakRefs_ = WeakRefSpecifier::Disabled; + bool invisibleToDebugger_ = false; + bool preserveJitCode_ = false; + bool sharedMemoryAndAtomics_ = false; + bool defineSharedArrayBufferConstructor_ = true; + bool coopAndCoep_ = false; + bool toSource_ = false; + bool propertyErrorMessageFix_ = false; + bool iteratorHelpers_ = false; + bool shadowRealms_ = false; +#ifdef NIGHTLY_BUILD + bool arrayGrouping_ = false; + // Pref for String.prototype.{is,to}WellFormed() methods. + bool wellFormedUnicodeStrings_ = false; +#endif + bool arrayFromAsync_ = true; + bool changeArrayByCopy_ = false; +#ifdef ENABLE_NEW_SET_METHODS + bool newSetMethods_ = false; +#endif + bool secureContext_ = false; + bool freezeBuiltins_ = false; +}; + +/** + * RealmBehaviors specifies behaviors of a realm that can be changed after the + * realm's been created. + */ +class JS_PUBLIC_API RealmBehaviors { + public: + RealmBehaviors() = default; + + // For certain globals, we know enough about the code that will run in them + // that we can discard script source entirely. + bool discardSource() const { return discardSource_; } + RealmBehaviors& setDiscardSource(bool flag) { + discardSource_ = flag; + return *this; + } + + bool clampAndJitterTime() const { return clampAndJitterTime_; } + RealmBehaviors& setClampAndJitterTime(bool flag) { + clampAndJitterTime_ = flag; + return *this; + } + + bool shouldResistFingerprinting() const { + return shouldResistFingerprinting_; + } + RealmBehaviors& setShouldResistFingerprinting(bool flag) { + shouldResistFingerprinting_ = flag; + return *this; + } + + class Override { + public: + Override() : mode_(Default) {} + + bool get(bool defaultValue) const { + if (mode_ == Default) { + return defaultValue; + } + return mode_ == ForceTrue; + } + + void set(bool overrideValue) { + mode_ = overrideValue ? ForceTrue : ForceFalse; + } + + void reset() { mode_ = Default; } + + private: + enum Mode { Default, ForceTrue, ForceFalse }; + + Mode mode_; + }; + + // A Realm can stop being "live" in all the ways that matter before its global + // is actually GCed. Consumers that tear down parts of a Realm or its global + // before that point should set isNonLive accordingly. + bool isNonLive() const { return isNonLive_; } + RealmBehaviors& setNonLive() { + isNonLive_ = true; + return *this; + } + + private: + bool discardSource_ = false; + bool clampAndJitterTime_ = true; + bool shouldResistFingerprinting_ = false; + bool isNonLive_ = false; +}; + +/** + * RealmOptions specifies realm characteristics: both those that can't be + * changed on a realm once it's been created (RealmCreationOptions), and those + * that can be changed on an existing realm (RealmBehaviors). + */ +class JS_PUBLIC_API RealmOptions { + public: + explicit RealmOptions() : creationOptions_(), behaviors_() {} + + RealmOptions(const RealmCreationOptions& realmCreation, + const RealmBehaviors& realmBehaviors) + : creationOptions_(realmCreation), behaviors_(realmBehaviors) {} + + // RealmCreationOptions specify fundamental realm characteristics that must + // be specified when the realm is created, that can't be changed after the + // realm is created. + RealmCreationOptions& creationOptions() { return creationOptions_; } + const RealmCreationOptions& creationOptions() const { + return creationOptions_; + } + + // RealmBehaviors specify realm characteristics that can be changed after + // the realm is created. + RealmBehaviors& behaviors() { return behaviors_; } + const RealmBehaviors& behaviors() const { return behaviors_; } + + private: + RealmCreationOptions creationOptions_; + RealmBehaviors behaviors_; +}; + +extern JS_PUBLIC_API const RealmCreationOptions& RealmCreationOptionsRef( + Realm* realm); + +extern JS_PUBLIC_API const RealmCreationOptions& RealmCreationOptionsRef( + JSContext* cx); + +extern JS_PUBLIC_API const RealmBehaviors& RealmBehaviorsRef(Realm* realm); + +extern JS_PUBLIC_API const RealmBehaviors& RealmBehaviorsRef(JSContext* cx); + +extern JS_PUBLIC_API void SetRealmNonLive(Realm* realm); + +} // namespace JS + +#endif // js_RealmOptions_h diff --git a/js/public/RefCounted.h b/js/public/RefCounted.h new file mode 100644 index 0000000000..de5a72f2d8 --- /dev/null +++ b/js/public/RefCounted.h @@ -0,0 +1,96 @@ +/* -*- 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 js_RefCounted_h +#define js_RefCounted_h + +#include "mozilla/Atomics.h" +#include "mozilla/RefCountType.h" + +#include "js/Utility.h" + +// These types implement the same interface as mozilla::(Atomic)RefCounted and +// must be used instead of mozilla::(Atomic)RefCounted for everything in +// SpiderMonkey. There are two reasons: +// - Release() needs to call js_delete, not delete +// - SpiderMonkey does not have MOZILLA_INTERNAL_API defined which can lead +// to ODR violations that show up as spurious leak reports when ref-counted +// types are allocated by SpiderMonkey and released by Gecko (or vice versa). + +namespace js { + +template <typename T> +class RefCounted { + static const MozRefCountType DEAD = 0xffffdead; + + protected: + RefCounted() : mRefCnt(0) {} + ~RefCounted() { MOZ_ASSERT(mRefCnt == DEAD); } + + public: + void AddRef() const { + MOZ_ASSERT(int32_t(mRefCnt) >= 0); + ++mRefCnt; + } + + void Release() const { + MOZ_ASSERT(int32_t(mRefCnt) > 0); + MozRefCountType cnt = --mRefCnt; + if (0 == cnt) { +#ifdef DEBUG + mRefCnt = DEAD; +#endif + js_delete(const_cast<T*>(static_cast<const T*>(this))); + } + } + + private: + mutable MozRefCountType mRefCnt; +}; + +template <typename T> +class AtomicRefCounted { + // On 64-bit systems, if the refcount type is small (say, 32 bits), there's + // a risk that it could overflow. So require it to be large enough. + + static_assert(sizeof(MozRefCountType) == sizeof(uintptr_t), + "You're at risk for ref count overflow."); + + static const MozRefCountType DEAD = ~MozRefCountType(0xffff) | 0xdead; + + protected: + AtomicRefCounted() = default; + ~AtomicRefCounted() { MOZ_ASSERT(mRefCnt == DEAD); } + + public: + void AddRef() const { + ++mRefCnt; + MOZ_ASSERT(mRefCnt != DEAD); + } + + void Release() const { + MOZ_ASSERT(mRefCnt != 0); + MozRefCountType cnt = --mRefCnt; + if (0 == cnt) { +#ifdef DEBUG + mRefCnt = DEAD; +#endif + js_delete(const_cast<T*>(static_cast<const T*>(this))); + } + } + + bool hasOneRef() const { + MOZ_ASSERT(mRefCnt > 0); + return mRefCnt == 1; + } + + private: + mutable mozilla::Atomic<MozRefCountType> mRefCnt{0}; +}; + +} // namespace js + +#endif /* js_RefCounted_h */ diff --git a/js/public/RegExp.h b/js/public/RegExp.h new file mode 100644 index 0000000000..eda8cdb6fa --- /dev/null +++ b/js/public/RegExp.h @@ -0,0 +1,106 @@ +/* -*- 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/. */ + +/* Regular expression-related operations. */ + +#ifndef js_RegExp_h +#define js_RegExp_h + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/RegExpFlags.h" // JS::RegExpFlags +#include "js/TypeDecls.h" + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSString; + +namespace JS { + +/** + * Create a new RegExp for the given Latin-1-encoded bytes and flags. + */ +extern JS_PUBLIC_API JSObject* NewRegExpObject(JSContext* cx, const char* bytes, + size_t length, + RegExpFlags flags); + +/** + * Create a new RegExp for the given source and flags. + */ +extern JS_PUBLIC_API JSObject* NewUCRegExpObject(JSContext* cx, + const char16_t* chars, + size_t length, + RegExpFlags flags); + +extern JS_PUBLIC_API bool SetRegExpInput(JSContext* cx, Handle<JSObject*> obj, + Handle<JSString*> input); + +extern JS_PUBLIC_API bool ClearRegExpStatics(JSContext* cx, + Handle<JSObject*> obj); + +/** + * Execute a regexp on a given input, starting from |indexp|. + * Returns false on OOM or over-recursion. + * + * On no match, |rval| is set to Null. + * On a match, |indexp| and the RegExp statics are updated. + * Then, if |test| is true, |rval| is set to true. + * Otherwise, |rval| is set to a match result object. + */ +extern JS_PUBLIC_API bool ExecuteRegExp(JSContext* cx, Handle<JSObject*> obj, + Handle<JSObject*> reobj, + const char16_t* chars, size_t length, + size_t* indexp, bool test, + MutableHandle<Value> rval); + +/** + * Execute a regexp on a given input, starting from |indexp|. + * This is the same as ExecuteRegExp, except it does not update the RegExp + * statics and can be called without a global object. + */ +extern JS_PUBLIC_API bool ExecuteRegExpNoStatics( + JSContext* cx, Handle<JSObject*> reobj, const char16_t* chars, + size_t length, size_t* indexp, bool test, MutableHandle<Value> rval); + +/** + * On success, returns true, setting |*isRegExp| to true if |obj| is a RegExp + * object or a wrapper around one, or to false if not. Returns false on + * failure. + * + * This method returns true with |*isRegExp == false| when passed an ES6 proxy + * whose target is a RegExp, or when passed a revoked proxy. + */ +extern JS_PUBLIC_API bool ObjectIsRegExp(JSContext* cx, Handle<JSObject*> obj, + bool* isRegExp); + +/** + * Given a RegExp object (or a wrapper around one), return the set of all + * JS::RegExpFlag::* for it. + */ +extern JS_PUBLIC_API RegExpFlags GetRegExpFlags(JSContext* cx, + Handle<JSObject*> obj); + +/** + * Return the source text for a RegExp object (or a wrapper around one), or null + * on failure. + */ +extern JS_PUBLIC_API JSString* GetRegExpSource(JSContext* cx, + Handle<JSObject*> obj); +/** + * Check whether the given source is a valid regexp. If the regexp parses + * successfully, returns true and sets |error| to undefined. If the regexp + * has a syntax error, returns true, sets |error| to that error object, and + * clears the exception. Returns false on OOM or over-recursion. + */ +extern JS_PUBLIC_API bool CheckRegExpSyntax(JSContext* cx, + const char16_t* chars, + size_t length, RegExpFlags flags, + MutableHandle<Value> error); + +} // namespace JS + +#endif // js_RegExp_h diff --git a/js/public/RegExpFlags.h b/js/public/RegExpFlags.h new file mode 100644 index 0000000000..4fa36031b0 --- /dev/null +++ b/js/public/RegExpFlags.h @@ -0,0 +1,158 @@ +/* -*- 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/. */ + +/* Regular expression flags. */ + +#ifndef js_RegExpFlags_h +#define js_RegExpFlags_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_IMPLICIT + +#include <stdint.h> // uint8_t + +namespace JS { + +/** + * Regular expression flag values, suitable for initializing a collection of + * regular expression flags as defined below in |RegExpFlags|. Flags are listed + * in alphabetical order by syntax -- /d, /g, /i, /m, /s, /u, /y. + */ +class RegExpFlag { + // WARNING TO SPIDERMONKEY HACKERS (embedders must assume these values can + // change): + // + // Flag-bit values appear in XDR and structured clone data formats, so none of + // these values can be changed (including to assign values in numerically + // ascending order) unless you also add a translation layer. + + public: + /** + * Add .indices property to the match result, i.e. /d + */ + static constexpr uint8_t HasIndices = 0b100'0000; + + /** + * Act globally and find *all* matches (rather than stopping after just the + * first one), i.e. /g. + */ + static constexpr uint8_t Global = 0b000'0010; + + /** + * Interpret regular expression source text case-insensitively by folding + * uppercase letters to lowercase, i.e. /i. + */ + static constexpr uint8_t IgnoreCase = 0b000'0001; + + /** Treat ^ and $ as begin and end of line, i.e. /m. */ + static constexpr uint8_t Multiline = 0b000'0100; + + /* Allow . to match newline characters, i.e. /s. */ + static constexpr uint8_t DotAll = 0b010'0000; + + /** Use Unicode semantics, i.e. /u. */ + static constexpr uint8_t Unicode = 0b001'0000; + + /** Only match starting from <regular expression>.lastIndex, i.e. /y. */ + static constexpr uint8_t Sticky = 0b000'1000; + + /** No regular expression flags. */ + static constexpr uint8_t NoFlags = 0b000'0000; + + /** All regular expression flags. */ + static constexpr uint8_t AllFlags = 0b111'1111; +}; + +/** + * A collection of regular expression flags. Individual flag values may be + * combined into a collection using bitwise operators. + */ +class RegExpFlags { + public: + using Flag = uint8_t; + + private: + Flag flags_; + + public: + RegExpFlags() = default; + + MOZ_IMPLICIT RegExpFlags(Flag flags) : flags_(flags) { + MOZ_ASSERT((flags & RegExpFlag::AllFlags) == flags, + "flags must not contain unrecognized flags"); + } + + RegExpFlags(const RegExpFlags&) = default; + + bool operator==(const RegExpFlags& other) const { + return flags_ == other.flags_; + } + + bool operator!=(const RegExpFlags& other) const { return !(*this == other); } + + RegExpFlags& operator&=(const RegExpFlags& rhs) { + flags_ &= rhs.flags_; + return *this; + } + + RegExpFlags& operator|=(const RegExpFlags& rhs) { + flags_ |= rhs.flags_; + return *this; + } + + RegExpFlags operator&(Flag flag) const { return RegExpFlags(flags_ & flag); } + + RegExpFlags operator|(Flag flag) const { return RegExpFlags(flags_ | flag); } + + RegExpFlags operator^(Flag flag) const { return RegExpFlags(flags_ ^ flag); } + + RegExpFlags operator~() const { + return RegExpFlags(~flags_ & RegExpFlag::AllFlags); + } + + bool hasIndices() const { return flags_ & RegExpFlag::HasIndices; } + bool global() const { return flags_ & RegExpFlag::Global; } + bool ignoreCase() const { return flags_ & RegExpFlag::IgnoreCase; } + bool multiline() const { return flags_ & RegExpFlag::Multiline; } + bool dotAll() const { return flags_ & RegExpFlag::DotAll; } + bool unicode() const { return flags_ & RegExpFlag::Unicode; } + bool sticky() const { return flags_ & RegExpFlag::Sticky; } + + explicit operator bool() const { return flags_ != 0; } + + Flag value() const { return flags_; } +}; + +inline RegExpFlags& operator&=(RegExpFlags& flags, RegExpFlags::Flag flag) { + flags = flags & flag; + return flags; +} + +inline RegExpFlags& operator|=(RegExpFlags& flags, RegExpFlags::Flag flag) { + flags = flags | flag; + return flags; +} + +inline RegExpFlags& operator^=(RegExpFlags& flags, RegExpFlags::Flag flag) { + flags = flags ^ flag; + return flags; +} + +inline RegExpFlags operator&(const RegExpFlags& lhs, const RegExpFlags& rhs) { + RegExpFlags result = lhs; + result &= rhs; + return lhs; +} + +inline RegExpFlags operator|(const RegExpFlags& lhs, const RegExpFlags& rhs) { + RegExpFlags result = lhs; + result |= rhs; + return result; +} + +} // namespace JS + +#endif // js_RegExpFlags_h diff --git a/js/public/Result.h b/js/public/Result.h new file mode 100644 index 0000000000..cfd68a927c --- /dev/null +++ b/js/public/Result.h @@ -0,0 +1,279 @@ +/* -*- 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/. */ + +/* + * [SMDOC] JS::Result + * + * `Result` is used as the return type of many SpiderMonkey functions that + * can either succeed or fail. See "/mfbt/Result.h". + * + * + * ## Which return type to use + * + * `Result` is for return values. Obviously, if you're writing a function that + * can't fail, don't use Result. Otherwise: + * + * JS::Result<> - function can fail, doesn't return anything on success + * (defaults to `JS::Result<JS::Ok, JS::Error>`) + * JS::Result<JS::Ok, JS::OOM> - like JS::Result<>, but fails only on OOM + * + * JS::Result<Data> - function can fail, returns Data on success + * JS::Result<Data, JS::OOM> - returns Data, fails only on OOM + * + * mozilla::GenericErrorResult<JS::Error> - always fails + * + * That last type is like a Result with no success type. It's used for + * functions like `js::ReportNotFunction` that always return an error + * result. `GenericErrorResult<E>` implicitly converts to `Result<V, E>`, + * regardless of V. + * + * + * ## Checking Results when your return type is Result + * + * When you call a function that returns a `Result`, use the `MOZ_TRY` macro to + * check for errors: + * + * MOZ_TRY(DefenestrateObject(cx, obj)); + * + * If `DefenestrateObject` returns a success result, `MOZ_TRY` is done, and + * control flows to the next statement. If `DefenestrateObject` returns an + * error result, `MOZ_TRY` will immediately return it, propagating the error to + * your caller. It's kind of like exceptions, but more explicit -- you can see + * in the code exactly where errors can happen. + * + * You can do a tail call instead of using `MOZ_TRY`: + * + * return DefenestrateObject(cx, obj); + * + * Indicate success with `return Ok();`. + * + * If the function returns a value on success, use `MOZ_TRY_VAR` to get it: + * + * RootedValue thrug(cx); + * MOZ_TRY_VAR(thrug, GetObjectThrug(cx, obj)); + * + * This behaves the same as `MOZ_TRY` on error. On success, the success + * value of `GetObjectThrug(cx, obj)` is assigned to the variable `thrug`. + * + * + * ## Checking Results when your return type is not Result + * + * This header defines alternatives to MOZ_TRY and MOZ_TRY_VAR for when you + * need to call a `Result` function from a function that uses false or nullptr + * to indicate errors: + * + * JS_TRY_OR_RETURN_FALSE(cx, DefenestrateObject(cx, obj)); + * JS_TRY_VAR_OR_RETURN_FALSE(cx, v, GetObjectThrug(cx, obj)); + * + * JS_TRY_OR_RETURN_NULL(cx, DefenestrateObject(cx, obj)); + * JS_TRY_VAR_OR_RETURN_NULL(cx, v, GetObjectThrug(cx, obj)); + * + * When TRY is not what you want, because you need to do some cleanup or + * recovery on error, use this idiom: + * + * if (!cx->resultToBool(expr_that_is_a_Result)) { + * ... your recovery code here ... + * } + * + * In place of a tail call, you can use one of these methods: + * + * return cx->resultToBool(expr); // false on error + * return cx->resultToPtr(expr); // null on error + * + * Once we are using `Result` everywhere, including in public APIs, all of + * these will go away. + * + * + * ## GC safety + * + * When a function returns a `JS::Result<JSObject*>`, it is the program's + * responsibility to check for errors and root the object before continuing: + * + * RootedObject wrapper(cx); + * MOZ_TRY_VAR(wrapper, Enwrapify(cx, thing)); + * + * This is ideal. On error, there is no object to root; on success, the + * assignment to wrapper roots it. GC safety is ensured. + * + * `Result` has methods .isOk(), .isErr(), .unwrap(), and .unwrapErr(), but if + * you're actually using them, it's possible to create a GC hazard. The static + * analysis will catch it if so, but that's hardly convenient. So try to stick + * to the idioms shown above. + * + * + * ## Future directions + * + * At present, JS::Error and JS::OOM are empty structs. The plan is to make them + * GC things that contain the actual error information (including the exception + * value and a saved stack). + * + * The long-term plan is to remove JS_IsExceptionPending and + * JS_GetPendingException in favor of JS::Error. Exception state will no longer + * exist. + */ + +#ifndef js_Result_h +#define js_Result_h + +#include "mozilla/Result.h" + +/** + * Evaluate the boolean expression expr. If it's true, do nothing. + * If it's false, return an error result. + */ +#define JS_TRY_BOOL_TO_RESULT(cx, expr) \ + do { \ + bool ok_ = (expr); \ + if (!ok_) return (cx)->boolToResult(ok_); \ + } while (0) + +/** + * JS_TRY_OR_RETURN_FALSE(cx, expr) runs expr to compute a Result value. + * On success, nothing happens; on error, it returns false immediately. + * + * Implementation note: this involves cx because this may eventually + * do the work of setting a pending exception or reporting OOM. + */ +#define JS_TRY_OR_RETURN_FALSE(cx, expr) \ + do { \ + auto tmpResult_ = (expr); \ + if (tmpResult_.isErr()) return (cx)->resultToBool(tmpResult_); \ + } while (0) + +/** + * Like JS_TRY_OR_RETURN_FALSE, but returning nullptr on error, + * rather than false. + */ +#define JS_TRY_OR_RETURN_NULL(cx, expr) \ + do { \ + auto tmpResult_ = (expr); \ + if (tmpResult_.isErr()) { \ + MOZ_ALWAYS_FALSE((cx)->resultToBool(tmpResult_)); \ + return nullptr; \ + } \ + } while (0) + +#define JS_TRY_VAR_OR_RETURN_FALSE(cx, target, expr) \ + do { \ + auto tmpResult_ = (expr); \ + if (tmpResult_.isErr()) return (cx)->resultToBool(tmpResult_); \ + (target) = tmpResult_.unwrap(); \ + } while (0) + +#define JS_TRY_VAR_OR_RETURN_NULL(cx, target, expr) \ + do { \ + auto tmpResult_ = (expr); \ + if (tmpResult_.isErr()) { \ + MOZ_ALWAYS_FALSE((cx)->resultToBool(tmpResult_)); \ + return nullptr; \ + } \ + (target) = tmpResult_.unwrap(); \ + } while (0) + +namespace JS { + +using mozilla::Ok; + +template <typename T> +struct UnusedZero; + +/** + * Type representing a JS error or exception. At the moment this only + * "represents" an error in a rather abstract way. + */ +struct Error { + // Since we claim UnusedZero<Error>::value and HasFreeLSB<Error>::value == + // true below, we must only use positive even enum values. + enum class ErrorKind : uintptr_t { Unspecified = 2, OOM = 4 }; + + const ErrorKind kind = ErrorKind::Unspecified; + + Error() = default; + + protected: + friend struct UnusedZero<Error>; + + constexpr MOZ_IMPLICIT Error(ErrorKind aKind) : kind(aKind) {} +}; + +struct OOM : Error { + constexpr OOM() : Error(ErrorKind::OOM) {} + + protected: + friend struct UnusedZero<OOM>; + + using Error::Error; +}; + +template <typename T> +struct UnusedZero { + using StorageType = std::underlying_type_t<Error::ErrorKind>; + + static constexpr bool value = true; + static constexpr StorageType nullValue = 0; + + static constexpr void AssertValid(StorageType aValue) {} + static constexpr T Inspect(const StorageType& aValue) { + return static_cast<Error::ErrorKind>(aValue); + } + static constexpr T Unwrap(StorageType aValue) { + return static_cast<Error::ErrorKind>(aValue); + } + static constexpr StorageType Store(T aValue) { + return static_cast<StorageType>(aValue.kind); + } +}; + +} // namespace JS + +namespace mozilla::detail { + +template <> +struct UnusedZero<JS::Error> : JS::UnusedZero<JS::Error> {}; + +template <> +struct UnusedZero<JS::OOM> : JS::UnusedZero<JS::OOM> {}; + +template <> +struct HasFreeLSB<JS::Error> { + static const bool value = true; +}; + +template <> +struct HasFreeLSB<JS::OOM> { + static const bool value = true; +}; +} // namespace mozilla::detail + +namespace JS { + +/** + * `Result` is intended to be the return type of JSAPI calls and internal + * functions that can run JS code or allocate memory from the JS GC heap. Such + * functions can: + * + * - succeed, possibly returning a value; + * + * - fail with a JS exception (out-of-memory falls in this category); or + * + * - fail because JS execution was terminated, which occurs when e.g. a + * user kills a script from the "slow script" UI. This is also how we + * unwind the stack when the debugger forces the current function to + * return. JS `catch` blocks can't catch this kind of failure, + * and JS `finally` blocks don't execute. + */ +template <typename V = Ok, typename E = Error> +using Result = mozilla::Result<V, E>; + +static_assert(sizeof(Result<>) == sizeof(uintptr_t), + "Result<> should be pointer-sized"); + +static_assert(sizeof(Result<int*, Error>) == sizeof(uintptr_t), + "Result<V*, Error> should be pointer-sized"); + +} // namespace JS + +#endif // js_Result_h diff --git a/js/public/RootingAPI.h b/js/public/RootingAPI.h new file mode 100644 index 0000000000..27d043c193 --- /dev/null +++ b/js/public/RootingAPI.h @@ -0,0 +1,1599 @@ +/* -*- 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 js_RootingAPI_h +#define js_RootingAPI_h + +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/EnumeratedArray.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Maybe.h" + +#include <type_traits> +#include <utility> + +#include "jspubtd.h" + +#include "js/ComparisonOperators.h" // JS::detail::DefineComparisonOps +#include "js/GCAnnotations.h" +#include "js/GCPolicyAPI.h" +#include "js/GCTypeMacros.h" // JS_FOR_EACH_PUBLIC_{,TAGGED_}GC_POINTER_TYPE +#include "js/HashTable.h" +#include "js/HeapAPI.h" // StackKindCount +#include "js/ProfilingStack.h" +#include "js/Realm.h" +#include "js/Stack.h" // JS::NativeStackLimit +#include "js/TypeDecls.h" +#include "js/UniquePtr.h" + +/* + * [SMDOC] Stack Rooting + * + * Moving GC Stack Rooting + * + * A moving GC may change the physical location of GC allocated things, even + * when they are rooted, updating all pointers to the thing to refer to its new + * location. The GC must therefore know about all live pointers to a thing, + * not just one of them, in order to behave correctly. + * + * The |Rooted| and |Handle| classes below are used to root stack locations + * whose value may be held live across a call that can trigger GC. For a + * code fragment such as: + * + * JSObject* obj = NewObject(cx); + * DoSomething(cx); + * ... = obj->lastProperty(); + * + * If |DoSomething()| can trigger a GC, the stack location of |obj| must be + * rooted to ensure that the GC does not move the JSObject referred to by + * |obj| without updating |obj|'s location itself. This rooting must happen + * regardless of whether there are other roots which ensure that the object + * itself will not be collected. + * + * If |DoSomething()| cannot trigger a GC, and the same holds for all other + * calls made between |obj|'s definitions and its last uses, then no rooting + * is required. + * + * SpiderMonkey can trigger a GC at almost any time and in ways that are not + * always clear. For example, the following innocuous-looking actions can + * cause a GC: allocation of any new GC thing; JSObject::hasProperty; + * JS_ReportError and friends; and ToNumber, among many others. The following + * dangerous-looking actions cannot trigger a GC: js_malloc, cx->malloc_, + * rt->malloc_, and friends and JS_ReportOutOfMemory. + * + * The following family of three classes will exactly root a stack location. + * Incorrect usage of these classes will result in a compile error in almost + * all cases. Therefore, it is very hard to be incorrectly rooted if you use + * these classes exclusively. These classes are all templated on the type T of + * the value being rooted. + * + * - Rooted<T> declares a variable of type T, whose value is always rooted. + * Rooted<T> may be automatically coerced to a Handle<T>, below. Rooted<T> + * should be used whenever a local variable's value may be held live across a + * call which can trigger a GC. + * + * - Handle<T> is a const reference to a Rooted<T>. Functions which take GC + * things or values as arguments and need to root those arguments should + * generally use handles for those arguments and avoid any explicit rooting. + * This has two benefits. First, when several such functions call each other + * then redundant rooting of multiple copies of the GC thing can be avoided. + * Second, if the caller does not pass a rooted value a compile error will be + * generated, which is quicker and easier to fix than when relying on a + * separate rooting analysis. + * + * - MutableHandle<T> is a non-const reference to Rooted<T>. It is used in the + * same way as Handle<T> and includes a |set(const T& v)| method to allow + * updating the value of the referenced Rooted<T>. A MutableHandle<T> can be + * created with an implicit cast from a Rooted<T>*. + * + * In some cases the small performance overhead of exact rooting (measured to + * be a few nanoseconds on desktop) is too much. In these cases, try the + * following: + * + * - Move all Rooted<T> above inner loops: this allows you to re-use the root + * on each iteration of the loop. + * + * - Pass Handle<T> through your hot call stack to avoid re-rooting costs at + * every invocation. + * + * The following diagram explains the list of supported, implicit type + * conversions between classes of this family: + * + * Rooted<T> ----> Handle<T> + * | ^ + * | | + * | | + * +---> MutableHandle<T> + * (via &) + * + * All of these types have an implicit conversion to raw pointers. + */ + +namespace js { + +// The defaulted Enable parameter for the following two types is for restricting +// specializations with std::enable_if. +template <typename T, typename Enable = void> +struct BarrierMethods {}; + +template <typename Element, typename Wrapper, typename Enable = void> +class WrappedPtrOperations {}; + +template <typename Element, typename Wrapper> +class MutableWrappedPtrOperations + : public WrappedPtrOperations<Element, Wrapper> {}; + +template <typename T, typename Wrapper> +class RootedOperations : public MutableWrappedPtrOperations<T, Wrapper> {}; + +template <typename T, typename Wrapper> +class HandleOperations : public WrappedPtrOperations<T, Wrapper> {}; + +template <typename T, typename Wrapper> +class MutableHandleOperations : public MutableWrappedPtrOperations<T, Wrapper> { +}; + +template <typename T, typename Wrapper> +class HeapOperations : public MutableWrappedPtrOperations<T, Wrapper> {}; + +// Cannot use FOR_EACH_HEAP_ABLE_GC_POINTER_TYPE, as this would import too many +// macros into scope + +// Add a 2nd template parameter to allow conditionally enabling partial +// specializations via std::enable_if. +template <typename T, typename Enable = void> +struct IsHeapConstructibleType : public std::false_type {}; + +#define JS_DECLARE_IS_HEAP_CONSTRUCTIBLE_TYPE(T) \ + template <> \ + struct IsHeapConstructibleType<T> : public std::true_type {}; +JS_FOR_EACH_PUBLIC_GC_POINTER_TYPE(JS_DECLARE_IS_HEAP_CONSTRUCTIBLE_TYPE) +JS_FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(JS_DECLARE_IS_HEAP_CONSTRUCTIBLE_TYPE) +// Note that JS_DECLARE_IS_HEAP_CONSTRUCTIBLE_TYPE is left defined, to allow +// declaring other types (eg from js/public/experimental/TypedData.h) to +// be used with Heap<>. + +namespace gc { +struct Cell; +} /* namespace gc */ + +// Important: Return a reference so passing a Rooted<T>, etc. to +// something that takes a |const T&| is not a GC hazard. +#define DECLARE_POINTER_CONSTREF_OPS(T) \ + operator const T&() const { return get(); } \ + const T& operator->() const { return get(); } + +// Assignment operators on a base class are hidden by the implicitly defined +// operator= on the derived class. Thus, define the operator= directly on the +// class as we would need to manually pass it through anyway. +#define DECLARE_POINTER_ASSIGN_OPS(Wrapper, T) \ + Wrapper<T>& operator=(const T& p) { \ + set(p); \ + return *this; \ + } \ + Wrapper<T>& operator=(T&& p) { \ + set(std::move(p)); \ + return *this; \ + } \ + Wrapper<T>& operator=(const Wrapper<T>& other) { \ + set(other.get()); \ + return *this; \ + } + +#define DELETE_ASSIGNMENT_OPS(Wrapper, T) \ + template <typename S> \ + Wrapper<T>& operator=(S) = delete; \ + Wrapper<T>& operator=(const Wrapper<T>&) = delete; + +#define DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr) \ + const T* address() const { return &(ptr); } \ + const T& get() const { return (ptr); } + +#define DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(ptr) \ + T* address() { return &(ptr); } \ + T& get() { return (ptr); } + +} /* namespace js */ + +namespace JS { + +JS_PUBLIC_API void HeapObjectPostWriteBarrier(JSObject** objp, JSObject* prev, + JSObject* next); +JS_PUBLIC_API void HeapStringPostWriteBarrier(JSString** objp, JSString* prev, + JSString* next); +JS_PUBLIC_API void HeapBigIntPostWriteBarrier(JS::BigInt** bip, + JS::BigInt* prev, + JS::BigInt* next); +JS_PUBLIC_API void HeapObjectWriteBarriers(JSObject** objp, JSObject* prev, + JSObject* next); +JS_PUBLIC_API void HeapStringWriteBarriers(JSString** objp, JSString* prev, + JSString* next); +JS_PUBLIC_API void HeapBigIntWriteBarriers(JS::BigInt** bip, JS::BigInt* prev, + JS::BigInt* next); +JS_PUBLIC_API void HeapScriptWriteBarriers(JSScript** objp, JSScript* prev, + JSScript* next); + +/** + * SafelyInitialized<T>::create() creates a safely-initialized |T|, suitable for + * use as a default value in situations requiring a safe but arbitrary |T| + * value. Implemented as a static method of a struct to allow partial + * specialization for subclasses via the Enable template parameter. + */ +template <typename T, typename Enable = void> +struct SafelyInitialized { + static T create() { + // This function wants to presume that |T()| -- which value-initializes a + // |T| per C++11 [expr.type.conv]p2 -- will produce a safely-initialized, + // safely-usable T that it can return. + +#if defined(XP_WIN) || defined(XP_MACOSX) || \ + (defined(XP_UNIX) && !defined(__clang__)) + + // That presumption holds for pointers, where value initialization produces + // a null pointer. + constexpr bool IsPointer = std::is_pointer_v<T>; + + // For classes and unions we *assume* that if |T|'s default constructor is + // non-trivial it'll initialize correctly. (This is unideal, but C++ + // doesn't offer a type trait indicating whether a class's constructor is + // user-defined, which better approximates our desired semantics.) + constexpr bool IsNonTriviallyDefaultConstructibleClassOrUnion = + (std::is_class_v<T> || + std::is_union_v<T>)&&!std::is_trivially_default_constructible_v<T>; + + static_assert(IsPointer || IsNonTriviallyDefaultConstructibleClassOrUnion, + "T() must evaluate to a safely-initialized T"); + +#endif + + return T(); + } +}; + +#ifdef JS_DEBUG +/** + * For generational GC, assert that an object is in the tenured generation as + * opposed to being in the nursery. + */ +extern JS_PUBLIC_API void AssertGCThingMustBeTenured(JSObject* obj); +extern JS_PUBLIC_API void AssertGCThingIsNotNurseryAllocable( + js::gc::Cell* cell); +#else +inline void AssertGCThingMustBeTenured(JSObject* obj) {} +inline void AssertGCThingIsNotNurseryAllocable(js::gc::Cell* cell) {} +#endif + +/** + * The Heap<T> class is a heap-stored reference to a JS GC thing for use outside + * the JS engine. All members of heap classes that refer to GC things should use + * Heap<T> (or possibly TenuredHeap<T>, described below). + * + * Heap<T> is an abstraction that hides some of the complexity required to + * maintain GC invariants for the contained reference. It uses operator + * overloading to provide a normal pointer interface, but adds barriers to + * notify the GC of changes. + * + * Heap<T> implements the following barriers: + * + * - Post-write barrier (necessary for generational GC). + * - Read barrier (necessary for incremental GC and cycle collector + * integration). + * + * Note Heap<T> does not have a pre-write barrier as used internally in the + * engine. The read barrier is used to mark anything read from a Heap<T> during + * an incremental GC. + * + * Heap<T> may be moved or destroyed outside of GC finalization and hence may be + * used in dynamic storage such as a Vector. + * + * Heap<T> instances must be traced when their containing object is traced to + * keep the pointed-to GC thing alive. + * + * Heap<T> objects should only be used on the heap. GC references stored on the + * C/C++ stack must use Rooted/Handle/MutableHandle instead. + * + * Type T must be a public GC pointer type. + */ +template <typename T> +class MOZ_NON_MEMMOVABLE Heap : public js::HeapOperations<T, Heap<T>> { + // Please note: this can actually also be used by nsXBLMaybeCompiled<T>, for + // legacy reasons. + static_assert(js::IsHeapConstructibleType<T>::value, + "Type T must be a public GC pointer type"); + + public: + using ElementType = T; + + Heap() : ptr(SafelyInitialized<T>::create()) { + // No barriers are required for initialization to the default value. + static_assert(sizeof(T) == sizeof(Heap<T>), + "Heap<T> must be binary compatible with T."); + } + explicit Heap(const T& p) : ptr(p) { + postWriteBarrier(SafelyInitialized<T>::create(), ptr); + } + + /* + * For Heap, move semantics are equivalent to copy semantics. However, we want + * the copy constructor to be explicit, and an explicit move constructor + * breaks common usage of move semantics, so we need to define both, even + * though they are equivalent. + */ + explicit Heap(const Heap<T>& other) : ptr(other.getWithoutExpose()) { + postWriteBarrier(SafelyInitialized<T>::create(), ptr); + } + Heap(Heap<T>&& other) : ptr(other.getWithoutExpose()) { + postWriteBarrier(SafelyInitialized<T>::create(), ptr); + } + + Heap& operator=(Heap<T>&& other) { + set(other.getWithoutExpose()); + other.set(SafelyInitialized<T>::create()); + return *this; + } + + ~Heap() { postWriteBarrier(ptr, SafelyInitialized<T>::create()); } + + DECLARE_POINTER_CONSTREF_OPS(T); + DECLARE_POINTER_ASSIGN_OPS(Heap, T); + + const T* address() const { return &ptr; } + + void exposeToActiveJS() const { js::BarrierMethods<T>::exposeToJS(ptr); } + + const T& get() const { + exposeToActiveJS(); + return ptr; + } + const T& getWithoutExpose() const { + js::BarrierMethods<T>::readBarrier(ptr); + return ptr; + } + const T& unbarrieredGet() const { return ptr; } + + void set(const T& newPtr) { + T tmp = ptr; + ptr = newPtr; + postWriteBarrier(tmp, ptr); + } + + T* unsafeGet() { return &ptr; } + + void unbarrieredSet(const T& newPtr) { ptr = newPtr; } + + explicit operator bool() const { + return bool(js::BarrierMethods<T>::asGCThingOrNull(ptr)); + } + explicit operator bool() { + return bool(js::BarrierMethods<T>::asGCThingOrNull(ptr)); + } + + private: + void postWriteBarrier(const T& prev, const T& next) { + js::BarrierMethods<T>::postWriteBarrier(&ptr, prev, next); + } + + T ptr; +}; + +namespace detail { + +template <typename T> +struct DefineComparisonOps<Heap<T>> : std::true_type { + static const T& get(const Heap<T>& v) { return v.unbarrieredGet(); } +}; + +} // namespace detail + +static MOZ_ALWAYS_INLINE bool ObjectIsTenured(JSObject* obj) { + return !js::gc::IsInsideNursery(reinterpret_cast<js::gc::Cell*>(obj)); +} + +static MOZ_ALWAYS_INLINE bool ObjectIsTenured(const Heap<JSObject*>& obj) { + return ObjectIsTenured(obj.unbarrieredGet()); +} + +static MOZ_ALWAYS_INLINE bool ObjectIsMarkedGray(JSObject* obj) { + auto cell = reinterpret_cast<js::gc::Cell*>(obj); + if (js::gc::IsInsideNursery(cell)) { + return false; + } + + auto tenuredCell = reinterpret_cast<js::gc::TenuredCell*>(cell); + return js::gc::detail::CellIsMarkedGrayIfKnown(tenuredCell); +} + +static MOZ_ALWAYS_INLINE bool ObjectIsMarkedGray( + const JS::Heap<JSObject*>& obj) { + return ObjectIsMarkedGray(obj.unbarrieredGet()); +} + +// The following *IsNotGray functions take account of the eventual +// gray marking state at the end of any ongoing incremental GC by +// delaying the checks if necessary. + +#ifdef DEBUG + +inline void AssertCellIsNotGray(const js::gc::Cell* maybeCell) { + if (maybeCell) { + js::gc::detail::AssertCellIsNotGray(maybeCell); + } +} + +inline void AssertObjectIsNotGray(JSObject* maybeObj) { + AssertCellIsNotGray(reinterpret_cast<js::gc::Cell*>(maybeObj)); +} + +inline void AssertObjectIsNotGray(const JS::Heap<JSObject*>& obj) { + AssertObjectIsNotGray(obj.unbarrieredGet()); +} + +#else + +inline void AssertCellIsNotGray(js::gc::Cell* maybeCell) {} +inline void AssertObjectIsNotGray(JSObject* maybeObj) {} +inline void AssertObjectIsNotGray(const JS::Heap<JSObject*>& obj) {} + +#endif + +/** + * The TenuredHeap<T> class is similar to the Heap<T> class above in that it + * encapsulates the GC concerns of an on-heap reference to a JS object. However, + * it has two important differences: + * + * 1) Pointers which are statically known to only reference "tenured" objects + * can avoid the extra overhead of SpiderMonkey's write barriers. + * + * 2) Objects in the "tenured" heap have stronger alignment restrictions than + * those in the "nursery", so it is possible to store flags in the lower + * bits of pointers known to be tenured. TenuredHeap wraps a normal tagged + * pointer with a nice API for accessing the flag bits and adds various + * assertions to ensure that it is not mis-used. + * + * GC things are said to be "tenured" when they are located in the long-lived + * heap: e.g. they have gained tenure as an object by surviving past at least + * one GC. For performance, SpiderMonkey allocates some things which are known + * to normally be long lived directly into the tenured generation; for example, + * global objects. Additionally, SpiderMonkey does not visit individual objects + * when deleting non-tenured objects, so object with finalizers are also always + * tenured; for instance, this includes most DOM objects. + * + * The considerations to keep in mind when using a TenuredHeap<T> vs a normal + * Heap<T> are: + * + * - It is invalid for a TenuredHeap<T> to refer to a non-tenured thing. + * - It is however valid for a Heap<T> to refer to a tenured thing. + * - It is not possible to store flag bits in a Heap<T>. + */ +template <typename T> +class TenuredHeap : public js::HeapOperations<T, TenuredHeap<T>> { + public: + using ElementType = T; + + TenuredHeap() : bits(0) { + static_assert(sizeof(T) == sizeof(TenuredHeap<T>), + "TenuredHeap<T> must be binary compatible with T."); + } + explicit TenuredHeap(T p) : bits(0) { setPtr(p); } + explicit TenuredHeap(const TenuredHeap<T>& p) : bits(0) { + setPtr(p.getPtr()); + } + + void setPtr(T newPtr) { + MOZ_ASSERT((reinterpret_cast<uintptr_t>(newPtr) & flagsMask) == 0); + MOZ_ASSERT(js::gc::IsCellPointerValidOrNull(newPtr)); + if (newPtr) { + AssertGCThingMustBeTenured(newPtr); + } + bits = (bits & flagsMask) | reinterpret_cast<uintptr_t>(newPtr); + } + + void setFlags(uintptr_t flagsToSet) { + MOZ_ASSERT((flagsToSet & ~flagsMask) == 0); + bits |= flagsToSet; + } + + void unsetFlags(uintptr_t flagsToUnset) { + MOZ_ASSERT((flagsToUnset & ~flagsMask) == 0); + bits &= ~flagsToUnset; + } + + bool hasFlag(uintptr_t flag) const { + MOZ_ASSERT((flag & ~flagsMask) == 0); + return (bits & flag) != 0; + } + + T unbarrieredGetPtr() const { return reinterpret_cast<T>(bits & ~flagsMask); } + uintptr_t getFlags() const { return bits & flagsMask; } + + void exposeToActiveJS() const { + js::BarrierMethods<T>::exposeToJS(unbarrieredGetPtr()); + } + T getPtr() const { + exposeToActiveJS(); + return unbarrieredGetPtr(); + } + + operator T() const { return getPtr(); } + T operator->() const { return getPtr(); } + + explicit operator bool() const { + return bool(js::BarrierMethods<T>::asGCThingOrNull(unbarrieredGetPtr())); + } + explicit operator bool() { + return bool(js::BarrierMethods<T>::asGCThingOrNull(unbarrieredGetPtr())); + } + + TenuredHeap<T>& operator=(T p) { + setPtr(p); + return *this; + } + + TenuredHeap<T>& operator=(const TenuredHeap<T>& other) { + bits = other.bits; + return *this; + } + + private: + enum { + maskBits = 3, + flagsMask = (1 << maskBits) - 1, + }; + + uintptr_t bits; +}; + +namespace detail { + +template <typename T> +struct DefineComparisonOps<TenuredHeap<T>> : std::true_type { + static const T get(const TenuredHeap<T>& v) { return v.unbarrieredGetPtr(); } +}; + +} // namespace detail + +// std::swap uses a stack temporary, which prevents classes like Heap<T> +// from being declared MOZ_HEAP_CLASS. +template <typename T> +void swap(TenuredHeap<T>& aX, TenuredHeap<T>& aY) { + T tmp = aX; + aX = aY; + aY = tmp; +} + +template <typename T> +void swap(Heap<T>& aX, Heap<T>& aY) { + T tmp = aX; + aX = aY; + aY = tmp; +} + +static MOZ_ALWAYS_INLINE bool ObjectIsMarkedGray( + const JS::TenuredHeap<JSObject*>& obj) { + return ObjectIsMarkedGray(obj.unbarrieredGetPtr()); +} + +template <typename T> +class MutableHandle; +template <typename T> +class Rooted; +template <typename T> +class PersistentRooted; + +/** + * Reference to a T that has been rooted elsewhere. This is most useful + * as a parameter type, which guarantees that the T lvalue is properly + * rooted. See "Move GC Stack Rooting" above. + * + * If you want to add additional methods to Handle for a specific + * specialization, define a HandleOperations<T> specialization containing them. + */ +template <typename T> +class MOZ_NONHEAP_CLASS Handle : public js::HandleOperations<T, Handle<T>> { + friend class MutableHandle<T>; + + public: + using ElementType = T; + + Handle(const Handle<T>&) = default; + + /* Creates a handle from a handle of a type convertible to T. */ + template <typename S> + MOZ_IMPLICIT Handle( + Handle<S> handle, + std::enable_if_t<std::is_convertible_v<S, T>, int> dummy = 0) { + static_assert(sizeof(Handle<T>) == sizeof(T*), + "Handle must be binary compatible with T*."); + ptr = reinterpret_cast<const T*>(handle.address()); + } + + MOZ_IMPLICIT Handle(decltype(nullptr)) { + static_assert(std::is_pointer_v<T>, + "nullptr_t overload not valid for non-pointer types"); + static void* const ConstNullValue = nullptr; + ptr = reinterpret_cast<const T*>(&ConstNullValue); + } + + MOZ_IMPLICIT Handle(MutableHandle<T> handle) { ptr = handle.address(); } + + /* + * Take care when calling this method! + * + * This creates a Handle from the raw location of a T. + * + * It should be called only if the following conditions hold: + * + * 1) the location of the T is guaranteed to be marked (for some reason + * other than being a Rooted), e.g., if it is guaranteed to be reachable + * from an implicit root. + * + * 2) the contents of the location are immutable, or at least cannot change + * for the lifetime of the handle, as its users may not expect its value + * to change underneath them. + */ + static constexpr Handle fromMarkedLocation(const T* p) { + return Handle(p, DeliberatelyChoosingThisOverload, + ImUsingThisOnlyInFromFromMarkedLocation); + } + + /* + * Construct a handle from an explicitly rooted location. This is the + * normal way to create a handle, and normally happens implicitly. + */ + template <typename S> + inline MOZ_IMPLICIT Handle( + const Rooted<S>& root, + std::enable_if_t<std::is_convertible_v<S, T>, int> dummy = 0); + + template <typename S> + inline MOZ_IMPLICIT Handle( + const PersistentRooted<S>& root, + std::enable_if_t<std::is_convertible_v<S, T>, int> dummy = 0); + + /* Construct a read only handle from a mutable handle. */ + template <typename S> + inline MOZ_IMPLICIT Handle( + MutableHandle<S>& root, + std::enable_if_t<std::is_convertible_v<S, T>, int> dummy = 0); + + DECLARE_POINTER_CONSTREF_OPS(T); + DECLARE_NONPOINTER_ACCESSOR_METHODS(*ptr); + + private: + Handle() = default; + DELETE_ASSIGNMENT_OPS(Handle, T); + + enum Disambiguator { DeliberatelyChoosingThisOverload = 42 }; + enum CallerIdentity { ImUsingThisOnlyInFromFromMarkedLocation = 17 }; + constexpr Handle(const T* p, Disambiguator, CallerIdentity) : ptr(p) {} + + const T* ptr; +}; + +namespace detail { + +template <typename T> +struct DefineComparisonOps<Handle<T>> : std::true_type { + static const T& get(const Handle<T>& v) { return v.get(); } +}; + +} // namespace detail + +/** + * Similar to a handle, but the underlying storage can be changed. This is + * useful for outparams. + * + * If you want to add additional methods to MutableHandle for a specific + * specialization, define a MutableHandleOperations<T> specialization containing + * them. + */ +template <typename T> +class MOZ_STACK_CLASS MutableHandle + : public js::MutableHandleOperations<T, MutableHandle<T>> { + public: + using ElementType = T; + + inline MOZ_IMPLICIT MutableHandle(Rooted<T>* root); + inline MOZ_IMPLICIT MutableHandle(PersistentRooted<T>* root); + + private: + // Disallow nullptr for overloading purposes. + MutableHandle(decltype(nullptr)) = delete; + + public: + MutableHandle(const MutableHandle<T>&) = default; + void set(const T& v) { + *ptr = v; + MOZ_ASSERT(GCPolicy<T>::isValid(*ptr)); + } + void set(T&& v) { + *ptr = std::move(v); + MOZ_ASSERT(GCPolicy<T>::isValid(*ptr)); + } + + /* + * This may be called only if the location of the T is guaranteed + * to be marked (for some reason other than being a Rooted), + * e.g., if it is guaranteed to be reachable from an implicit root. + * + * Create a MutableHandle from a raw location of a T. + */ + static MutableHandle fromMarkedLocation(T* p) { + MutableHandle h; + h.ptr = p; + return h; + } + + DECLARE_POINTER_CONSTREF_OPS(T); + DECLARE_NONPOINTER_ACCESSOR_METHODS(*ptr); + DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(*ptr); + + private: + MutableHandle() = default; + DELETE_ASSIGNMENT_OPS(MutableHandle, T); + + T* ptr; +}; + +namespace detail { + +template <typename T> +struct DefineComparisonOps<MutableHandle<T>> : std::true_type { + static const T& get(const MutableHandle<T>& v) { return v.get(); } +}; + +} // namespace detail + +} /* namespace JS */ + +namespace js { + +namespace detail { + +// Default implementations for barrier methods on GC thing pointers. +template <typename T> +struct PtrBarrierMethodsBase { + static T* initial() { return nullptr; } + static gc::Cell* asGCThingOrNull(T* v) { + if (!v) { + return nullptr; + } + MOZ_ASSERT(uintptr_t(v) > 32); + return reinterpret_cast<gc::Cell*>(v); + } + static void exposeToJS(T* t) { + if (t) { + js::gc::ExposeGCThingToActiveJS(JS::GCCellPtr(t)); + } + } + static void readBarrier(T* t) { + if (t) { + js::gc::IncrementalReadBarrier(JS::GCCellPtr(t)); + } + } +}; + +} // namespace detail + +template <typename T> +struct BarrierMethods<T*> : public detail::PtrBarrierMethodsBase<T> { + static void postWriteBarrier(T** vp, T* prev, T* next) { + if (next) { + JS::AssertGCThingIsNotNurseryAllocable( + reinterpret_cast<js::gc::Cell*>(next)); + } + } +}; + +template <> +struct BarrierMethods<JSObject*> + : public detail::PtrBarrierMethodsBase<JSObject> { + static void postWriteBarrier(JSObject** vp, JSObject* prev, JSObject* next) { + JS::HeapObjectPostWriteBarrier(vp, prev, next); + } + static void exposeToJS(JSObject* obj) { + if (obj) { + JS::ExposeObjectToActiveJS(obj); + } + } +}; + +template <> +struct BarrierMethods<JSFunction*> + : public detail::PtrBarrierMethodsBase<JSFunction> { + static void postWriteBarrier(JSFunction** vp, JSFunction* prev, + JSFunction* next) { + JS::HeapObjectPostWriteBarrier(reinterpret_cast<JSObject**>(vp), + reinterpret_cast<JSObject*>(prev), + reinterpret_cast<JSObject*>(next)); + } + static void exposeToJS(JSFunction* fun) { + if (fun) { + JS::ExposeObjectToActiveJS(reinterpret_cast<JSObject*>(fun)); + } + } +}; + +template <> +struct BarrierMethods<JSString*> + : public detail::PtrBarrierMethodsBase<JSString> { + static void postWriteBarrier(JSString** vp, JSString* prev, JSString* next) { + JS::HeapStringPostWriteBarrier(vp, prev, next); + } +}; + +template <> +struct BarrierMethods<JS::BigInt*> + : public detail::PtrBarrierMethodsBase<JS::BigInt> { + static void postWriteBarrier(JS::BigInt** vp, JS::BigInt* prev, + JS::BigInt* next) { + JS::HeapBigIntPostWriteBarrier(vp, prev, next); + } +}; + +// Provide hash codes for Cell kinds that may be relocated and, thus, not have +// a stable address to use as the base for a hash code. Instead of the address, +// this hasher uses Cell::getUniqueId to provide exact matches and as a base +// for generating hash codes. +// +// Note: this hasher, like PointerHasher can "hash" a nullptr. While a nullptr +// would not likely be a useful key, there are some cases where being able to +// hash a nullptr is useful, either on purpose or because of bugs: +// (1) existence checks where the key may happen to be null and (2) some +// aggregate Lookup kinds embed a JSObject* that is frequently null and do not +// null test before dispatching to the hasher. +template <typename T> +struct JS_PUBLIC_API StableCellHasher { + using Key = T; + using Lookup = T; + + static bool maybeGetHash(const Lookup& l, mozilla::HashNumber* hashOut); + static bool ensureHash(const Lookup& l, HashNumber* hashOut); + static HashNumber hash(const Lookup& l); + static bool match(const Key& k, const Lookup& l); + // The rekey hash policy method is not provided since you dont't need to + // rekey any more when using this policy. +}; + +template <typename T> +struct JS_PUBLIC_API StableCellHasher<JS::Heap<T>> { + using Key = JS::Heap<T>; + using Lookup = T; + + static bool maybeGetHash(const Lookup& l, HashNumber* hashOut) { + return StableCellHasher<T>::maybeGetHash(l, hashOut); + } + static bool ensureHash(const Lookup& l, HashNumber* hashOut) { + return StableCellHasher<T>::ensureHash(l, hashOut); + } + static HashNumber hash(const Lookup& l) { + return StableCellHasher<T>::hash(l); + } + static bool match(const Key& k, const Lookup& l) { + return StableCellHasher<T>::match(k.unbarrieredGet(), l); + } +}; + +} // namespace js + +namespace mozilla { + +template <typename T> +struct FallibleHashMethods<js::StableCellHasher<T>> { + template <typename Lookup> + static bool maybeGetHash(Lookup&& l, HashNumber* hashOut) { + return js::StableCellHasher<T>::maybeGetHash(std::forward<Lookup>(l), + hashOut); + } + template <typename Lookup> + static bool ensureHash(Lookup&& l, HashNumber* hashOut) { + return js::StableCellHasher<T>::ensureHash(std::forward<Lookup>(l), + hashOut); + } +}; + +} // namespace mozilla + +namespace js { + +struct VirtualTraceable { + virtual ~VirtualTraceable() = default; + virtual void trace(JSTracer* trc, const char* name) = 0; +}; + +class StackRootedBase { + public: + StackRootedBase* previous() { return prev; } + + protected: + StackRootedBase** stack; + StackRootedBase* prev; + + template <typename T> + auto* derived() { + return static_cast<JS::Rooted<T>*>(this); + } +}; + +class PersistentRootedBase + : protected mozilla::LinkedListElement<PersistentRootedBase> { + protected: + friend class mozilla::LinkedList<PersistentRootedBase>; + friend class mozilla::LinkedListElement<PersistentRootedBase>; + + template <typename T> + auto* derived() { + return static_cast<JS::PersistentRooted<T>*>(this); + } +}; + +struct StackRootedTraceableBase : public StackRootedBase, + public VirtualTraceable {}; + +class PersistentRootedTraceableBase : public PersistentRootedBase, + public VirtualTraceable {}; + +template <typename Base, typename T> +class TypedRootedGCThingBase : public Base { + public: + void trace(JSTracer* trc, const char* name); +}; + +template <typename Base, typename T> +class TypedRootedTraceableBase : public Base { + public: + void trace(JSTracer* trc, const char* name) override { + auto* self = this->template derived<T>(); + JS::GCPolicy<T>::trace(trc, self->address(), name); + } +}; + +template <typename T> +struct RootedTraceableTraits { + using StackBase = TypedRootedTraceableBase<StackRootedTraceableBase, T>; + using PersistentBase = + TypedRootedTraceableBase<PersistentRootedTraceableBase, T>; +}; + +template <typename T> +struct RootedGCThingTraits { + using StackBase = TypedRootedGCThingBase<StackRootedBase, T>; + using PersistentBase = TypedRootedGCThingBase<PersistentRootedBase, T>; +}; + +} /* namespace js */ + +namespace JS { + +class JS_PUBLIC_API AutoGCRooter; + +enum class AutoGCRooterKind : uint8_t { + WrapperVector, /* js::AutoWrapperVector */ + Wrapper, /* js::AutoWrapperRooter */ + Custom, /* js::CustomAutoRooter */ + + Limit +}; + +using RootedListHeads = + mozilla::EnumeratedArray<RootKind, RootKind::Limit, js::StackRootedBase*>; + +using AutoRooterListHeads = + mozilla::EnumeratedArray<AutoGCRooterKind, AutoGCRooterKind::Limit, + AutoGCRooter*>; + +// Superclass of JSContext which can be used for rooting data in use by the +// current thread but that does not provide all the functions of a JSContext. +class RootingContext { + // Stack GC roots for Rooted GC heap pointers. + RootedListHeads stackRoots_; + template <typename T> + friend class Rooted; + + // Stack GC roots for AutoFooRooter classes. + AutoRooterListHeads autoGCRooters_; + friend class AutoGCRooter; + + // Gecko profiling metadata. + // This isn't really rooting related. It's only here because we want + // GetContextProfilingStackIfEnabled to be inlineable into non-JS code, and + // we didn't want to add another superclass of JSContext just for this. + js::GeckoProfilerThread geckoProfiler_; + + public: + RootingContext(); + + void traceStackRoots(JSTracer* trc); + + /* Implemented in gc/RootMarking.cpp. */ + void traceAllGCRooters(JSTracer* trc); + void traceWrapperGCRooters(JSTracer* trc); + static void traceGCRooterList(JSTracer* trc, AutoGCRooter* head); + + void checkNoGCRooters(); + + js::GeckoProfilerThread& geckoProfiler() { return geckoProfiler_; } + + protected: + // The remaining members in this class should only be accessed through + // JSContext pointers. They are unrelated to rooting and are in place so + // that inlined API functions can directly access the data. + + /* The current realm. */ + Realm* realm_; + + /* The current zone. */ + Zone* zone_; + + public: + /* Limit pointer for checking native stack consumption. */ + JS::NativeStackLimit nativeStackLimit[StackKindCount]; + +#ifdef __wasi__ + // For WASI we can't catch call-stack overflows with stack-pointer checks, so + // we count recursion depth with RAII based AutoCheckRecursionLimit. + uint32_t wasiRecursionDepth = 0u; + + static constexpr uint32_t wasiRecursionDepthLimit = 350u; +#endif // __wasi__ + + static const RootingContext* get(const JSContext* cx) { + return reinterpret_cast<const RootingContext*>(cx); + } + + static RootingContext* get(JSContext* cx) { + return reinterpret_cast<RootingContext*>(cx); + } + + friend JS::Realm* js::GetContextRealm(const JSContext* cx); + friend JS::Zone* js::GetContextZone(const JSContext* cx); +}; + +class JS_PUBLIC_API AutoGCRooter { + public: + using Kind = AutoGCRooterKind; + + AutoGCRooter(JSContext* cx, Kind kind) + : AutoGCRooter(JS::RootingContext::get(cx), kind) {} + AutoGCRooter(RootingContext* cx, Kind kind) + : down(cx->autoGCRooters_[kind]), + stackTop(&cx->autoGCRooters_[kind]), + kind_(kind) { + MOZ_ASSERT(this != *stackTop); + *stackTop = this; + } + + ~AutoGCRooter() { + MOZ_ASSERT(this == *stackTop); + *stackTop = down; + } + + void trace(JSTracer* trc); + + private: + friend class RootingContext; + + AutoGCRooter* const down; + AutoGCRooter** const stackTop; + + /* + * Discriminates actual subclass of this being used. The meaning is + * indicated by the corresponding value in the Kind enum. + */ + Kind kind_; + + /* No copy or assignment semantics. */ + AutoGCRooter(AutoGCRooter& ida) = delete; + void operator=(AutoGCRooter& ida) = delete; +} JS_HAZ_ROOTED_BASE; + +/** + * Custom rooting behavior for internal and external clients. + * + * Deprecated. Where possible, use Rooted<> instead. + */ +class MOZ_RAII JS_PUBLIC_API CustomAutoRooter : private AutoGCRooter { + public: + template <typename CX> + explicit CustomAutoRooter(const CX& cx) + : AutoGCRooter(cx, AutoGCRooter::Kind::Custom) {} + + friend void AutoGCRooter::trace(JSTracer* trc); + + protected: + virtual ~CustomAutoRooter() = default; + + /** Supplied by derived class to trace roots. */ + virtual void trace(JSTracer* trc) = 0; +}; + +namespace detail { + +template <typename T> +constexpr bool IsTraceable_v = + MapTypeToRootKind<T>::kind == JS::RootKind::Traceable; + +template <typename T> +using RootedTraits = + std::conditional_t<IsTraceable_v<T>, js::RootedTraceableTraits<T>, + js::RootedGCThingTraits<T>>; + +} /* namespace detail */ + +/** + * Local variable of type T whose value is always rooted. This is typically + * used for local variables, or for non-rooted values being passed to a + * function that requires a handle, e.g. Foo(Root<T>(cx, x)). + * + * If you want to add additional methods to Rooted for a specific + * specialization, define a RootedOperations<T> specialization containing them. + */ +template <typename T> +class MOZ_RAII Rooted : public detail::RootedTraits<T>::StackBase, + public js::RootedOperations<T, Rooted<T>> { + inline void registerWithRootLists(RootedListHeads& roots) { + this->stack = &roots[JS::MapTypeToRootKind<T>::kind]; + this->prev = *this->stack; + *this->stack = this; + } + + inline RootedListHeads& rootLists(RootingContext* cx) { + return cx->stackRoots_; + } + inline RootedListHeads& rootLists(JSContext* cx) { + return rootLists(RootingContext::get(cx)); + } + + public: + using ElementType = T; + + // Construct an empty Rooted holding a safely initialized but empty T. + // Requires T to have a copy constructor in order to copy the safely + // initialized value. + // + // Note that for SFINAE to reject this method, the 2nd template parameter must + // depend on RootingContext somehow even though we really only care about T. + template <typename RootingContext, + typename = std::enable_if_t<std::is_copy_constructible_v<T>, + RootingContext>> + explicit Rooted(const RootingContext& cx) + : ptr(SafelyInitialized<T>::create()) { + registerWithRootLists(rootLists(cx)); + } + + // Provide an initial value. Requires T to be constructible from the given + // argument. + template <typename RootingContext, typename S> + Rooted(const RootingContext& cx, S&& initial) + : ptr(std::forward<S>(initial)) { + MOZ_ASSERT(GCPolicy<T>::isValid(ptr)); + registerWithRootLists(rootLists(cx)); + } + + // (Traceables only) Construct the contained value from the given arguments. + // Constructs in-place, so T does not need to be copyable or movable. + // + // Note that a copyable Traceable passed only a RootingContext will + // choose the above SafelyInitialized<T> constructor, because otherwise + // identical functions with parameter packs are considered less specialized. + // + // The SFINAE type must again depend on an inferred template parameter. + template < + typename RootingContext, typename... CtorArgs, + typename = std::enable_if_t<detail::IsTraceable_v<T>, RootingContext>> + explicit Rooted(const RootingContext& cx, CtorArgs... args) + : ptr(std::forward<CtorArgs>(args)...) { + MOZ_ASSERT(GCPolicy<T>::isValid(ptr)); + registerWithRootLists(rootLists(cx)); + } + + ~Rooted() { + MOZ_ASSERT(*this->stack == this); + *this->stack = this->prev; + } + + /* + * This method is public for Rooted so that Codegen.py can use a Rooted + * interchangeably with a MutableHandleValue. + */ + void set(const T& value) { + ptr = value; + MOZ_ASSERT(GCPolicy<T>::isValid(ptr)); + } + void set(T&& value) { + ptr = std::move(value); + MOZ_ASSERT(GCPolicy<T>::isValid(ptr)); + } + + DECLARE_POINTER_CONSTREF_OPS(T); + DECLARE_POINTER_ASSIGN_OPS(Rooted, T); + + T& get() { return ptr; } + const T& get() const { return ptr; } + + T* address() { return &ptr; } + const T* address() const { return &ptr; } + + private: + T ptr; + + Rooted(const Rooted&) = delete; +} JS_HAZ_ROOTED; + +namespace detail { + +template <typename T> +struct DefineComparisonOps<Rooted<T>> : std::true_type { + static const T& get(const Rooted<T>& v) { return v.get(); } +}; + +} // namespace detail + +} /* namespace JS */ + +namespace js { + +/* + * Inlinable accessors for JSContext. + * + * - These must not be available on the more restricted superclasses of + * JSContext, so we can't simply define them on RootingContext. + * + * - They're perfectly ordinary JSContext functionality, so ought to be + * usable without resorting to jsfriendapi.h, and when JSContext is an + * incomplete type. + */ +inline JS::Realm* GetContextRealm(const JSContext* cx) { + return JS::RootingContext::get(cx)->realm_; +} + +inline JS::Compartment* GetContextCompartment(const JSContext* cx) { + if (JS::Realm* realm = GetContextRealm(cx)) { + return GetCompartmentForRealm(realm); + } + return nullptr; +} + +inline JS::Zone* GetContextZone(const JSContext* cx) { + return JS::RootingContext::get(cx)->zone_; +} + +inline ProfilingStack* GetContextProfilingStackIfEnabled(JSContext* cx) { + return JS::RootingContext::get(cx) + ->geckoProfiler() + .getProfilingStackIfEnabled(); +} + +/** + * Augment the generic Rooted<T> interface when T = JSObject* with + * class-querying and downcasting operations. + * + * Given a Rooted<JSObject*> obj, one can view + * Handle<StringObject*> h = obj.as<StringObject*>(); + * as an optimization of + * Rooted<StringObject*> rooted(cx, &obj->as<StringObject*>()); + * Handle<StringObject*> h = rooted; + */ +template <typename Container> +class RootedOperations<JSObject*, Container> + : public MutableWrappedPtrOperations<JSObject*, Container> { + public: + template <class U> + JS::Handle<U*> as() const; +}; + +/** + * Augment the generic Handle<T> interface when T = JSObject* with + * downcasting operations. + * + * Given a Handle<JSObject*> obj, one can view + * Handle<StringObject*> h = obj.as<StringObject*>(); + * as an optimization of + * Rooted<StringObject*> rooted(cx, &obj->as<StringObject*>()); + * Handle<StringObject*> h = rooted; + */ +template <typename Container> +class HandleOperations<JSObject*, Container> + : public WrappedPtrOperations<JSObject*, Container> { + public: + template <class U> + JS::Handle<U*> as() const; +}; + +} /* namespace js */ + +namespace JS { + +template <typename T> +template <typename S> +inline Handle<T>::Handle( + const Rooted<S>& root, + std::enable_if_t<std::is_convertible_v<S, T>, int> dummy) { + ptr = reinterpret_cast<const T*>(root.address()); +} + +template <typename T> +template <typename S> +inline Handle<T>::Handle( + const PersistentRooted<S>& root, + std::enable_if_t<std::is_convertible_v<S, T>, int> dummy) { + ptr = reinterpret_cast<const T*>(root.address()); +} + +template <typename T> +template <typename S> +inline Handle<T>::Handle( + MutableHandle<S>& root, + std::enable_if_t<std::is_convertible_v<S, T>, int> dummy) { + ptr = reinterpret_cast<const T*>(root.address()); +} + +template <typename T> +inline MutableHandle<T>::MutableHandle(Rooted<T>* root) { + static_assert(sizeof(MutableHandle<T>) == sizeof(T*), + "MutableHandle must be binary compatible with T*."); + ptr = root->address(); +} + +template <typename T> +inline MutableHandle<T>::MutableHandle(PersistentRooted<T>* root) { + static_assert(sizeof(MutableHandle<T>) == sizeof(T*), + "MutableHandle must be binary compatible with T*."); + ptr = root->address(); +} + +JS_PUBLIC_API void AddPersistentRoot(RootingContext* cx, RootKind kind, + js::PersistentRootedBase* root); + +JS_PUBLIC_API void AddPersistentRoot(JSRuntime* rt, RootKind kind, + js::PersistentRootedBase* root); + +/** + * A copyable, assignable global GC root type with arbitrary lifetime, an + * infallible constructor, and automatic unrooting on destruction. + * + * These roots can be used in heap-allocated data structures, so they are not + * associated with any particular JSContext or stack. They are registered with + * the JSRuntime itself, without locking. Initialization may take place on + * construction, or in two phases if the no-argument constructor is called + * followed by init(). + * + * Note that you must not use an PersistentRooted in an object owned by a JS + * object: + * + * Whenever one object whose lifetime is decided by the GC refers to another + * such object, that edge must be traced only if the owning JS object is traced. + * This applies not only to JS objects (which obviously are managed by the GC) + * but also to C++ objects owned by JS objects. + * + * If you put a PersistentRooted in such a C++ object, that is almost certainly + * a leak. When a GC begins, the referent of the PersistentRooted is treated as + * live, unconditionally (because a PersistentRooted is a *root*), even if the + * JS object that owns it is unreachable. If there is any path from that + * referent back to the JS object, then the C++ object containing the + * PersistentRooted will not be destructed, and the whole blob of objects will + * not be freed, even if there are no references to them from the outside. + * + * In the context of Firefox, this is a severe restriction: almost everything in + * Firefox is owned by some JS object or another, so using PersistentRooted in + * such objects would introduce leaks. For these kinds of edges, Heap<T> or + * TenuredHeap<T> would be better types. It's up to the implementor of the type + * containing Heap<T> or TenuredHeap<T> members to make sure their referents get + * marked when the object itself is marked. + */ +template <typename T> +class PersistentRooted : public detail::RootedTraits<T>::PersistentBase, + public js::RootedOperations<T, PersistentRooted<T>> { + void registerWithRootLists(RootingContext* cx) { + MOZ_ASSERT(!initialized()); + JS::RootKind kind = JS::MapTypeToRootKind<T>::kind; + AddPersistentRoot(cx, kind, this); + } + + void registerWithRootLists(JSRuntime* rt) { + MOZ_ASSERT(!initialized()); + JS::RootKind kind = JS::MapTypeToRootKind<T>::kind; + AddPersistentRoot(rt, kind, this); + } + + // Used when JSContext type is incomplete and so it is not known to inherit + // from RootingContext. + void registerWithRootLists(JSContext* cx) { + registerWithRootLists(RootingContext::get(cx)); + } + + public: + using ElementType = T; + + PersistentRooted() : ptr(SafelyInitialized<T>::create()) {} + + template < + typename RootHolder, + typename = std::enable_if_t<std::is_copy_constructible_v<T>, RootHolder>> + explicit PersistentRooted(const RootHolder& cx) + : ptr(SafelyInitialized<T>::create()) { + registerWithRootLists(cx); + } + + template < + typename RootHolder, typename U, + typename = std::enable_if_t<std::is_constructible_v<T, U>, RootHolder>> + PersistentRooted(const RootHolder& cx, U&& initial) + : ptr(std::forward<U>(initial)) { + registerWithRootLists(cx); + } + + template <typename RootHolder, typename... CtorArgs, + typename = std::enable_if_t<detail::IsTraceable_v<T>, RootHolder>> + explicit PersistentRooted(const RootHolder& cx, CtorArgs... args) + : ptr(std::forward<CtorArgs>(args)...) { + registerWithRootLists(cx); + } + + PersistentRooted(const PersistentRooted& rhs) : ptr(rhs.ptr) { + /* + * Copy construction takes advantage of the fact that the original + * is already inserted, and simply adds itself to whatever list the + * original was on - no JSRuntime pointer needed. + * + * This requires mutating rhs's links, but those should be 'mutable' + * anyway. C++ doesn't let us declare mutable base classes. + */ + const_cast<PersistentRooted&>(rhs).setNext(this); + } + + bool initialized() const { return this->isInList(); } + + void init(RootingContext* cx) { init(cx, SafelyInitialized<T>::create()); } + void init(JSContext* cx) { init(RootingContext::get(cx)); } + + template <typename U> + void init(RootingContext* cx, U&& initial) { + ptr = std::forward<U>(initial); + registerWithRootLists(cx); + } + template <typename U> + void init(JSContext* cx, U&& initial) { + ptr = std::forward<U>(initial); + registerWithRootLists(RootingContext::get(cx)); + } + + void reset() { + if (initialized()) { + set(SafelyInitialized<T>::create()); + this->remove(); + } + } + + DECLARE_POINTER_CONSTREF_OPS(T); + DECLARE_POINTER_ASSIGN_OPS(PersistentRooted, T); + + T& get() { return ptr; } + const T& get() const { return ptr; } + + T* address() { + MOZ_ASSERT(initialized()); + return &ptr; + } + const T* address() const { return &ptr; } + + template <typename U> + void set(U&& value) { + MOZ_ASSERT(initialized()); + ptr = std::forward<U>(value); + } + + private: + T ptr; +} JS_HAZ_ROOTED; + +namespace detail { + +template <typename T> +struct DefineComparisonOps<PersistentRooted<T>> : std::true_type { + static const T& get(const PersistentRooted<T>& v) { return v.get(); } +}; + +} // namespace detail + +} /* namespace JS */ + +namespace js { + +template <typename T, typename D, typename Container> +class WrappedPtrOperations<UniquePtr<T, D>, Container> { + const UniquePtr<T, D>& uniquePtr() const { + return static_cast<const Container*>(this)->get(); + } + + public: + explicit operator bool() const { return !!uniquePtr(); } + T* get() const { return uniquePtr().get(); } + T* operator->() const { return get(); } + T& operator*() const { return *uniquePtr(); } +}; + +template <typename T, typename D, typename Container> +class MutableWrappedPtrOperations<UniquePtr<T, D>, Container> + : public WrappedPtrOperations<UniquePtr<T, D>, Container> { + UniquePtr<T, D>& uniquePtr() { return static_cast<Container*>(this)->get(); } + + public: + [[nodiscard]] typename UniquePtr<T, D>::Pointer release() { + return uniquePtr().release(); + } + void reset(T* ptr = T()) { uniquePtr().reset(ptr); } +}; + +template <typename T, typename Container> +class WrappedPtrOperations<mozilla::Maybe<T>, Container> { + const mozilla::Maybe<T>& maybe() const { + return static_cast<const Container*>(this)->get(); + } + + public: + // This only supports a subset of Maybe's interface. + bool isSome() const { return maybe().isSome(); } + bool isNothing() const { return maybe().isNothing(); } + const T value() const { return maybe().value(); } + const T* operator->() const { return maybe().ptr(); } + const T& operator*() const { return maybe().ref(); } +}; + +template <typename T, typename Container> +class MutableWrappedPtrOperations<mozilla::Maybe<T>, Container> + : public WrappedPtrOperations<mozilla::Maybe<T>, Container> { + mozilla::Maybe<T>& maybe() { return static_cast<Container*>(this)->get(); } + + public: + // This only supports a subset of Maybe's interface. + T* operator->() { return maybe().ptr(); } + T& operator*() { return maybe().ref(); } + void reset() { return maybe().reset(); } +}; + +namespace gc { + +template <typename T, typename TraceCallbacks> +void CallTraceCallbackOnNonHeap(T* v, const TraceCallbacks& aCallbacks, + const char* aName, void* aClosure) { + static_assert(sizeof(T) == sizeof(JS::Heap<T>), + "T and Heap<T> must be compatible."); + MOZ_ASSERT(v); + mozilla::DebugOnly<Cell*> cell = BarrierMethods<T>::asGCThingOrNull(*v); + MOZ_ASSERT(cell); + MOZ_ASSERT(!IsInsideNursery(cell)); + JS::Heap<T>* asHeapT = reinterpret_cast<JS::Heap<T>*>(v); + aCallbacks.Trace(asHeapT, aName, aClosure); +} + +} /* namespace gc */ + +template <typename Wrapper, typename T1, typename T2> +class WrappedPtrOperations<std::pair<T1, T2>, Wrapper> { + const std::pair<T1, T2>& pair() const { + return static_cast<const Wrapper*>(this)->get(); + } + + public: + const T1& first() const { return pair().first; } + const T2& second() const { return pair().second; } +}; + +template <typename Wrapper, typename T1, typename T2> +class MutableWrappedPtrOperations<std::pair<T1, T2>, Wrapper> + : public WrappedPtrOperations<std::pair<T1, T2>, Wrapper> { + std::pair<T1, T2>& pair() { return static_cast<Wrapper*>(this)->get(); } + + public: + T1& first() { return pair().first; } + T2& second() { return pair().second; } +}; + +} /* namespace js */ + +#endif /* js_RootingAPI_h */ diff --git a/js/public/SavedFrameAPI.h b/js/public/SavedFrameAPI.h new file mode 100644 index 0000000000..066d0d7cfa --- /dev/null +++ b/js/public/SavedFrameAPI.h @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * Functions and types related to SavedFrame objects created by the Debugger + * API. + */ + +#ifndef js_SavedFrameAPI_h +#define js_SavedFrameAPI_h + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/TypeDecls.h" + +struct JSPrincipals; + +namespace JS { + +/* + * Accessors for working with SavedFrame JSObjects + * + * Each of these functions assert that if their `HandleObject savedFrame` + * argument is non-null, its JSClass is the SavedFrame class (or it is a + * cross-compartment or Xray wrapper around an object with the SavedFrame class) + * and the object is not the SavedFrame.prototype object. + * + * Each of these functions will find the first SavedFrame object in the chain + * whose underlying stack frame principals are subsumed by the given + * |principals|, and operate on that SavedFrame object. This prevents leaking + * information about privileged frames to un-privileged callers + * + * The SavedFrame in parameters do _NOT_ need to be in the same compartment as + * the cx, and the various out parameters are _NOT_ guaranteed to be in the same + * compartment as cx. + * + * You may consider or skip over self-hosted frames by passing + * `SavedFrameSelfHosted::Include` or `SavedFrameSelfHosted::Exclude` + * respectively. + * + * Additionally, it may be the case that there is no such SavedFrame object + * whose captured frame's principals are subsumed by |principals|! If the + * `HandleObject savedFrame` argument is null, or the |principals| do not + * subsume any of the chained SavedFrame object's principals, + * `SavedFrameResult::AccessDenied` is returned and a (hopefully) sane default + * value is chosen for the out param. + * + * See also `js/src/doc/SavedFrame/SavedFrame.md`. + */ + +enum class SavedFrameResult { Ok, AccessDenied }; + +enum class SavedFrameSelfHosted { Include, Exclude }; + +/** + * Given a SavedFrame JSObject, get its source property. Defaults to the empty + * string. + */ +extern JS_PUBLIC_API SavedFrameResult GetSavedFrameSource( + JSContext* cx, JSPrincipals* principals, Handle<JSObject*> savedFrame, + MutableHandle<JSString*> sourcep, + SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include); + +/** + * Given a SavedFrame JSObject, get an ID identifying its ScriptSource. + * Defaults to 0. + */ +extern JS_PUBLIC_API SavedFrameResult GetSavedFrameSourceId( + JSContext* cx, JSPrincipals* principals, Handle<JSObject*> savedFrame, + uint32_t* sourceIdp, + SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include); + +/** + * Given a SavedFrame JSObject, get its line property. Defaults to 0. + */ +extern JS_PUBLIC_API SavedFrameResult GetSavedFrameLine( + JSContext* cx, JSPrincipals* principals, Handle<JSObject*> savedFrame, + uint32_t* linep, + SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include); + +/** + * Given a SavedFrame JSObject, get its column property. Defaults to 0. + */ +extern JS_PUBLIC_API SavedFrameResult GetSavedFrameColumn( + JSContext* cx, JSPrincipals* principals, Handle<JSObject*> savedFrame, + uint32_t* columnp, + SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include); + +/** + * Given a SavedFrame JSObject, get its functionDisplayName string, or nullptr + * if SpiderMonkey was unable to infer a name for the captured frame's + * function. Defaults to nullptr. + */ +extern JS_PUBLIC_API SavedFrameResult GetSavedFrameFunctionDisplayName( + JSContext* cx, JSPrincipals* principals, Handle<JSObject*> savedFrame, + MutableHandle<JSString*> namep, + SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include); + +/** + * Given a SavedFrame JSObject, get its asyncCause string. Defaults to nullptr. + */ +extern JS_PUBLIC_API SavedFrameResult GetSavedFrameAsyncCause( + JSContext* cx, JSPrincipals* principals, Handle<JSObject*> savedFrame, + MutableHandle<JSString*> asyncCausep, + SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include); + +/** + * Given a SavedFrame JSObject, get its asyncParent SavedFrame object or nullptr + * if there is no asyncParent. The `asyncParentp` out parameter is _NOT_ + * guaranteed to be in the cx's compartment. Defaults to nullptr. + */ +extern JS_PUBLIC_API SavedFrameResult GetSavedFrameAsyncParent( + JSContext* cx, JSPrincipals* principals, Handle<JSObject*> savedFrame, + MutableHandle<JSObject*> asyncParentp, + SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include); + +/** + * Given a SavedFrame JSObject, get its parent SavedFrame object or nullptr if + * it is the oldest frame in the stack. The `parentp` out parameter is _NOT_ + * guaranteed to be in the cx's compartment. Defaults to nullptr. + */ +extern JS_PUBLIC_API SavedFrameResult GetSavedFrameParent( + JSContext* cx, JSPrincipals* principals, Handle<JSObject*> savedFrame, + MutableHandle<JSObject*> parentp, + SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include); + +/** + * Given a SavedFrame object, convert it and its transitive parents to plain + * objects. Because SavedFrame objects store their properties on the prototype, + * they cannot be usefully stringified to JSON. Assigning their properties to + * plain objects allow those objects to be stringified and the saved frame stack + * can be encoded as a string. + */ +JS_PUBLIC_API JSObject* ConvertSavedFrameToPlainObject( + JSContext* cx, JS::HandleObject savedFrame, + JS::SavedFrameSelfHosted selfHosted); + +} // namespace JS + +namespace js { + +/** + * Get the first SavedFrame object in this SavedFrame stack whose principals are + * subsumed by the given |principals|. If there is no such frame, return + * nullptr. + * + * Do NOT pass a non-SavedFrame object here. + */ +extern JS_PUBLIC_API JSObject* GetFirstSubsumedSavedFrame( + JSContext* cx, JSPrincipals* principals, JS::Handle<JSObject*> savedFrame, + JS::SavedFrameSelfHosted selfHosted); + +} // namespace js + +#endif /* js_SavedFrameAPI_h */ diff --git a/js/public/ScalarType.h b/js/public/ScalarType.h new file mode 100644 index 0000000000..3c98fa204c --- /dev/null +++ b/js/public/ScalarType.h @@ -0,0 +1,232 @@ +/* -*- 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/. */ + +/* An enumeration of all possible element types in typed data. */ + +#ifndef js_ScalarType_h +#define js_ScalarType_h + +#include "mozilla/Assertions.h" // MOZ_CRASH + +#include <stddef.h> // size_t + +namespace JS { + +namespace Scalar { + +// Scalar types that can appear in typed arrays. +// The enum values must be kept in sync with: +// +// * the TYPEDARRAY_KIND constants +// * the SCTAG_TYPED_ARRAY constants +// * JS_FOR_EACH_TYPED_ARRAY +// * JS_FOR_PROTOTYPES_ +// * JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE +// * JIT compilation +// +// and the existing entries here must not be renumbered, since they are +// necessary for backwards compatibility with structured clones from previous +// versions. (It is fine to add new entries and increment +// MaxTypedArrayViewType, or change anything at or after +// MaxTypedArrayViewType.) +enum Type { + Int8 = 0, + Uint8, + Int16, + Uint16, + Int32, + Uint32, + Float32, + Float64, + + /** + * Special type that is a uint8_t, but assignments are clamped to [0, 256). + * Treat the raw data type as a uint8_t. + */ + Uint8Clamped, + + BigInt64, + BigUint64, + + /** + * Types that don't have their own TypedArray equivalent, for now. + * E.g. DataView + */ + MaxTypedArrayViewType, + + Int64, + Simd128, +}; + +static inline size_t byteSize(Type atype) { + switch (atype) { + case Int8: + case Uint8: + case Uint8Clamped: + return 1; + case Int16: + case Uint16: + return 2; + case Int32: + case Uint32: + case Float32: + return 4; + case Int64: + case Float64: + case BigInt64: + case BigUint64: + return 8; + case Simd128: + return 16; + case MaxTypedArrayViewType: + break; + } + MOZ_CRASH("invalid scalar type"); +} + +static inline bool isSignedIntType(Type atype) { + switch (atype) { + case Int8: + case Int16: + case Int32: + case Int64: + case BigInt64: + return true; + case Uint8: + case Uint8Clamped: + case Uint16: + case Uint32: + case Float32: + case Float64: + case BigUint64: + case Simd128: + return false; + case MaxTypedArrayViewType: + break; + } + MOZ_CRASH("invalid scalar type"); +} + +static inline bool isBigIntType(Type atype) { + switch (atype) { + case BigInt64: + case BigUint64: + return true; + case Int8: + case Int16: + case Int32: + case Int64: + case Uint8: + case Uint8Clamped: + case Uint16: + case Uint32: + case Float32: + case Float64: + case Simd128: + return false; + case MaxTypedArrayViewType: + break; + } + MOZ_CRASH("invalid scalar type"); +} + +static inline bool isFloatingType(Type atype) { + switch (atype) { + case Int8: + case Uint8: + case Uint8Clamped: + case Int16: + case Uint16: + case Int32: + case Uint32: + case Int64: + case BigInt64: + case BigUint64: + return false; + case Float32: + case Float64: + case Simd128: + return true; + case MaxTypedArrayViewType: + break; + } + MOZ_CRASH("invalid scalar type"); +} + +static inline const char* name(Type atype) { + switch (atype) { + case Int8: + return "Int8"; + case Uint8: + return "Uint8"; + case Int16: + return "Int16"; + case Uint16: + return "Uint16"; + case Int32: + return "Int32"; + case Uint32: + return "Uint32"; + case Float32: + return "Float32"; + case Float64: + return "Float64"; + case Uint8Clamped: + return "Uint8Clamped"; + case BigInt64: + return "BigInt64"; + case BigUint64: + return "BigUint64"; + case MaxTypedArrayViewType: + return "MaxTypedArrayViewType"; + case Int64: + return "Int64"; + case Simd128: + return "Simd128"; + } + MOZ_CRASH("invalid scalar type"); +} + +static inline const char* byteSizeString(Type atype) { + switch (atype) { + case Int8: + case Uint8: + case Uint8Clamped: + return "1"; + case Int16: + case Uint16: + return "2"; + case Int32: + case Uint32: + case Float32: + return "4"; + case Int64: + case Float64: + case BigInt64: + case BigUint64: + return "8"; + case Simd128: + return "16"; + case MaxTypedArrayViewType: + break; + } + MOZ_CRASH("invalid scalar type"); +} + +} // namespace Scalar + +} // namespace JS + +namespace js { + +// This is aliased in NamespaceImports.h, but that is internal-only and +// inaccessible to Gecko code, which uses this type fairly heavily. Until such +// uses are changed, we need the alias here as well. +namespace Scalar = JS::Scalar; + +} // namespace js + +#endif // js_ScalarType_h diff --git a/js/public/ScriptPrivate.h b/js/public/ScriptPrivate.h new file mode 100644 index 0000000000..1c7f1801fe --- /dev/null +++ b/js/public/ScriptPrivate.h @@ -0,0 +1,51 @@ +/* -*- 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 js_ScriptPrivate_h +#define js_ScriptPrivate_h + +#include "jstypes.h" + +#include "js/TypeDecls.h" + +namespace JS { + +/** + * Set a private value associated with a script. Note that this value is shared + * by all nested scripts compiled from a single source file. + */ +extern JS_PUBLIC_API void SetScriptPrivate(JSScript* script, + const JS::Value& value); + +/** + * Get the private value associated with a script. Note that this value is + * shared by all nested scripts compiled from a single source file. + */ +extern JS_PUBLIC_API JS::Value GetScriptPrivate(JSScript* script); + +/** + * Return the private value associated with currently executing script or + * module, or undefined if there is no such script. + */ +extern JS_PUBLIC_API JS::Value GetScriptedCallerPrivate(JSContext* cx); + +/** + * Hooks called when references to a script private value are created or + * destroyed. This allows use of a reference counted object as the + * script private. + */ +using ScriptPrivateReferenceHook = void (*)(const JS::Value&); + +/** + * Set the script private finalize hook for the runtime to the given function. + */ +extern JS_PUBLIC_API void SetScriptPrivateReferenceHooks( + JSRuntime* rt, ScriptPrivateReferenceHook addRefHook, + ScriptPrivateReferenceHook releaseHook); + +} // namespace JS + +#endif // js_ScriptPrivate_h diff --git a/js/public/ShadowRealmCallbacks.h b/js/public/ShadowRealmCallbacks.h new file mode 100644 index 0000000000..bbd01b9d8c --- /dev/null +++ b/js/public/ShadowRealmCallbacks.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_ShadowReamCallbacks_h +#define js_ShadowReamCallbacks_h + +#include "jstypes.h" +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" + +struct JS_PUBLIC_API JSContext; + +namespace JS { + +class RealmOptions; + +using GlobalInitializeCallback = bool (*)(JSContext*, JS::Handle<JSObject*>); + +// Install the HostInitializeShadowRealm callback that will be invoked when +// creating a shadow realm. +// +// The callback will be passed the realm's global object, so that it is possible +// for the embedding to make any host-determined manipulations to the global, +// such as installing interfaces or helpers that should exist even within +// ShadowRealms. (For example, in the web platform, WebIDL with the +// [Exposed=*] attribute should be installed within a shadow realm.) +extern JS_PUBLIC_API void SetShadowRealmInitializeGlobalCallback( + JSContext* cx, GlobalInitializeCallback callback); + +using GlobalCreationCallback = + JSObject* (*)(JSContext* cx, JS::RealmOptions& creationOptions, + JSPrincipals* principals, + JS::Handle<JSObject*> enclosingGlobal); + +// Create the Global object for a ShadowRealm. +// +// This isn't directly specified, however at least in Gecko, in order to +// correctly implement HostInitializeShadowRealm, there are requirements +// placed on the global for the ShadowRealm. +// +// This callback should return a Global object compatible with the +// callback installed by SetShadowRealmInitializeGlobalCallback +extern JS_PUBLIC_API void SetShadowRealmGlobalCreationCallback( + JSContext* cx, GlobalCreationCallback callback); + +} // namespace JS + +#endif // js_ShadowReamCallbacks_h diff --git a/js/public/SharedArrayBuffer.h b/js/public/SharedArrayBuffer.h new file mode 100644 index 0000000000..f427b7c806 --- /dev/null +++ b/js/public/SharedArrayBuffer.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* ArrayBuffer functionality. */ + +#ifndef js_SharedArrayBuffer_h +#define js_SharedArrayBuffer_h + +#include <stddef.h> // size_t +#include <stdint.h> // uint8_t + +#include "jstypes.h" // JS_PUBLIC_API + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; + +namespace JS { +class JS_PUBLIC_API AutoRequireNoGC; + +// CREATION + +/** + * Create a new SharedArrayBuffer with the given byte length. This + * may only be called if + * JS::RealmCreationOptionsRef(cx).getSharedMemoryAndAtomicsEnabled() is + * true. + */ +extern JS_PUBLIC_API JSObject* NewSharedArrayBuffer(JSContext* cx, + size_t nbytes); + +// TYPE TESTING + +/** + * Check whether obj supports the JS::GetSharedArrayBuffer* APIs. Note that + * this may return false if a security wrapper is encountered that denies the + * unwrapping. If this test succeeds, then it is safe to call the various + * accessor JSAPI calls defined below. + */ +extern JS_PUBLIC_API bool IsSharedArrayBufferObject(JSObject* obj); + +// ACCESSORS + +extern JS_PUBLIC_API JSObject* UnwrapSharedArrayBuffer(JSObject* obj); + +extern JS_PUBLIC_API size_t GetSharedArrayBufferByteLength(JSObject* obj); + +extern JS_PUBLIC_API uint8_t* GetSharedArrayBufferData(JSObject* obj, + bool* isSharedMemory, + const AutoRequireNoGC&); + +// Ditto for SharedArrayBuffer. +// +// There is an isShared out argument for API consistency (eases use from DOM). +// It will always be set to true. +extern JS_PUBLIC_API void GetSharedArrayBufferLengthAndData( + JSObject* obj, size_t* length, bool* isSharedMemory, uint8_t** data); + +/** + * Returns true if there are any live SharedArrayBuffer objects, including those + * for wasm memories, associated with the context. This is conservative, + * because it does not run GC. Some dead objects may not have been collected + * yet and thus will be thought live. + */ +extern JS_PUBLIC_API bool ContainsSharedArrayBuffer(JSContext* cx); + +/** + * Return the isShared flag of a ArrayBufferView subtypes, which denotes whether + * the underlying buffer is a SharedArrayBuffer. + * + * |obj| must have passed a JS_IsArrayBufferViewObject test, or somehow + * be known that it would pass such a test: it is a ArrayBufferView subtypes or + * a wrapper of a ArrayBufferView subtypes, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API bool IsArrayBufferViewShared(JSObject* obj); + +} // namespace JS + +#endif /* js_SharedArrayBuffer_h */ diff --git a/js/public/SliceBudget.h b/js/public/SliceBudget.h new file mode 100644 index 0000000000..0d7427bf0b --- /dev/null +++ b/js/public/SliceBudget.h @@ -0,0 +1,144 @@ +/* -*- 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 js_SliceBudget_h +#define js_SliceBudget_h + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Variant.h" + +#include <stdint.h> + +#include "jstypes.h" + +namespace js { + +struct JS_PUBLIC_API TimeBudget { + const mozilla::TimeDuration budget; + mozilla::TimeStamp deadline; // Calculated when SliceBudget is constructed. + + explicit TimeBudget(mozilla::TimeDuration duration) : budget(duration) {} + explicit TimeBudget(int64_t milliseconds) + : budget(mozilla::TimeDuration::FromMilliseconds(milliseconds)) {} + + void setDeadlineFromNow(); +}; + +struct JS_PUBLIC_API WorkBudget { + const int64_t budget; + + explicit WorkBudget(int64_t work) : budget(work) {} +}; + +struct UnlimitedBudget {}; + +/* + * This class describes a limit to the amount of work to be performed in a GC + * slice, so that we can return to the mutator without pausing for too long. The + * budget may be based on a deadline time or an amount of work to be performed, + * or may be unlimited. + * + * To reduce the number of gettimeofday calls, we only check the time every 1000 + * operations. + */ +class JS_PUBLIC_API SliceBudget { + public: + using InterruptRequestFlag = mozilla::Atomic<bool, mozilla::Relaxed>; + + // Whether this slice is running in (predicted to be) idle time. + // Only used for recording in the profile. + bool idle = false; + + // Whether this slice was given an extended budget, larger than + // the predicted idle time. + bool extended = false; + + private: + static const intptr_t UnlimitedCounter = INTPTR_MAX; + + // Most calls to isOverBudget will only check the counter value. Every N + // steps, do a more "expensive" check -- look at the current time and/or + // check the atomic interrupt flag. + static constexpr intptr_t StepsPerExpensiveCheck = 1000; + + // Configuration + + mozilla::Variant<TimeBudget, WorkBudget, UnlimitedBudget> budget; + + // External flag to request the current slice to be interrupted + // (and return isOverBudget() early.) Applies only to time-based budgets. + InterruptRequestFlag* interruptRequested = nullptr; + + // How many steps to count before checking the time and possibly the interrupt + // flag. + int64_t counter = StepsPerExpensiveCheck; + + // This SliceBudget is considered interrupted from the time isOverBudget() + // finds the interrupt flag set. + bool interrupted = false; + + explicit SliceBudget(InterruptRequestFlag* irqPtr) + : budget(UnlimitedBudget()), + interruptRequested(irqPtr), + counter(irqPtr ? StepsPerExpensiveCheck : UnlimitedCounter) {} + + bool checkOverBudget(); + + public: + // Use to create an unlimited budget. + static SliceBudget unlimited() { return SliceBudget(nullptr); } + + // Instantiate as SliceBudget(TimeBudget(n)). + explicit SliceBudget(TimeBudget time, + InterruptRequestFlag* interrupt = nullptr); + + explicit SliceBudget(mozilla::TimeDuration duration, + InterruptRequestFlag* interrupt = nullptr) + : SliceBudget(TimeBudget(duration.ToMilliseconds()), interrupt) {} + + // Instantiate as SliceBudget(WorkBudget(n)). + explicit SliceBudget(WorkBudget work); + + // Register having performed the given number of steps (counted against a + // work budget, or progress towards the next time or callback check). + void step(uint64_t steps = 1) { + MOZ_ASSERT(steps > 0); + counter -= steps; + } + + // Do enough steps to force an "expensive" (time and/or callback) check on + // the next call to isOverBudget. Useful when switching between major phases + // of an operation like a cycle collection. + void stepAndForceCheck() { + if (!isUnlimited()) { + counter = 0; + } + } + + bool isOverBudget() { return counter <= 0 && checkOverBudget(); } + + bool isWorkBudget() const { return budget.is<WorkBudget>(); } + bool isTimeBudget() const { return budget.is<TimeBudget>(); } + bool isUnlimited() const { return budget.is<UnlimitedBudget>(); } + + mozilla::TimeDuration timeBudgetDuration() const { + return budget.as<TimeBudget>().budget; + } + int64_t timeBudget() const { return timeBudgetDuration().ToMilliseconds(); } + int64_t workBudget() const { return budget.as<WorkBudget>().budget; } + + mozilla::TimeStamp deadline() const { + return budget.as<TimeBudget>().deadline; + } + + int describe(char* buffer, size_t maxlen) const; +}; + +} // namespace js + +#endif /* js_SliceBudget_h */ diff --git a/js/public/SourceText.h b/js/public/SourceText.h new file mode 100644 index 0000000000..88592be875 --- /dev/null +++ b/js/public/SourceText.h @@ -0,0 +1,353 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * SourceText encapsulates a count of char16_t (UTF-16) or Utf8Unit (UTF-8) + * code units (note: code *units*, not bytes or code points) and those code + * units ("source units"). (Latin-1 is not supported: all places where Latin-1 + * must be compiled first convert to a supported encoding.) + * + * A SourceText either observes without owning, or takes ownership of, source + * units passed to |SourceText::init|. Thus SourceText can be used to + * efficiently avoid copying. + * + * Rules for use: + * + * 1) The passed-in source units must be allocated with js_malloc(), + * js_calloc(), or js_realloc() if |SourceText::init| is instructed to take + * ownership of the source units. + * 2) If |SourceText::init| merely borrows the source units, the user must + * keep them alive until associated JS compilation is complete. + * 3) Code that calls |SourceText::take{Chars,Units}()| must keep the source + * units alive until JS compilation completes. Normally only the JS engine + * should call |SourceText::take{Chars,Units}()|. + * 4) Use the appropriate SourceText parameterization depending on the source + * units encoding. + * + * Example use: + * + * size_t length = 512; + * char16_t* chars = js_pod_malloc<char16_t>(length); + * if (!chars) { + * JS_ReportOutOfMemory(cx); + * return false; + * } + * JS::SourceText<char16_t> srcBuf; + * if (!srcBuf.init(cx, chars, length, JS::SourceOwnership::TakeOwnership)) { + * return false; + * } + * JS::Rooted<JSScript*> script(cx); + * if (!JS::Compile(cx, options, srcBuf, &script)) { + * return false; + * } + */ + +#ifndef js_SourceText_h +#define js_SourceText_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_COLD, MOZ_IS_CLASS_INIT +#include "mozilla/Likely.h" // MOZ_UNLIKELY + +#include <stddef.h> // size_t +#include <stdint.h> // UINT32_MAX +#include <type_traits> // std::conditional_t, std::is_same_v + +#include "js/UniquePtr.h" // js::UniquePtr +#include "js/Utility.h" // JS::FreePolicy + +namespace mozilla { +union Utf8Unit; +} + +namespace js { +class FrontendContext; +} // namespace js + +namespace JS { + +class JS_PUBLIC_API AutoStableStringChars; +using FrontendContext = js::FrontendContext; + +namespace detail { + +MOZ_COLD extern JS_PUBLIC_API void ReportSourceTooLong(JSContext* cx); +MOZ_COLD extern JS_PUBLIC_API void ReportSourceTooLong(JS::FrontendContext* fc); + +} // namespace detail + +enum class SourceOwnership { + Borrowed, + TakeOwnership, +}; + +template <typename Unit> +class SourceText final { + private: + static_assert(std::is_same_v<Unit, mozilla::Utf8Unit> || + std::is_same_v<Unit, char16_t>, + "Unit must be either char16_t or Utf8Unit for " + "SourceText<Unit>"); + + /** |char16_t| or |Utf8Unit| source units of uncertain validity. */ + const Unit* units_ = nullptr; + + /** The length in code units of |units_|. */ + uint32_t length_ = 0; + + /** + * Whether this owns |units_| or merely observes source units owned by some + * other object. + */ + bool ownsUnits_ = false; + + public: + // A C++ character type that can represent the source units -- suitable for + // passing to C++ string functions. + using CharT = + std::conditional_t<std::is_same_v<Unit, char16_t>, char16_t, char>; + + public: + /** + * Construct a SourceText. It must be initialized using |init()| before it + * can be used as compilation source text. + */ + SourceText() = default; + + /** + * Construct a SourceText from contents extracted from |other|. This + * SourceText will then act exactly as |other| would have acted, had it + * not been passed to this function. |other| will return to its default- + * constructed state and must have |init()| called on it to use it. + */ + SourceText(SourceText&& other) + : units_(other.units_), + length_(other.length_), + ownsUnits_(other.ownsUnits_) { + other.units_ = nullptr; + other.length_ = 0; + other.ownsUnits_ = false; + } + + ~SourceText() { + if (ownsUnits_) { + js_free(const_cast<Unit*>(units_)); + } + } + + private: + template <typename ContextT> + [[nodiscard]] MOZ_IS_CLASS_INIT bool initImpl(ContextT* context, + const Unit* units, + size_t unitsLength, + SourceOwnership ownership) { + MOZ_ASSERT_IF(units == nullptr, unitsLength == 0); + + // Ideally we'd use |Unit| and not cast below, but the risk of a static + // initializer is too great. + static const CharT emptyString[] = {'\0'}; + + // Initialize all fields *before* checking length. This ensures that + // if |ownership == SourceOwnership::TakeOwnership|, |units| will be + // freed when |this|'s destructor is called. + if (units) { + units_ = units; + length_ = static_cast<uint32_t>(unitsLength); + ownsUnits_ = ownership == SourceOwnership::TakeOwnership; + } else { + units_ = reinterpret_cast<const Unit*>(emptyString); + length_ = 0; + ownsUnits_ = false; + } + + // IMPLEMENTATION DETAIL, DO NOT RELY ON: This limit is used so we can + // store offsets in |JSScript|s as |uint32_t|. It could be lifted + // fairly easily if desired, as the compiler uses |size_t| internally. + if (MOZ_UNLIKELY(unitsLength > UINT32_MAX)) { + detail::ReportSourceTooLong(context); + return false; + } + + return true; + } + + public: + /** + * Initialize this with source unit data: |char16_t| for UTF-16 source + * units, or |Utf8Unit| for UTF-8 source units. + * + * If |ownership == TakeOwnership|, *this function* takes ownership of + * |units|, *even if* this function fails, and you MUST NOT free |units| + * yourself. This single-owner-friendly approach reduces risk of leaks on + * failure. + * + * |units| may be null if |unitsLength == 0|; if so, this will silently be + * initialized using non-null, unowned units. + */ + [[nodiscard]] MOZ_IS_CLASS_INIT bool init(JSContext* cx, const Unit* units, + size_t unitsLength, + SourceOwnership ownership) { + return initImpl(cx, units, unitsLength, ownership); + } + [[nodiscard]] MOZ_IS_CLASS_INIT bool init(JS::FrontendContext* fc, + const Unit* units, + size_t unitsLength, + SourceOwnership ownership) { + return initImpl(fc, units, unitsLength, ownership); + } + + /** + * Exactly identical to the |init()| overload above that accepts + * |const Unit*|, but instead takes character data: |const CharT*|. + * + * (We can't just write this to accept |const CharT*|, because then in the + * UTF-16 case this overload and the one above would be identical. So we + * use SFINAE to expose the |CharT| overload only if it's different.) + */ + template <typename Char, + typename = std::enable_if_t<std::is_same_v<Char, CharT> && + !std::is_same_v<Char, Unit>>> + [[nodiscard]] MOZ_IS_CLASS_INIT bool init(JSContext* cx, const Char* chars, + size_t charsLength, + SourceOwnership ownership) { + return initImpl(cx, reinterpret_cast<const Unit*>(chars), charsLength, + ownership); + } + template <typename Char, + typename = std::enable_if_t<std::is_same_v<Char, CharT> && + !std::is_same_v<Char, Unit>>> + [[nodiscard]] MOZ_IS_CLASS_INIT bool init(JS::FrontendContext* fc, + const Char* chars, + size_t charsLength, + SourceOwnership ownership) { + return initImpl(fc, reinterpret_cast<const Unit*>(chars), charsLength, + ownership); + } + + /** + * Initialize this using source units transferred out of |data|. + */ + [[nodiscard]] bool init(JSContext* cx, + js::UniquePtr<Unit[], JS::FreePolicy> data, + size_t dataLength) { + return initImpl(cx, data.release(), dataLength, + SourceOwnership::TakeOwnership); + } + [[nodiscard]] bool init(JS::FrontendContext* fc, + js::UniquePtr<Unit[], JS::FreePolicy> data, + size_t dataLength) { + return initImpl(fc, data.release(), dataLength, + SourceOwnership::TakeOwnership); + } + + /** + * Exactly identical to the |init()| overload above that accepts + * |UniquePtr<Unit[], JS::FreePolicy>|, but instead takes character data: + * |UniquePtr<CharT[], JS::FreePolicy>|. + * + * (We can't just duplicate the signature above with s/Unit/CharT/, because + * then in the UTF-16 case this overload and the one above would be identical. + * So we use SFINAE to expose the |CharT| overload only if it's different.) + */ + template <typename Char, + typename = std::enable_if_t<std::is_same_v<Char, CharT> && + !std::is_same_v<Char, Unit>>> + [[nodiscard]] bool init(JSContext* cx, + js::UniquePtr<Char[], JS::FreePolicy> data, + size_t dataLength) { + return init(cx, data.release(), dataLength, SourceOwnership::TakeOwnership); + } + template <typename Char, + typename = std::enable_if_t<std::is_same_v<Char, CharT> && + !std::is_same_v<Char, Unit>>> + [[nodiscard]] bool init(JS::FrontendContext* fc, + js::UniquePtr<Char[], JS::FreePolicy> data, + size_t dataLength) { + return init(fc, data.release(), dataLength, SourceOwnership::TakeOwnership); + } + + /** + * Initialize this using an AutoStableStringChars. Transfers the code units if + * they are owned by the AutoStableStringChars, otherwise borrow directly from + * the underlying JSString. The AutoStableStringChars must outlive this + * SourceText and must be explicitly configured to the same unit type as this + * SourceText. + */ + [[nodiscard]] bool initMaybeBorrowed(JSContext* cx, + AutoStableStringChars& linearChars); + [[nodiscard]] bool initMaybeBorrowed(JS::FrontendContext* fc, + AutoStableStringChars& linearChars); + + /** + * Access the encapsulated data using a code unit type. + * + * This function is useful for code that wants to interact with source text + * as *code units*, not as string data. This doesn't matter for UTF-16, + * but it's a crucial distinction for UTF-8. When UTF-8 source text is + * encapsulated, |Unit| being |mozilla::Utf8Unit| unambiguously indicates + * that the code units are UTF-8. In contrast |const char*| returned by + * |get()| below could hold UTF-8 (or its ASCII subset) or Latin-1 or (in + * particularly cursed embeddings) EBCDIC or some other legacy character + * set. Prefer this function to |get()| wherever possible. + */ + const Unit* units() const { return units_; } + + /** + * Access the encapsulated data using a character type. + * + * This function is useful for interactions with character-centric actions + * like interacting with UniqueChars/UniqueTwoByteChars or printing out + * text in a debugger, that only work with |CharT|. But as |CharT| loses + * encoding specificity when UTF-8 source text is encapsulated, prefer + * |units()| to this function. + */ + const CharT* get() const { return reinterpret_cast<const CharT*>(units_); } + + /** + * Returns true if this owns the source units and will free them on + * destruction. If true, it is legal to call |take{Chars,Units}()|. + */ + bool ownsUnits() const { return ownsUnits_; } + + /** + * Count of the underlying source units -- code units, not bytes or code + * points -- in this. + */ + uint32_t length() const { return length_; } + + /** + * Retrieve and take ownership of the underlying source units. The caller + * is now responsible for calling js_free() on the returned value, *but + * only after JS script compilation has completed*. + * + * After underlying source units have been taken, this will continue to + * refer to the same data -- it just won't own the data. get() and + * length() will return the same values, but ownsUnits() will be false. + * The taken source units must be kept alive until after JS script + * compilation completes, as noted above, for this to be safe. + * + * The caller must check ownsUnits() before calling takeUnits(). Taking + * and then free'ing an unowned buffer will have dire consequences. + */ + Unit* takeUnits() { + MOZ_ASSERT(ownsUnits_); + ownsUnits_ = false; + return const_cast<Unit*>(units_); + } + + /** + * Akin to |takeUnits()| in all respects, but returns characters rather + * than units. + */ + CharT* takeChars() { return reinterpret_cast<CharT*>(takeUnits()); } + + private: + SourceText(const SourceText&) = delete; + void operator=(const SourceText&) = delete; +}; + +} // namespace JS + +#endif /* js_SourceText_h */ diff --git a/js/public/StableStringChars.h b/js/public/StableStringChars.h new file mode 100644 index 0000000000..a12813d89c --- /dev/null +++ b/js/public/StableStringChars.h @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * Safely access the contents of a string even as GC can cause the string's + * contents to move around in memory. + */ + +#ifndef js_StableStringChars_h +#define js_StableStringChars_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_INIT_OUTSIDE_CTOR, MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/Range.h" // mozilla::Range + +#include <stddef.h> // size_t +#include <stdint.h> // uint8_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/AllocPolicy.h" +#include "js/RootingAPI.h" // JS::Handle, JS::Rooted +#include "js/String.h" // JS::GetStringLength +#include "js/TypeDecls.h" // JSContext, JS::Latin1Char, JSString +#include "js/Vector.h" // js::Vector + +class JSLinearString; + +namespace JS { + +/** + * This class provides safe access to a string's chars across a GC. If we ever + * nursery allocate strings' out of line chars, this class will have to make a + * copy, so it's best to avoid using this class unless you really need it. It's + * usually more efficient to use the latin1Chars/twoByteChars JSString methods + * and often the code can be rewritten so that only indexes instead of char + * pointers are used in parts of the code that can GC. + */ +class MOZ_STACK_CLASS JS_PUBLIC_API AutoStableStringChars final { + /* + * When copying string char, use this many bytes of inline storage. This is + * chosen to allow the inline string types to be copied without allocating. + * This is asserted in AutoStableStringChars::allocOwnChars. + */ + static const size_t InlineCapacity = 24; + + /* Ensure the string is kept alive while we're using its chars. */ + Rooted<JSString*> s_; + union MOZ_INIT_OUTSIDE_CTOR { + const char16_t* twoByteChars_; + const Latin1Char* latin1Chars_; + }; + mozilla::Maybe<js::Vector<uint8_t, InlineCapacity>> ownChars_; + enum State { Uninitialized, Latin1, TwoByte }; + State state_; + + public: + explicit AutoStableStringChars(JSContext* cx) + : s_(cx), state_(Uninitialized) {} + + [[nodiscard]] bool init(JSContext* cx, JSString* s); + + /* Like init(), but Latin1 chars are inflated to TwoByte. */ + [[nodiscard]] bool initTwoByte(JSContext* cx, JSString* s); + + bool isLatin1() const { return state_ == Latin1; } + bool isTwoByte() const { return state_ == TwoByte; } + + const Latin1Char* latin1Chars() const { + MOZ_ASSERT(state_ == Latin1); + return latin1Chars_; + } + const char16_t* twoByteChars() const { + MOZ_ASSERT(state_ == TwoByte); + return twoByteChars_; + } + + mozilla::Range<const Latin1Char> latin1Range() const { + MOZ_ASSERT(state_ == Latin1); + return mozilla::Range<const Latin1Char>(latin1Chars_, length()); + } + + mozilla::Range<const char16_t> twoByteRange() const { + MOZ_ASSERT(state_ == TwoByte); + return mozilla::Range<const char16_t>(twoByteChars_, length()); + } + + /* If we own the chars, transfer ownership to the caller. */ + bool maybeGiveOwnershipToCaller() { + MOZ_ASSERT(state_ != Uninitialized); + if (!ownChars_.isSome() || !ownChars_->extractRawBuffer()) { + return false; + } + state_ = Uninitialized; + ownChars_.reset(); + return true; + } + + size_t length() const { return GetStringLength(s_); } + + private: + AutoStableStringChars(const AutoStableStringChars& other) = delete; + void operator=(const AutoStableStringChars& other) = delete; + + bool baseIsInline(Handle<JSLinearString*> linearString); + template <typename T> + T* allocOwnChars(JSContext* cx, size_t count); + bool copyLatin1Chars(JSContext* cx, Handle<JSLinearString*> linearString); + bool copyTwoByteChars(JSContext* cx, Handle<JSLinearString*> linearString); + bool copyAndInflateLatin1Chars(JSContext*, + Handle<JSLinearString*> linearString); +}; + +} // namespace JS + +#endif /* js_StableStringChars_h */ diff --git a/js/public/Stack.h b/js/public/Stack.h new file mode 100644 index 0000000000..6f01b2b728 --- /dev/null +++ b/js/public/Stack.h @@ -0,0 +1,234 @@ +/* -*- 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 js_Stack_h +#define js_Stack_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/Variant.h" // mozilla::Variant + +#include <stddef.h> // size_t +#include <stdint.h> // uint32_t, uintptr_t, UINTPTR_MAX +#include <utility> // std::move + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Principals.h" // JSPrincipals, JS_HoldPrincipals, JS_DropPrincipals +#include "js/TypeDecls.h" // JSContext, Handle*, MutableHandle* + +namespace JS { + +using NativeStackSize = size_t; + +using NativeStackBase = uintptr_t; + +using NativeStackLimit = uintptr_t; + +#if JS_STACK_GROWTH_DIRECTION > 0 +constexpr NativeStackLimit NativeStackLimitMin = 0; +constexpr NativeStackLimit NativeStackLimitMax = UINTPTR_MAX; +#else +constexpr NativeStackLimit NativeStackLimitMin = UINTPTR_MAX; +constexpr NativeStackLimit NativeStackLimitMax = 0; +#endif + +#ifdef __wasi__ +// We build with the "stack-first" wasm-ld option, so the stack grows downward +// toward zero. Let's set a limit just a bit above this so that we catch an +// overflow before a Wasm trap occurs. +constexpr NativeStackLimit WASINativeStackLimit = 1024; +#endif // __wasi__ + +inline NativeStackLimit GetNativeStackLimit(NativeStackBase base, + NativeStackSize size) { +#if JS_STACK_GROWTH_DIRECTION > 0 + MOZ_ASSERT(base <= size_t(-1) - size); + return base + size - 1; +#else // stack grows up + MOZ_ASSERT(base >= size); + return base - (size - 1); +#endif // stack grows down +} + +} // namespace JS + +/** + * Set the size of the native stack that should not be exceed. To disable + * stack size checking pass 0. + * + * SpiderMonkey allows for a distinction between system code (such as GCs, which + * may incidentally be triggered by script but are not strictly performed on + * behalf of such script), trusted script (as determined by + * JS_SetTrustedPrincipals), and untrusted script. Each kind of code may have a + * different stack quota, allowing embedders to keep higher-priority machinery + * running in the face of scripted stack exhaustion by something else. + * + * The stack quotas for each kind of code should be monotonically descending, + * and may be specified with this function. If 0 is passed for a given kind + * of code, it defaults to the value of the next-highest-priority kind. + * + * This function may only be called immediately after the runtime is initialized + * and before any code is executed and/or interrupts requested. + */ +extern JS_PUBLIC_API void JS_SetNativeStackQuota( + JSContext* cx, JS::NativeStackSize systemCodeStackSize, + JS::NativeStackSize trustedScriptStackSize = 0, + JS::NativeStackSize untrustedScriptStackSize = 0); + +namespace js { + +enum class StackFormat { SpiderMonkey, V8, Default }; + +/* + * Sets the format used for stringifying Error stacks. + * + * The default format is StackFormat::SpiderMonkey. Use StackFormat::V8 + * in order to emulate V8's stack formatting. StackFormat::Default can't be + * used here. + */ +extern JS_PUBLIC_API void SetStackFormat(JSContext* cx, StackFormat format); + +extern JS_PUBLIC_API StackFormat GetStackFormat(JSContext* cx); + +} // namespace js + +namespace JS { + +/** + * Capture all frames. + */ +struct AllFrames {}; + +/** + * Capture at most this many frames. + */ +struct MaxFrames { + uint32_t maxFrames; + + explicit MaxFrames(uint32_t max) : maxFrames(max) { MOZ_ASSERT(max > 0); } +}; + +/** + * Capture the first frame with the given principals. By default, do not + * consider self-hosted frames with the given principals as satisfying the stack + * capture. + */ +struct JS_PUBLIC_API FirstSubsumedFrame { + JSContext* cx; + JSPrincipals* principals; + bool ignoreSelfHosted; + + /** + * Use the cx's current compartment's principals. + */ + explicit FirstSubsumedFrame(JSContext* cx, + bool ignoreSelfHostedFrames = true); + + explicit FirstSubsumedFrame(JSContext* ctx, JSPrincipals* p, + bool ignoreSelfHostedFrames = true) + : cx(ctx), principals(p), ignoreSelfHosted(ignoreSelfHostedFrames) { + if (principals) { + JS_HoldPrincipals(principals); + } + } + + // No copying because we want to avoid holding and dropping principals + // unnecessarily. + FirstSubsumedFrame(const FirstSubsumedFrame&) = delete; + FirstSubsumedFrame& operator=(const FirstSubsumedFrame&) = delete; + + FirstSubsumedFrame(FirstSubsumedFrame&& rhs) + : principals(rhs.principals), ignoreSelfHosted(rhs.ignoreSelfHosted) { + MOZ_ASSERT(this != &rhs, "self move disallowed"); + rhs.principals = nullptr; + } + + FirstSubsumedFrame& operator=(FirstSubsumedFrame&& rhs) { + new (this) FirstSubsumedFrame(std::move(rhs)); + return *this; + } + + ~FirstSubsumedFrame() { + if (principals) { + JS_DropPrincipals(cx, principals); + } + } +}; + +using StackCapture = mozilla::Variant<AllFrames, MaxFrames, FirstSubsumedFrame>; + +/** + * Capture the current call stack as a chain of SavedFrame JSObjects, and set + * |stackp| to the SavedFrame for the youngest stack frame, or nullptr if there + * are no JS frames on the stack. + * + * The |capture| parameter describes the portion of the JS stack to capture: + * + * * |JS::AllFrames|: Capture all frames on the stack. + * + * * |JS::MaxFrames|: Capture no more than |JS::MaxFrames::maxFrames| from the + * stack. + * + * * |JS::FirstSubsumedFrame|: Capture the first frame whose principals are + * subsumed by |JS::FirstSubsumedFrame::principals|. By default, do not + * consider self-hosted frames; this can be controlled via the + * |JS::FirstSubsumedFrame::ignoreSelfHosted| flag. Do not capture any async + * stack. + */ +extern JS_PUBLIC_API bool CaptureCurrentStack( + JSContext* cx, MutableHandleObject stackp, + StackCapture&& capture = StackCapture(AllFrames())); + +/** + * Returns true if capturing stack trace data to associate with an asynchronous + * operation is currently enabled for the current context realm. + * + * Users should check this state before capturing a stack that will be passed + * back to AutoSetAsyncStackForNewCalls later, in order to avoid capturing a + * stack for async use when we don't actually want to capture it. + */ +extern JS_PUBLIC_API bool IsAsyncStackCaptureEnabledForRealm(JSContext* cx); + +/* + * This is a utility function for preparing an async stack to be used + * by some other object. This may be used when you need to treat a + * given stack trace as an async parent. If you just need to capture + * the current stack, async parents and all, use CaptureCurrentStack + * instead. + * + * Here |asyncStack| is the async stack to prepare. It is copied into + * |cx|'s current compartment, and the newest frame is given + * |asyncCause| as its asynchronous cause. If |maxFrameCount| is + * |Some(n)|, capture at most the youngest |n| frames. The + * new stack object is written to |stackp|. Returns true on success, + * or sets an exception and returns |false| on error. + */ +extern JS_PUBLIC_API bool CopyAsyncStack( + JSContext* cx, HandleObject asyncStack, HandleString asyncCause, + MutableHandleObject stackp, const mozilla::Maybe<size_t>& maxFrameCount); + +/** + * Given a SavedFrame JSObject stack, stringify it in the same format as + * Error.prototype.stack. The stringified stack out parameter is placed in the + * cx's compartment. Defaults to the empty string. + * + * The same notes above about SavedFrame accessors applies here as well: cx + * doesn't need to be in stack's compartment, and stack can be null, a + * SavedFrame object, or a wrapper (CCW or Xray) around a SavedFrame object. + * SavedFrames not subsumed by |principals| are skipped. + * + * Optional indent parameter specifies the number of white spaces to indent + * each line. + */ +extern JS_PUBLIC_API bool BuildStackString( + JSContext* cx, JSPrincipals* principals, HandleObject stack, + MutableHandleString stringp, size_t indent = 0, + js::StackFormat stackFormat = js::StackFormat::Default); + +} // namespace JS + +#endif // js_Stack_h diff --git a/js/public/StreamConsumer.h b/js/public/StreamConsumer.h new file mode 100644 index 0000000000..c05f20ae7e --- /dev/null +++ b/js/public/StreamConsumer.h @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_StreamConsumer_h +#define js_StreamConsumer_h + +#include "mozilla/Attributes.h" +#include "mozilla/RefCountType.h" + +#include <stddef.h> +#include <stdint.h> + +#include "jstypes.h" + +#include "js/AllocPolicy.h" +#include "js/TypeDecls.h" +#include "js/UniquePtr.h" +#include "js/Vector.h" + +namespace JS { + +/** + * The ConsumeStreamCallback is called from an active JSContext, passing a + * StreamConsumer that wishes to consume the given host object as a stream of + * bytes with the given MIME type. On failure, the embedding must report the + * appropriate error on 'cx'. On success, the embedding must call + * consumer->consumeChunk() repeatedly on any thread until exactly one of: + * - consumeChunk() returns false + * - the embedding calls consumer->streamEnd() + * - the embedding calls consumer->streamError() + * before JS_DestroyContext(cx) or JS::ShutdownAsyncTasks(cx) is called. + * + * Note: consumeChunk(), streamEnd() and streamError() may be called + * synchronously by ConsumeStreamCallback. + * + * When streamEnd() is called, the embedding may optionally pass an + * OptimizedEncodingListener*, indicating that there is a cache entry associated + * with this stream that can store an optimized encoding of the bytes that were + * just streamed at some point in the future by having SpiderMonkey call + * storeOptimizedEncoding(). Until the optimized encoding is ready, SpiderMonkey + * will hold an outstanding refcount to keep the listener alive. + * + * After storeOptimizedEncoding() is called, on cache hit, the embedding + * may call consumeOptimizedEncoding() instead of consumeChunk()/streamEnd(). + * The embedding must ensure that the GetOptimizedEncodingBuildId() (see + * js/BuildId.h) at the time when an optimized encoding is created is the same + * as when it is later consumed. + */ + +class OptimizedEncodingListener { + protected: + virtual ~OptimizedEncodingListener() = default; + + public: + // SpiderMonkey will hold an outstanding reference count as long as it holds + // a pointer to OptimizedEncodingListener. + virtual MozExternalRefCountType MOZ_XPCOM_ABI AddRef() = 0; + virtual MozExternalRefCountType MOZ_XPCOM_ABI Release() = 0; + + // SpiderMonkey may optionally call storeOptimizedEncoding() after it has + // finished processing a streamed resource. + virtual void storeOptimizedEncoding(const uint8_t* bytes, size_t length) = 0; +}; + +class JS_PUBLIC_API StreamConsumer { + protected: + // AsyncStreamConsumers are created and destroyed by SpiderMonkey. + StreamConsumer() = default; + virtual ~StreamConsumer() = default; + + public: + // Called by the embedding as each chunk of bytes becomes available. + // If this function returns 'false', the stream must drop all pointers to + // this StreamConsumer. + virtual bool consumeChunk(const uint8_t* begin, size_t length) = 0; + + // Called by the embedding when the stream reaches end-of-file, passing the + // listener described above. + virtual void streamEnd(OptimizedEncodingListener* listener = nullptr) = 0; + + // Called by the embedding when there is an error during streaming. The + // given error code should be passed to the ReportStreamErrorCallback on the + // main thread to produce the semantically-correct rejection value. + virtual void streamError(size_t errorCode) = 0; + + // Called by the embedding *instead of* consumeChunk()/streamEnd() if an + // optimized encoding is available from a previous streaming of the same + // contents with the same optimized build id. + virtual void consumeOptimizedEncoding(const uint8_t* begin, + size_t length) = 0; + + // Provides optional stream attributes such as base or source mapping URLs. + // Necessarily called before consumeChunk(), streamEnd(), streamError() or + // consumeOptimizedEncoding(). The caller retains ownership of the strings. + virtual void noteResponseURLs(const char* maybeUrl, + const char* maybeSourceMapUrl) = 0; +}; + +enum class MimeType { Wasm }; + +using ConsumeStreamCallback = bool (*)(JSContext*, JS::HandleObject, MimeType, + StreamConsumer*); + +using ReportStreamErrorCallback = void (*)(JSContext*, size_t); + +extern JS_PUBLIC_API void InitConsumeStreamCallback( + JSContext* cx, ConsumeStreamCallback consume, + ReportStreamErrorCallback report); + +} // namespace JS + +#endif // js_StreamConsumer_h diff --git a/js/public/String.h b/js/public/String.h new file mode 100644 index 0000000000..43c710235f --- /dev/null +++ b/js/public/String.h @@ -0,0 +1,538 @@ +/* -*- 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/. */ + +/* JavaScript string operations. */ + +#ifndef js_String_h +#define js_String_h + +#include "js/shadow/String.h" // JS::shadow::String + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_ALWAYS_INLINE +#include "mozilla/Likely.h" // MOZ_LIKELY +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/Range.h" // mozilla::Range +#include "mozilla/Span.h" // mozilla::Span + // std::tuple + +#include <algorithm> // std::copy_n +#include <stddef.h> // size_t +#include <stdint.h> // uint32_t, uint64_t, INT32_MAX + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/CharacterEncoding.h" // JS::UTF8Chars, JS::ConstUTF8CharsZ +#include "js/Id.h" // jsid, JSID_IS_STRING, JSID_TO_STRING +#include "js/RootingAPI.h" // JS::Handle +#include "js/TypeDecls.h" // JS::Latin1Char +#include "js/UniquePtr.h" // JS::UniquePtr +#include "js/Utility.h" // JS::FreePolicy, JS::UniqueTwoByteChars +#include "js/Value.h" // JS::Value + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSAtom; +class JSLinearString; +class JS_PUBLIC_API JSString; + +namespace JS { + +class JS_PUBLIC_API AutoRequireNoGC; + +} // namespace JS + +extern JS_PUBLIC_API JSString* JS_GetEmptyString(JSContext* cx); + +// Don't want to export data, so provide accessors for non-inline Values. +extern JS_PUBLIC_API JS::Value JS_GetEmptyStringValue(JSContext* cx); + +/* + * String creation. + * + * NB: JS_NewUCString takes ownership of bytes on success, avoiding a copy; + * but on error (signified by null return), it leaves chars owned by the + * caller. So the caller must free bytes in the error case, if it has no use + * for them. In contrast, all the JS_New*StringCopy* functions do not take + * ownership of the character memory passed to them -- they copy it. + */ + +extern JS_PUBLIC_API JSString* JS_NewStringCopyN(JSContext* cx, const char* s, + size_t n); + +extern JS_PUBLIC_API JSString* JS_NewStringCopyZ(JSContext* cx, const char* s); + +extern JS_PUBLIC_API JSString* JS_NewStringCopyUTF8Z( + JSContext* cx, const JS::ConstUTF8CharsZ s); + +extern JS_PUBLIC_API JSString* JS_NewStringCopyUTF8N(JSContext* cx, + const JS::UTF8Chars s); + +extern JS_PUBLIC_API JSString* JS_AtomizeStringN(JSContext* cx, const char* s, + size_t length); + +extern JS_PUBLIC_API JSString* JS_AtomizeString(JSContext* cx, const char* s); + +// Note: unlike the non-pinning JS_Atomize* functions, this can be called +// without entering a realm/zone. +extern JS_PUBLIC_API JSString* JS_AtomizeAndPinStringN(JSContext* cx, + const char* s, + size_t length); + +// Note: unlike the non-pinning JS_Atomize* functions, this can be called +// without entering a realm/zone. +extern JS_PUBLIC_API JSString* JS_AtomizeAndPinString(JSContext* cx, + const char* s); + +extern JS_PUBLIC_API JSString* JS_NewLatin1String( + JSContext* cx, js::UniquePtr<JS::Latin1Char[], JS::FreePolicy> chars, + size_t length); + +extern JS_PUBLIC_API JSString* JS_NewUCString(JSContext* cx, + JS::UniqueTwoByteChars chars, + size_t length); + +extern JS_PUBLIC_API JSString* JS_NewUCStringDontDeflate( + JSContext* cx, JS::UniqueTwoByteChars chars, size_t length); + +extern JS_PUBLIC_API JSString* JS_NewUCStringCopyN(JSContext* cx, + const char16_t* s, size_t n); + +extern JS_PUBLIC_API JSString* JS_NewUCStringCopyZ(JSContext* cx, + const char16_t* s); + +extern JS_PUBLIC_API JSString* JS_AtomizeUCStringN(JSContext* cx, + const char16_t* s, + size_t length); + +extern JS_PUBLIC_API JSString* JS_AtomizeUCString(JSContext* cx, + const char16_t* s); + +extern JS_PUBLIC_API bool JS_CompareStrings(JSContext* cx, JSString* str1, + JSString* str2, int32_t* result); + +[[nodiscard]] extern JS_PUBLIC_API bool JS_StringEqualsAscii( + JSContext* cx, JSString* str, const char* asciiBytes, bool* match); + +// Same as above, but when the length of asciiBytes (excluding the +// trailing null, if any) is known. +[[nodiscard]] extern JS_PUBLIC_API bool JS_StringEqualsAscii( + JSContext* cx, JSString* str, const char* asciiBytes, size_t length, + bool* match); + +template <size_t N> +[[nodiscard]] bool JS_StringEqualsLiteral(JSContext* cx, JSString* str, + const char (&asciiBytes)[N], + bool* match) { + MOZ_ASSERT(asciiBytes[N - 1] == '\0'); + return JS_StringEqualsAscii(cx, str, asciiBytes, N - 1, match); +} + +extern JS_PUBLIC_API size_t JS_PutEscapedString(JSContext* cx, char* buffer, + size_t size, JSString* str, + char quote); + +/* + * Extracting string characters and length. + * + * While getting the length of a string is infallible, getting the chars can + * fail. As indicated by the lack of a JSContext parameter, there are two + * special cases where getting the chars is infallible: + * + * The first case is for strings that have been atomized, e.g. directly by + * JS_AtomizeAndPinString or implicitly because it is stored in a jsid. + * + * The second case is "linear" strings that have been explicitly prepared in a + * fallible context by JS_EnsureLinearString. To catch errors, a separate opaque + * JSLinearString type is returned by JS_EnsureLinearString and expected by + * JS_Get{Latin1,TwoByte}StringCharsAndLength. Note, though, that this is purely + * a syntactic distinction: the input and output of JS_EnsureLinearString are + * the same actual GC-thing. If a JSString is known to be linear, + * JS_ASSERT_STRING_IS_LINEAR can be used to make a debug-checked cast. Example: + * + * // In a fallible context. + * JSLinearString* lstr = JS_EnsureLinearString(cx, str); + * if (!lstr) { + * return false; + * } + * MOZ_ASSERT(lstr == JS_ASSERT_STRING_IS_LINEAR(str)); + * + * // In an infallible context, for the same 'str'. + * AutoCheckCannotGC nogc; + * const char16_t* chars = JS::GetTwoByteLinearStringChars(nogc, lstr) + * MOZ_ASSERT(chars); + * + * Note: JS strings (including linear strings and atoms) are not + * null-terminated! + * + * Additionally, string characters are stored as either Latin1Char (8-bit) + * or char16_t (16-bit). Clients can use JS::StringHasLatin1Chars and can then + * call either the Latin1* or TwoByte* functions. Some functions like + * JS_CopyStringChars and JS_GetStringCharAt accept both Latin1 and TwoByte + * strings. + */ + +extern JS_PUBLIC_API size_t JS_GetStringLength(JSString* str); + +extern JS_PUBLIC_API bool JS_StringIsLinear(JSString* str); + +extern JS_PUBLIC_API const JS::Latin1Char* JS_GetLatin1StringCharsAndLength( + JSContext* cx, const JS::AutoRequireNoGC& nogc, JSString* str, + size_t* length); + +extern JS_PUBLIC_API const char16_t* JS_GetTwoByteStringCharsAndLength( + JSContext* cx, const JS::AutoRequireNoGC& nogc, JSString* str, + size_t* length); + +extern JS_PUBLIC_API bool JS_GetStringCharAt(JSContext* cx, JSString* str, + size_t index, char16_t* res); + +extern JS_PUBLIC_API const char16_t* JS_GetTwoByteExternalStringChars( + JSString* str); + +extern JS_PUBLIC_API bool JS_CopyStringChars(JSContext* cx, + mozilla::Range<char16_t> dest, + JSString* str); + +/** + * Copies the string's characters to a null-terminated char16_t buffer. + * + * Returns nullptr on OOM. + */ +extern JS_PUBLIC_API JS::UniqueTwoByteChars JS_CopyStringCharsZ(JSContext* cx, + JSString* str); + +extern JS_PUBLIC_API JSLinearString* JS_EnsureLinearString(JSContext* cx, + JSString* str); + +static MOZ_ALWAYS_INLINE JSLinearString* JS_ASSERT_STRING_IS_LINEAR( + JSString* str) { + MOZ_ASSERT(JS_StringIsLinear(str)); + return reinterpret_cast<JSLinearString*>(str); +} + +static MOZ_ALWAYS_INLINE JSString* JS_FORGET_STRING_LINEARNESS( + JSLinearString* str) { + return reinterpret_cast<JSString*>(str); +} + +/* + * Additional APIs that avoid fallibility when given a linear string. + */ + +extern JS_PUBLIC_API bool JS_LinearStringEqualsAscii(JSLinearString* str, + const char* asciiBytes); +extern JS_PUBLIC_API bool JS_LinearStringEqualsAscii(JSLinearString* str, + const char* asciiBytes, + size_t length); + +template <size_t N> +bool JS_LinearStringEqualsLiteral(JSLinearString* str, + const char (&asciiBytes)[N]) { + MOZ_ASSERT(asciiBytes[N - 1] == '\0'); + return JS_LinearStringEqualsAscii(str, asciiBytes, N - 1); +} + +extern JS_PUBLIC_API size_t JS_PutEscapedLinearString(char* buffer, size_t size, + JSLinearString* str, + char quote); + +/** + * Create a dependent string, i.e., a string that owns no character storage, + * but that refers to a slice of another string's chars. Dependent strings + * are mutable by definition, so the thread safety comments above apply. + */ +extern JS_PUBLIC_API JSString* JS_NewDependentString(JSContext* cx, + JS::Handle<JSString*> str, + size_t start, + size_t length); + +/** + * Concatenate two strings, possibly resulting in a rope. + * See above for thread safety comments. + */ +extern JS_PUBLIC_API JSString* JS_ConcatStrings(JSContext* cx, + JS::Handle<JSString*> left, + JS::Handle<JSString*> right); + +/** + * For JS_DecodeBytes, set *dstlenp to the size of the destination buffer before + * the call; on return, *dstlenp contains the number of characters actually + * stored. To determine the necessary destination buffer size, make a sizing + * call that passes nullptr for dst. + * + * On errors, the functions report the error. In that case, *dstlenp contains + * the number of characters or bytes transferred so far. If cx is nullptr, no + * error is reported on failure, and the functions simply return false. + * + * NB: This function does not store an additional zero byte or char16_t after + * the transcoded string. + */ +JS_PUBLIC_API bool JS_DecodeBytes(JSContext* cx, const char* src, size_t srclen, + char16_t* dst, size_t* dstlenp); + +/** + * Get number of bytes in the string encoding (without accounting for a + * terminating zero bytes. The function returns (size_t) -1 if the string + * can not be encoded into bytes and reports an error using cx accordingly. + */ +JS_PUBLIC_API size_t JS_GetStringEncodingLength(JSContext* cx, JSString* str); + +/** + * Encode string into a buffer. The function does not stores an additional + * zero byte. The function returns (size_t) -1 if the string can not be + * encoded into bytes with no error reported. Otherwise it returns the number + * of bytes that are necessary to encode the string. If that exceeds the + * length parameter, the string will be cut and only length bytes will be + * written into the buffer. + */ +[[nodiscard]] JS_PUBLIC_API bool JS_EncodeStringToBuffer(JSContext* cx, + JSString* str, + char* buffer, + size_t length); + +/** + * Encode as many scalar values of the string as UTF-8 as can fit + * into the caller-provided buffer replacing unpaired surrogates + * with the REPLACEMENT CHARACTER. + * + * If JS::StringHasLatin1Chars(str) returns true, the function + * is guaranteed to convert the entire string if + * buffer.Length() >= 2 * JS_GetStringLength(str). Otherwise, + * the function is guaranteed to convert the entire string if + * buffer.Length() >= 3 * JS_GetStringLength(str). + * + * This function does not alter the representation of |str| or + * any |JSString*| substring that is a constituent part of it. + * Returns mozilla::Nothing() on OOM, without reporting an error; + * some data may have been written to |buffer| when this happens. + * + * If there's no OOM, returns the number of code units read and + * the number of code units written. + * + * The semantics of this method match the semantics of + * TextEncoder.encodeInto(). + * + * The function does not store an additional zero byte. + */ +JS_PUBLIC_API mozilla::Maybe<std::tuple<size_t, size_t>> +JS_EncodeStringToUTF8BufferPartial(JSContext* cx, JSString* str, + mozilla::Span<char> buffer); + +namespace JS { + +/** + * Maximum length of a JS string. This is chosen so that the number of bytes + * allocated for a null-terminated TwoByte string still fits in int32_t. + */ +static constexpr uint32_t MaxStringLength = (1 << 30) - 2; + +static_assert((uint64_t(MaxStringLength) + 1) * sizeof(char16_t) <= INT32_MAX, + "size of null-terminated JSString char buffer must fit in " + "INT32_MAX"); + +/** Compute the length of a string. */ +MOZ_ALWAYS_INLINE size_t GetStringLength(JSString* s) { + return shadow::AsShadowString(s)->length(); +} + +/** Compute the length of a linear string. */ +MOZ_ALWAYS_INLINE size_t GetLinearStringLength(JSLinearString* s) { + return shadow::AsShadowString(s)->length(); +} + +/** Return true iff the given linear string uses Latin-1 storage. */ +MOZ_ALWAYS_INLINE bool LinearStringHasLatin1Chars(JSLinearString* s) { + return shadow::AsShadowString(s)->hasLatin1Chars(); +} + +/** Return true iff the given string uses Latin-1 storage. */ +MOZ_ALWAYS_INLINE bool StringHasLatin1Chars(JSString* s) { + return shadow::AsShadowString(s)->hasLatin1Chars(); +} + +/** + * Given a linear string known to use Latin-1 storage, return a pointer to that + * storage. This pointer remains valid only as long as no GC occurs. + */ +MOZ_ALWAYS_INLINE const Latin1Char* GetLatin1LinearStringChars( + const AutoRequireNoGC& nogc, JSLinearString* linear) { + return shadow::AsShadowString(linear)->latin1LinearChars(); +} + +/** + * Given a linear string known to use two-byte storage, return a pointer to that + * storage. This pointer remains valid only as long as no GC occurs. + */ +MOZ_ALWAYS_INLINE const char16_t* GetTwoByteLinearStringChars( + const AutoRequireNoGC& nogc, JSLinearString* linear) { + return shadow::AsShadowString(linear)->twoByteLinearChars(); +} + +/** + * Given an in-range index into the provided string, return the character at + * that index. + */ +MOZ_ALWAYS_INLINE char16_t GetLinearStringCharAt(JSLinearString* linear, + size_t index) { + shadow::String* s = shadow::AsShadowString(linear); + MOZ_ASSERT(index < s->length()); + + return s->hasLatin1Chars() ? s->latin1LinearChars()[index] + : s->twoByteLinearChars()[index]; +} + +/** + * Convert an atom to a linear string. All atoms are linear, so this + * operation is infallible. + */ +MOZ_ALWAYS_INLINE JSLinearString* AtomToLinearString(JSAtom* atom) { + return reinterpret_cast<JSLinearString*>(atom); +} + +/** + * If the provided string uses externally-managed storage, return true and set + * |*callbacks| to the external-string callbacks used to create it and |*chars| + * to a pointer to its two-byte storage. (These pointers remain valid as long + * as the provided string is kept alive.) + */ +MOZ_ALWAYS_INLINE bool IsExternalString( + JSString* str, const JSExternalStringCallbacks** callbacks, + const char16_t** chars) { + shadow::String* s = shadow::AsShadowString(str); + + if (!s->isExternal()) { + return false; + } + + *callbacks = s->externalCallbacks; + *chars = s->nonInlineCharsTwoByte; + return true; +} + +namespace detail { + +extern JS_PUBLIC_API JSLinearString* StringToLinearStringSlow(JSContext* cx, + JSString* str); + +} // namespace detail + +/** Convert a string to a linear string. */ +MOZ_ALWAYS_INLINE JSLinearString* StringToLinearString(JSContext* cx, + JSString* str) { + if (MOZ_LIKELY(shadow::AsShadowString(str)->isLinear())) { + return reinterpret_cast<JSLinearString*>(str); + } + + return detail::StringToLinearStringSlow(cx, str); +} + +/** Copy characters in |s[start..start + len]| to |dest[0..len]|. */ +MOZ_ALWAYS_INLINE void CopyLinearStringChars(char16_t* dest, JSLinearString* s, + size_t len, size_t start = 0) { +#ifdef DEBUG + size_t stringLen = GetLinearStringLength(s); + MOZ_ASSERT(start <= stringLen); + MOZ_ASSERT(len <= stringLen - start); +#endif + + shadow::String* str = shadow::AsShadowString(s); + + if (str->hasLatin1Chars()) { + const Latin1Char* src = str->latin1LinearChars(); + for (size_t i = 0; i < len; i++) { + dest[i] = src[start + i]; + } + } else { + const char16_t* src = str->twoByteLinearChars(); + std::copy_n(src + start, len, dest); + } +} + +/** + * Copy characters in |s[start..start + len]| to |dest[0..len]|, lossily + * truncating 16-bit values to |char| if necessary. + */ +MOZ_ALWAYS_INLINE void LossyCopyLinearStringChars(char* dest, JSLinearString* s, + size_t len, + size_t start = 0) { +#ifdef DEBUG + size_t stringLen = GetLinearStringLength(s); + MOZ_ASSERT(start <= stringLen); + MOZ_ASSERT(len <= stringLen - start); +#endif + + shadow::String* str = shadow::AsShadowString(s); + + if (LinearStringHasLatin1Chars(s)) { + const Latin1Char* src = str->latin1LinearChars(); + for (size_t i = 0; i < len; i++) { + dest[i] = char(src[start + i]); + } + } else { + const char16_t* src = str->twoByteLinearChars(); + for (size_t i = 0; i < len; i++) { + dest[i] = char(src[start + i]); + } + } +} + +/** + * Copy characters in |s[start..start + len]| to |dest[0..len]|. + * + * This function is fallible. If you already have a linear string, use the + * infallible |JS::CopyLinearStringChars| above instead. + */ +[[nodiscard]] inline bool CopyStringChars(JSContext* cx, char16_t* dest, + JSString* s, size_t len, + size_t start = 0) { + JSLinearString* linear = StringToLinearString(cx, s); + if (!linear) { + return false; + } + + CopyLinearStringChars(dest, linear, len, start); + return true; +} + +/** + * Copy characters in |s[start..start + len]| to |dest[0..len]|, lossily + * truncating 16-bit values to |char| if necessary. + * + * This function is fallible. If you already have a linear string, use the + * infallible |JS::LossyCopyLinearStringChars| above instead. + */ +[[nodiscard]] inline bool LossyCopyStringChars(JSContext* cx, char* dest, + JSString* s, size_t len, + size_t start = 0) { + JSLinearString* linear = StringToLinearString(cx, s); + if (!linear) { + return false; + } + + LossyCopyLinearStringChars(dest, linear, len, start); + return true; +} + +} // namespace JS + +/** DO NOT USE, only present for Rust bindings as a temporary hack */ +[[deprecated]] extern JS_PUBLIC_API bool JS_DeprecatedStringHasLatin1Chars( + JSString* str); + +// JSString* is an aligned pointer, but this information isn't available in the +// public header. We specialize HasFreeLSB here so that JS::Result<JSString*> +// compiles. + +namespace mozilla { +namespace detail { +template <> +struct HasFreeLSB<JSString*> { + static constexpr bool value = true; +}; +} // namespace detail +} // namespace mozilla + +#endif // js_String_h diff --git a/js/public/StructuredClone.h b/js/public/StructuredClone.h new file mode 100644 index 0000000000..55c5c91fe1 --- /dev/null +++ b/js/public/StructuredClone.h @@ -0,0 +1,782 @@ +/* -*- 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 js_StructuredClone_h +#define js_StructuredClone_h + +#include "mozilla/Attributes.h" +#include "mozilla/BufferList.h" +#include "mozilla/MemoryReporting.h" + +#include <stdint.h> +#include <utility> + +#include "jstypes.h" + +#include "js/AllocPolicy.h" +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" +#include "js/Vector.h" + +/* + * API for safe passing of structured data, HTML 2018 Feb 21 section 2.7. + * <https://html.spec.whatwg.org/multipage/structured-data.html> + * + * This is a serialization scheme for JS values, somewhat like JSON. It + * preserves some aspects of JS objects (strings, numbers, own data properties + * with string keys, array elements) but not others (methods, getters and + * setters, prototype chains). Unlike JSON, structured data: + * + * - can contain cyclic references. + * + * - handles Maps, Sets, and some other object types. + * + * - supports *transferring* objects of certain types from one realm to + * another, rather than cloning them. + * + * - is specified by a living standard, and continues to evolve. + * + * - is encoded in a nonstandard binary format, and is never exposed to Web + * content in its serialized form. It's used internally by the browser to + * send data from one thread/realm/domain to another, not across the + * network. + */ + +struct JSStructuredCloneReader; +struct JSStructuredCloneWriter; + +/** + * The structured-clone serialization format version number. + * + * When serialized data is stored as bytes, e.g. in your Firefox profile, later + * versions of the engine may have to read it. When you upgrade Firefox, we + * don't crawl through your whole profile converting all saved data from the + * previous version of the serialization format to the latest version. So it is + * normal to have data in old formats stored in your profile. + * + * The JS engine can *write* data only in the current format version. + * + * It can *read* any data written in the current version, and data written for + * DifferentProcess scope in earlier versions. + * + * + * ## When to bump this version number + * + * When making a change so drastic that the JS engine needs to know whether + * it's reading old or new serialized data in order to handle both correctly, + * increment this version number. Make sure the engine can still read all + * old data written with previous versions. + * + * If StructuredClone.cpp doesn't contain code that distinguishes between + * version 8 and version 9, there should not be a version 9. + * + * Do not increment for changes that only affect SameProcess encoding. + * + * Increment only for changes that would otherwise break old serialized data. + * Do not increment for new data types. (Rationale: Modulo bugs, older versions + * of the JS engine can already correctly throw errors when they encounter new, + * unrecognized features. A version number bump does not actually help them.) + */ +#define JS_STRUCTURED_CLONE_VERSION 8 + +namespace JS { + +/** + * Indicates the "scope of validity" of serialized data. + * + * Writing plain JS data produces an array of bytes that can be copied and + * read in another process or whatever. The serialized data is Plain Old Data. + * However, HTML also supports `Transferable` objects, which, when cloned, can + * be moved from the source object into the clone, like when you take a + * photograph of someone and it steals their soul. + * See <https://developer.mozilla.org/en-US/docs/Web/API/Transferable>. + * We support cloning and transferring objects of many types. + * + * For example, when we transfer an ArrayBuffer (within a process), we "detach" + * the ArrayBuffer, embed the raw buffer pointer in the serialized data, and + * later install it in a new ArrayBuffer in the destination realm. Ownership + * of that buffer memory is transferred from the original ArrayBuffer to the + * serialized data and then to the clone. + * + * This only makes sense within a single address space. When we transfer an + * ArrayBuffer to another process, the contents of the buffer must be copied + * into the serialized data. (The original ArrayBuffer is still detached, + * though, for consistency; in some cases the caller shouldn't know or care if + * the recipient is in the same process.) + * + * ArrayBuffers are actually a lucky case; some objects (like MessagePorts) + * can't reasonably be stored by value in serialized data -- it's pointers or + * nothing. + * + * So there is a tradeoff between scope of validity -- how far away the + * serialized data may be sent and still make sense -- and efficiency or + * features. The read and write algorithms therefore take an argument of this + * type, allowing the user to control those trade-offs. + */ +enum class StructuredCloneScope : uint32_t { + /** + * The most restrictive scope, with greatest efficiency and features. + * + * When writing, this means: The caller promises that the serialized data + * will **not** be shipped off to a different process or stored in a + * database. However, it may be shipped to another thread. It's OK to + * produce serialized data that contains pointers to data that is safe to + * send across threads, such as array buffers. In Rust terms, the + * serialized data will be treated as `Send` but not `Copy`. + * + * When reading, this means: Accept transferred objects and buffers + * (pointers). The caller promises that the serialized data was written + * using this API (otherwise, the serialized data may contain bogus + * pointers, leading to undefined behavior). + * + * Starts from 1 because there used to be a SameProcessSameThread enum value + * of 0 and these values are encoded into the structured serialization format + * as part of the SCTAG_HEADER, and IndexedDB persists the representation to + * disk. + */ + SameProcess = 1, + + /** + * When writing, this means we're writing for an audience in a different + * process. Produce serialized data that can be sent to other processes, + * bitwise copied, or even stored as bytes in a database and read by later + * versions of Firefox years from now. The HTML5 spec refers to this as + * "ForStorage" as in StructuredSerializeForStorage, though we use + * DifferentProcess for IPC as well as storage. + * + * Transferable objects are limited to ArrayBuffers, whose contents are + * copied into the serialized data (rather than just writing a pointer). + * + * When reading, this means: Do not accept pointers. + */ + DifferentProcess, + + /** + * Handle a backwards-compatibility case with IndexedDB (bug 1434308): when + * reading, this means to treat legacy SameProcess data as if it were + * DifferentProcess. + * + * Do not use this for writing; use DifferentProcess instead. + */ + DifferentProcessForIndexedDB, + + /** + * Existing code wants to be able to create an uninitialized + * JSStructuredCloneData without knowing the scope, then populate it with + * data (at which point the scope *is* known.) + */ + Unassigned, + + /** + * This scope is used when the deserialization context is unknown. When + * writing, DifferentProcess or SameProcess scope is chosen based on the + * nature of the object. + */ + UnknownDestination, +}; + +/** Values used to describe the ownership individual Transferables. + * + * Note that these *can* show up in DifferentProcess clones, since + * DifferentProcess ArrayBuffers can be Transferred. In that case, this will + * distinguish the specific ownership mechanism: is it a malloc pointer or a + * memory mapping? */ +enum TransferableOwnership { + /** Transferable data has not been filled in yet. */ + SCTAG_TMO_UNFILLED = 0, + + /** Structured clone buffer does not yet own the data. */ + SCTAG_TMO_UNOWNED = 1, + + /** All enum values at least this large are owned by the clone buffer. */ + SCTAG_TMO_FIRST_OWNED = 2, + + /** Data is a pointer that can be freed. */ + SCTAG_TMO_ALLOC_DATA = SCTAG_TMO_FIRST_OWNED, + + /** Data is a memory mapped pointer. */ + SCTAG_TMO_MAPPED_DATA = 3, + + /** + * Data is embedding-specific. The engine can free it by calling the + * freeTransfer op. */ + SCTAG_TMO_CUSTOM = 4, + + /** + * Same as SCTAG_TMO_CUSTOM, but the embedding can also use + * SCTAG_TMO_USER_MIN and greater, up to 2^32-1, to distinguish specific + * ownership variants. + */ + SCTAG_TMO_USER_MIN +}; + +class CloneDataPolicy { + bool allowIntraClusterClonableSharedObjects_; + bool allowSharedMemoryObjects_; + + public: + // The default is to deny all policy-controlled aspects. + + CloneDataPolicy() + : allowIntraClusterClonableSharedObjects_(false), + allowSharedMemoryObjects_(false) {} + + // SharedArrayBuffers and WASM modules can only be cloned intra-process + // because the shared memory areas are allocated in process-private memory or + // because there are security issues of sharing them cross agent clusters. + // y default, we don't allow shared-memory and intra-cluster objects. Clients + // should therefore enable these 2 clone features when needed. + + void allowIntraClusterClonableSharedObjects() { + allowIntraClusterClonableSharedObjects_ = true; + } + bool areIntraClusterClonableSharedObjectsAllowed() const { + return allowIntraClusterClonableSharedObjects_; + } + + void allowSharedMemoryObjects() { allowSharedMemoryObjects_ = true; } + bool areSharedMemoryObjectsAllowed() const { + return allowSharedMemoryObjects_; + } +}; + +} /* namespace JS */ + +/** + * Read structured data from the reader r. This hook is used to read a value + * previously serialized by a call to the WriteStructuredCloneOp hook. + * + * tag and data are the pair of uint32_t values from the header. The callback + * may use the JS_Read* APIs to read any other relevant parts of the object + * from the reader r. closure is any value passed to the JS_ReadStructuredClone + * function. + * + * Return the new object on success, or raise an exception and return nullptr on + * error. + */ +typedef JSObject* (*ReadStructuredCloneOp)( + JSContext* cx, JSStructuredCloneReader* r, + const JS::CloneDataPolicy& cloneDataPolicy, uint32_t tag, uint32_t data, + void* closure); + +/** + * Structured data serialization hook. The engine can write primitive values, + * Objects, Arrays, Dates, RegExps, TypedArrays, ArrayBuffers, Sets, Maps, + * and SharedTypedArrays. Any other type of object requires application support. + * This callback must first use the JS_WriteUint32Pair API to write an object + * header, passing a value greater than JS_SCTAG_USER to the tag parameter. + * Then it can use the JS_Write* APIs to write any other relevant parts of + * the value v to the writer w. closure is any value passed to the + * JS_WriteStructuredClone function. + * + * Return true on success, false on error. On error, an exception should + * normally be set. + */ +typedef bool (*WriteStructuredCloneOp)(JSContext* cx, + JSStructuredCloneWriter* w, + JS::HandleObject obj, + bool* sameProcessScopeRequired, + void* closure); + +/** + * This is called when serialization or deserialization encounters an error. + * To follow HTML5, the application must throw a DATA_CLONE_ERR DOMException + * with error set to one of the JS_SCERR_* values. + * + * Note that if the .reportError field of the JSStructuredCloneCallbacks is + * set (to a function with this signature), then an exception will *not* be + * set on the JSContext when an error is encountered. The clone operation + * will still be aborted and will return false, however, so it is up to the + * embedding to do what it needs to for the error. + * + * Example: for the DOM, mozilla::dom::StructuredCloneHolder will save away + * the error message during its reportError callback. Then when the overall + * operation fails, it will clear any exception that might have been set + * from other ways to fail and pass the saved error message to + * ErrorResult::ThrowDataCloneError(). + */ +typedef void (*StructuredCloneErrorOp)(JSContext* cx, uint32_t errorid, + void* closure, const char* errorMessage); + +/** + * This is called when JS_ReadStructuredClone receives a transferable object + * not known to the engine. If this hook does not exist or returns false, the + * JS engine calls the reportError op if set, otherwise it throws a + * DATA_CLONE_ERR DOM Exception. This method is called before any other + * callback and must return a non-null object in returnObject on success. + * + * If this readTransfer() hook is called and produces an object, then the + * read() hook will *not* be called for the same object, since the main data + * will only contain a backreference to the already-read object. + */ +typedef bool (*ReadTransferStructuredCloneOp)( + JSContext* cx, JSStructuredCloneReader* r, uint32_t tag, void* content, + uint64_t extraData, void* closure, JS::MutableHandleObject returnObject); + +/** + * Called when JS_WriteStructuredClone receives a transferable object not + * handled by the engine. If this hook does not exist or returns false, the JS + * engine will call the reportError hook or fall back to throwing a + * DATA_CLONE_ERR DOM Exception. This method is called before any other + * callback. + * + * tag: indicates what type of transferable this is. Must be greater than + * 0xFFFF0201 (value of the internal SCTAG_TRANSFER_MAP_PENDING_ENTRY) + * + * ownership: see TransferableOwnership, above. Used to communicate any needed + * ownership info to the FreeTransferStructuredCloneOp. + * + * content, extraData: what the ReadTransferStructuredCloneOp will receive + */ +typedef bool (*TransferStructuredCloneOp)(JSContext* cx, + JS::Handle<JSObject*> obj, + void* closure, + // Output: + uint32_t* tag, + JS::TransferableOwnership* ownership, + void** content, uint64_t* extraData); + +/** + * Called when freeing a transferable handled by the embedding. Note that it + * should never trigger a garbage collection (and will assert in a + * debug build if it does.) + * + * This callback will be used to release ownership in three situations: + * + * 1. During serialization: an object is Transferred from, then an error is + * encountered later and the incomplete serialization is discarded. + * + * 2. During deserialization: before an object is Transferred to, an error + * is encountered and the incompletely deserialized clone is discarded. + * + * 3. Serialized data that includes Transferring is never deserialized (eg when + * the receiver disappears before reading in the message), and the clone data + * is destroyed. + * + */ +typedef void (*FreeTransferStructuredCloneOp)( + uint32_t tag, JS::TransferableOwnership ownership, void* content, + uint64_t extraData, void* closure); + +/** + * Called when the transferring objects are checked. If this function returns + * false, the serialization ends throwing a DataCloneError exception. + */ +typedef bool (*CanTransferStructuredCloneOp)(JSContext* cx, + JS::Handle<JSObject*> obj, + bool* sameProcessScopeRequired, + void* closure); + +/** + * Called when a SharedArrayBuffer (including one owned by a Wasm memory object) + * has been processed in context `cx` by structured cloning. If `receiving` is + * true then the SAB has been received from a channel and a new SAB object has + * been created; if false then an existing SAB has been serialized onto a + * channel. + * + * If the callback returns false then the clone operation (read or write) will + * signal a failure. + */ +typedef bool (*SharedArrayBufferClonedOp)(JSContext* cx, bool receiving, + void* closure); + +struct JSStructuredCloneCallbacks { + ReadStructuredCloneOp read; + WriteStructuredCloneOp write; + StructuredCloneErrorOp reportError; + ReadTransferStructuredCloneOp readTransfer; + TransferStructuredCloneOp writeTransfer; + FreeTransferStructuredCloneOp freeTransfer; + CanTransferStructuredCloneOp canTransfer; + SharedArrayBufferClonedOp sabCloned; +}; + +enum OwnTransferablePolicy { + /** + * The buffer owns any Transferables that it might contain, and should + * properly release them upon destruction. + */ + OwnsTransferablesIfAny, + + /** + * Do not free any Transferables within this buffer when deleting it. This + * is used to mark a clone buffer as containing data from another process, + * and so it can't legitimately contain pointers. If the buffer claims to + * have transferables, it's a bug or an attack. This is also used for + * abandon(), where a buffer still contains raw data but the ownership has + * been given over to some other entity. + */ + IgnoreTransferablesIfAny, + + /** + * A buffer that cannot contain Transferables at all. This usually means + * the buffer is empty (not yet filled in, or having been cleared). + */ + NoTransferables +}; + +namespace js { +class SharedArrayRawBuffer; + +class SharedArrayRawBufferRefs { + public: + SharedArrayRawBufferRefs() = default; + SharedArrayRawBufferRefs(SharedArrayRawBufferRefs&& other) = default; + SharedArrayRawBufferRefs& operator=(SharedArrayRawBufferRefs&& other); + ~SharedArrayRawBufferRefs(); + + [[nodiscard]] bool acquire(JSContext* cx, SharedArrayRawBuffer* rawbuf); + [[nodiscard]] bool acquireAll(JSContext* cx, + const SharedArrayRawBufferRefs& that); + void takeOwnership(SharedArrayRawBufferRefs&&); + void releaseAll(); + + private: + js::Vector<js::SharedArrayRawBuffer*, 0, js::SystemAllocPolicy> refs_; +}; + +template <typename T, typename AllocPolicy> +struct BufferIterator; +} // namespace js + +/** + * JSStructuredCloneData represents structured clone data together with the + * information needed to read/write/transfer/free the records within it, in the + * form of a set of callbacks. + */ +class MOZ_NON_MEMMOVABLE JS_PUBLIC_API JSStructuredCloneData { + public: + using BufferList = mozilla::BufferList<js::SystemAllocPolicy>; + using Iterator = BufferList::IterImpl; + + private: + static const size_t kStandardCapacity = 4096; + + BufferList bufList_; + + // The (address space, thread) scope within which this clone is valid. Note + // that this must be either set during construction, or start out as + // Unassigned and transition once to something else. + JS::StructuredCloneScope scope_; + + const JSStructuredCloneCallbacks* callbacks_ = nullptr; + void* closure_ = nullptr; + OwnTransferablePolicy ownTransferables_ = + OwnTransferablePolicy::NoTransferables; + js::SharedArrayRawBufferRefs refsHeld_; + + friend struct JSStructuredCloneWriter; + friend class JS_PUBLIC_API JSAutoStructuredCloneBuffer; + template <typename T, typename AllocPolicy> + friend struct js::BufferIterator; + + public: + // The constructor must be infallible but SystemAllocPolicy is not, so both + // the initial size and initial capacity of the BufferList must be zero. + explicit JSStructuredCloneData(JS::StructuredCloneScope scope) + : bufList_(0, 0, kStandardCapacity, js::SystemAllocPolicy()), + scope_(scope) {} + + // Steal the raw data from a BufferList. In this case, we don't know the + // scope and none of the callback info is assigned yet. + JSStructuredCloneData(BufferList&& buffers, JS::StructuredCloneScope scope, + OwnTransferablePolicy ownership) + : bufList_(std::move(buffers)), + scope_(scope), + ownTransferables_(ownership) {} + JSStructuredCloneData(JSStructuredCloneData&& other) = default; + JSStructuredCloneData& operator=(JSStructuredCloneData&& other) = default; + ~JSStructuredCloneData(); + + void setCallbacks(const JSStructuredCloneCallbacks* callbacks, void* closure, + OwnTransferablePolicy policy) { + callbacks_ = callbacks; + closure_ = closure; + ownTransferables_ = policy; + } + + [[nodiscard]] bool Init(size_t initialCapacity = 0) { + return bufList_.Init(0, initialCapacity); + } + + JS::StructuredCloneScope scope() const { + if (scope_ == JS::StructuredCloneScope::UnknownDestination) { + return JS::StructuredCloneScope::DifferentProcess; + } + return scope_; + } + + void sameProcessScopeRequired() { + if (scope_ == JS::StructuredCloneScope::UnknownDestination) { + scope_ = JS::StructuredCloneScope::SameProcess; + } + } + + void initScope(JS::StructuredCloneScope newScope) { + MOZ_ASSERT(Size() == 0, "initScope() of nonempty JSStructuredCloneData"); + if (scope() != JS::StructuredCloneScope::Unassigned) { + MOZ_ASSERT(scope() == newScope, + "Cannot change scope after it has been initialized"); + } + scope_ = newScope; + } + + size_t Size() const { return bufList_.Size(); } + + const Iterator Start() const { return bufList_.Iter(); } + + [[nodiscard]] bool Advance(Iterator& iter, size_t distance) const { + return iter.AdvanceAcrossSegments(bufList_, distance); + } + + [[nodiscard]] bool ReadBytes(Iterator& iter, char* buffer, + size_t size) const { + return bufList_.ReadBytes(iter, buffer, size); + } + + // Append new data to the end of the buffer. + [[nodiscard]] bool AppendBytes(const char* data, size_t size) { + MOZ_ASSERT(scope() != JS::StructuredCloneScope::Unassigned); + return bufList_.WriteBytes(data, size); + } + + // Update data stored within the existing buffer. There must be at least + // 'size' bytes between the position of 'iter' and the end of the buffer. + [[nodiscard]] bool UpdateBytes(Iterator& iter, const char* data, + size_t size) const { + MOZ_ASSERT(scope() != JS::StructuredCloneScope::Unassigned); + while (size > 0) { + size_t remaining = iter.RemainingInSegment(); + size_t nbytes = std::min(remaining, size); + memcpy(iter.Data(), data, nbytes); + data += nbytes; + size -= nbytes; + iter.Advance(bufList_, nbytes); + } + return true; + } + + char* AllocateBytes(size_t maxSize, size_t* size) { + return bufList_.AllocateBytes(maxSize, size); + } + + void Clear() { + discardTransferables(); + bufList_.Clear(); + } + + // Return a new read-only JSStructuredCloneData that "borrows" the contents + // of |this|. Its lifetime should not exceed the donor's. This is only + // allowed for DifferentProcess clones, so finalization of the borrowing + // clone will do nothing. + JSStructuredCloneData Borrow(Iterator& iter, size_t size, + bool* success) const { + MOZ_ASSERT(scope() == JS::StructuredCloneScope::DifferentProcess); + return JSStructuredCloneData( + bufList_.Borrow<js::SystemAllocPolicy>(iter, size, success), scope(), + IgnoreTransferablesIfAny); + } + + // Iterate over all contained data, one BufferList segment's worth at a + // time, and invoke the given FunctionToApply with the data pointer and + // size. The function should return a bool value, and this loop will exit + // with false if the function ever returns false. + template <typename FunctionToApply> + bool ForEachDataChunk(FunctionToApply&& function) const { + Iterator iter = bufList_.Iter(); + while (!iter.Done()) { + if (!function(iter.Data(), iter.RemainingInSegment())) { + return false; + } + iter.Advance(bufList_, iter.RemainingInSegment()); + } + return true; + } + + // Append the entire contents of other's bufList_ to our own. + [[nodiscard]] bool Append(const JSStructuredCloneData& other) { + MOZ_ASSERT(scope() == other.scope()); + return other.ForEachDataChunk( + [&](const char* data, size_t size) { return AppendBytes(data, size); }); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { + return bufList_.SizeOfExcludingThis(mallocSizeOf); + } + + void discardTransferables(); + + private: + // This internal method exposes the real value of scope_. It's meant to be + // used only when starting the writing. + JS::StructuredCloneScope scopeForInternalWriting() const { return scope_; } +}; + +/** + * Implements StructuredDeserialize and StructuredDeserializeWithTransfer. + * + * Note: If `data` contains transferable objects, it can be read only once. + */ +JS_PUBLIC_API bool JS_ReadStructuredClone( + JSContext* cx, const JSStructuredCloneData& data, uint32_t version, + JS::StructuredCloneScope scope, JS::MutableHandleValue vp, + const JS::CloneDataPolicy& cloneDataPolicy, + const JSStructuredCloneCallbacks* optionalCallbacks, void* closure); + +/** + * Implements StructuredSerialize, StructuredSerializeForStorage, and + * StructuredSerializeWithTransfer. + * + * Note: If the scope is DifferentProcess then the cloneDataPolicy must deny + * shared-memory objects, or an error will be signaled if a shared memory object + * is seen. + */ +JS_PUBLIC_API bool JS_WriteStructuredClone( + JSContext* cx, JS::HandleValue v, JSStructuredCloneData* data, + JS::StructuredCloneScope scope, const JS::CloneDataPolicy& cloneDataPolicy, + const JSStructuredCloneCallbacks* optionalCallbacks, void* closure, + JS::HandleValue transferable); + +JS_PUBLIC_API bool JS_StructuredCloneHasTransferables( + JSStructuredCloneData& data, bool* hasTransferable); + +JS_PUBLIC_API bool JS_StructuredClone( + JSContext* cx, JS::HandleValue v, JS::MutableHandleValue vp, + const JSStructuredCloneCallbacks* optionalCallbacks, void* closure); + +/** + * The C-style API calls to read and write structured clones are fragile -- + * they rely on the caller to properly handle ownership of the clone data, and + * the handling of the input data as well as the interpretation of the contents + * of the clone buffer are dependent on the callbacks passed in. If you + * serialize and deserialize with different callbacks, the results are + * questionable. + * + * JSAutoStructuredCloneBuffer wraps things up in an RAII class for data + * management, and uses the same callbacks for both writing and reading + * (serializing and deserializing). + */ +class JS_PUBLIC_API JSAutoStructuredCloneBuffer { + JSStructuredCloneData data_; + uint32_t version_; + + public: + JSAutoStructuredCloneBuffer(JS::StructuredCloneScope scope, + const JSStructuredCloneCallbacks* callbacks, + void* closure) + : data_(scope), version_(JS_STRUCTURED_CLONE_VERSION) { + data_.setCallbacks(callbacks, closure, + OwnTransferablePolicy::NoTransferables); + } + + JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer&& other); + JSAutoStructuredCloneBuffer& operator=(JSAutoStructuredCloneBuffer&& other); + + ~JSAutoStructuredCloneBuffer() { clear(); } + + JSStructuredCloneData& data() { return data_; } + bool empty() const { return !data_.Size(); } + + void clear(); + + JS::StructuredCloneScope scope() const { return data_.scope(); } + + /** + * Adopt some memory. It will be automatically freed by the destructor. + * data must have been allocated by the JS engine (e.g., extracted via + * JSAutoStructuredCloneBuffer::steal). + */ + void adopt(JSStructuredCloneData&& data, + uint32_t version = JS_STRUCTURED_CLONE_VERSION, + const JSStructuredCloneCallbacks* callbacks = nullptr, + void* closure = nullptr); + + /** + * Release ownership of the buffer and assign it and ownership of it to + * `data`. + */ + void giveTo(JSStructuredCloneData* data); + + bool read(JSContext* cx, JS::MutableHandleValue vp, + const JS::CloneDataPolicy& cloneDataPolicy = JS::CloneDataPolicy(), + const JSStructuredCloneCallbacks* optionalCallbacks = nullptr, + void* closure = nullptr); + + bool write(JSContext* cx, JS::HandleValue v, + const JSStructuredCloneCallbacks* optionalCallbacks = nullptr, + void* closure = nullptr); + + bool write(JSContext* cx, JS::HandleValue v, JS::HandleValue transferable, + const JS::CloneDataPolicy& cloneDataPolicy, + const JSStructuredCloneCallbacks* optionalCallbacks = nullptr, + void* closure = nullptr); + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { + return data_.SizeOfExcludingThis(mallocSizeOf); + } + + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } + + private: + // Copy and assignment are not supported. + JSAutoStructuredCloneBuffer(const JSAutoStructuredCloneBuffer& other) = + delete; + JSAutoStructuredCloneBuffer& operator=( + const JSAutoStructuredCloneBuffer& other) = delete; +}; + +// The range of tag values the application may use for its own custom object +// types. +#define JS_SCTAG_USER_MIN ((uint32_t)0xFFFF8000) +#define JS_SCTAG_USER_MAX ((uint32_t)0xFFFFFFFF) + +#define JS_SCERR_RECURSION 0 +#define JS_SCERR_TRANSFERABLE 1 +#define JS_SCERR_DUP_TRANSFERABLE 2 +#define JS_SCERR_UNSUPPORTED_TYPE 3 +#define JS_SCERR_SHMEM_TRANSFERABLE 4 +#define JS_SCERR_TYPED_ARRAY_DETACHED 5 +#define JS_SCERR_WASM_NO_TRANSFER 6 +#define JS_SCERR_NOT_CLONABLE 7 +#define JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP 8 + +JS_PUBLIC_API bool JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1, + uint32_t* p2); + +JS_PUBLIC_API bool JS_ReadBytes(JSStructuredCloneReader* r, void* p, + size_t len); + +JS_PUBLIC_API bool JS_ReadString(JSStructuredCloneReader* r, + JS::MutableHandleString str); + +JS_PUBLIC_API bool JS_ReadDouble(JSStructuredCloneReader* r, double* v); + +JS_PUBLIC_API bool JS_ReadTypedArray(JSStructuredCloneReader* r, + JS::MutableHandleValue vp); + +JS_PUBLIC_API bool JS_WriteUint32Pair(JSStructuredCloneWriter* w, uint32_t tag, + uint32_t data); + +JS_PUBLIC_API bool JS_WriteBytes(JSStructuredCloneWriter* w, const void* p, + size_t len); + +JS_PUBLIC_API bool JS_WriteString(JSStructuredCloneWriter* w, + JS::HandleString str); + +JS_PUBLIC_API bool JS_WriteDouble(JSStructuredCloneWriter* w, double v); + +JS_PUBLIC_API bool JS_WriteTypedArray(JSStructuredCloneWriter* w, + JS::HandleValue v); + +JS_PUBLIC_API bool JS_ObjectNotWritten(JSStructuredCloneWriter* w, + JS::HandleObject obj); + +JS_PUBLIC_API JS::StructuredCloneScope JS_GetStructuredCloneScope( + JSStructuredCloneWriter* w); + +#endif /* js_StructuredClone_h */ diff --git a/js/public/SweepingAPI.h b/js/public/SweepingAPI.h new file mode 100644 index 0000000000..7833d65b7a --- /dev/null +++ b/js/public/SweepingAPI.h @@ -0,0 +1,121 @@ +/* -*- 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 js_SweepingAPI_h +#define js_SweepingAPI_h + +#include "mozilla/LinkedList.h" +#include "mozilla/Maybe.h" + +#include "jstypes.h" + +#include "js/GCAnnotations.h" +#include "js/GCPolicyAPI.h" +#include "js/RootingAPI.h" + +namespace js { +namespace gc { + +class StoreBuffer; + +JS_PUBLIC_API void LockStoreBuffer(StoreBuffer* sb); +JS_PUBLIC_API void UnlockStoreBuffer(StoreBuffer* sb); + +class AutoLockStoreBuffer { + StoreBuffer* sb; + + public: + explicit AutoLockStoreBuffer(StoreBuffer* sb) : sb(sb) { + LockStoreBuffer(sb); + } + ~AutoLockStoreBuffer() { UnlockStoreBuffer(sb); } +}; + +} // namespace gc +} // namespace js + +namespace JS { +namespace detail { +class WeakCacheBase; +} // namespace detail + +namespace shadow { +JS_PUBLIC_API void RegisterWeakCache(JS::Zone* zone, + JS::detail::WeakCacheBase* cachep); +JS_PUBLIC_API void RegisterWeakCache(JSRuntime* rt, + JS::detail::WeakCacheBase* cachep); +} // namespace shadow + +namespace detail { +class WeakCacheBase : public mozilla::LinkedListElement<WeakCacheBase> { + WeakCacheBase() = delete; + explicit WeakCacheBase(const WeakCacheBase&) = delete; + + public: + explicit WeakCacheBase(Zone* zone) { shadow::RegisterWeakCache(zone, this); } + explicit WeakCacheBase(JSRuntime* rt) { shadow::RegisterWeakCache(rt, this); } + WeakCacheBase(WeakCacheBase&& other) = default; + virtual ~WeakCacheBase() = default; + + virtual size_t traceWeak(JSTracer* trc, js::gc::StoreBuffer* sbToLock) = 0; + + // Sweeping will be skipped if the cache is empty already. + virtual bool empty() = 0; + + // Enable/disable read barrier during incremental sweeping and set the tracer + // to use. + virtual bool setIncrementalBarrierTracer(JSTracer* trc) { + // Derived classes do not support incremental barriers by default. + return false; + } + virtual bool needsIncrementalBarrier() const { + // Derived classes do not support incremental barriers by default. + return false; + } +}; +} // namespace detail + +// A WeakCache stores the given Sweepable container and links itself into a +// list of such caches that are swept during each GC. A WeakCache can be +// specific to a zone, or across a whole runtime, depending on which +// constructor is used. +template <typename T> +class WeakCache : protected detail::WeakCacheBase, + public js::MutableWrappedPtrOperations<T, WeakCache<T>> { + T cache; + + public: + using Type = T; + + template <typename... Args> + explicit WeakCache(Zone* zone, Args&&... args) + : WeakCacheBase(zone), cache(std::forward<Args>(args)...) {} + template <typename... Args> + explicit WeakCache(JSRuntime* rt, Args&&... args) + : WeakCacheBase(rt), cache(std::forward<Args>(args)...) {} + + const T& get() const { return cache; } + T& get() { return cache; } + + size_t traceWeak(JSTracer* trc, js::gc::StoreBuffer* sbToLock) override { + // Take the store buffer lock in case sweeping triggers any generational + // post barriers. This is not always required and WeakCache specializations + // may delay or skip taking the lock as appropriate. + mozilla::Maybe<js::gc::AutoLockStoreBuffer> lock; + if (sbToLock) { + lock.emplace(sbToLock); + } + + GCPolicy<T>::traceWeak(trc, &cache); + return 0; + } + + bool empty() override { return cache.empty(); } +} JS_HAZ_NON_GC_POINTER; + +} // namespace JS + +#endif // js_SweepingAPI_h diff --git a/js/public/Symbol.h b/js/public/Symbol.h new file mode 100644 index 0000000000..1e16552b36 --- /dev/null +++ b/js/public/Symbol.h @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* Symbols. */ + +#ifndef js_Symbol_h +#define js_Symbol_h + +#include "js/shadow/Symbol.h" // JS::shadow::Symbol::WellKnownAPILimit + +#include <stddef.h> // size_t +#include <stdint.h> // uintptr_t, uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/TypeDecls.h" + +namespace JS { + +class JS_PUBLIC_API Symbol; + +/** + * Create a new Symbol with the given description. This function never returns + * a Symbol that is in the Runtime-wide symbol registry. + * + * If description is null, the new Symbol's [[Description]] attribute is + * undefined. + */ +extern JS_PUBLIC_API Symbol* NewSymbol(JSContext* cx, + Handle<JSString*> description); + +/** + * Symbol.for as specified in ES6. + * + * Get a Symbol with the description 'key' from the Runtime-wide symbol + * registry. If there is not already a Symbol with that description in the + * registry, a new Symbol is created and registered. 'key' must not be null. + */ +extern JS_PUBLIC_API Symbol* GetSymbolFor(JSContext* cx, Handle<JSString*> key); + +/** + * Get the [[Description]] attribute of the given symbol. + * + * This function is infallible. If it returns null, that means the symbol's + * [[Description]] is undefined. + */ +extern JS_PUBLIC_API JSString* GetSymbolDescription(Handle<Symbol*> symbol); + +/* Well-known symbols. */ +#define JS_FOR_EACH_WELL_KNOWN_SYMBOL(MACRO) \ + MACRO(isConcatSpreadable) \ + MACRO(iterator) \ + MACRO(match) \ + MACRO(replace) \ + MACRO(search) \ + MACRO(species) \ + MACRO(hasInstance) \ + MACRO(split) \ + MACRO(toPrimitive) \ + MACRO(toStringTag) \ + MACRO(unscopables) \ + MACRO(asyncIterator) \ + MACRO(matchAll) + +enum class SymbolCode : uint32_t { +// There is one SymbolCode for each well-known symbol. +#define JS_DEFINE_SYMBOL_ENUM(name) name, + JS_FOR_EACH_WELL_KNOWN_SYMBOL( + JS_DEFINE_SYMBOL_ENUM) // SymbolCode::iterator, etc. +#undef JS_DEFINE_SYMBOL_ENUM + Limit, + WellKnownAPILimit = JS::shadow::Symbol::WellKnownAPILimit, + PrivateNameSymbol = 0xfffffffd, // created by the #PrivateName syntax. + InSymbolRegistry = + 0xfffffffe, // created by Symbol.for() or JS::GetSymbolFor() + UniqueSymbol = 0xffffffff // created by Symbol() or JS::NewSymbol() +}; + +/* For use in loops that iterate over the well-known symbols. */ +const size_t WellKnownSymbolLimit = size_t(SymbolCode::Limit); + +/** + * Return the SymbolCode telling what sort of symbol `symbol` is. + * + * A symbol's SymbolCode never changes once it is created. + */ +extern JS_PUBLIC_API SymbolCode GetSymbolCode(Handle<Symbol*> symbol); + +/** + * Get one of the well-known symbols defined by ES6. A single set of well-known + * symbols is shared by all compartments in a JSRuntime. + * + * `which` must be in the range [0, WellKnownSymbolLimit). + */ +extern JS_PUBLIC_API Symbol* GetWellKnownSymbol(JSContext* cx, + SymbolCode which); + +/** + * Return true if the given JSPropertySpec::name or JSFunctionSpec::name value + * is actually a symbol code and not a string. See JS_SYM_FN. + */ +inline bool PropertySpecNameIsSymbol(uintptr_t name) { + return name != 0 && name - 1 < WellKnownSymbolLimit; +} + +} // namespace JS + +#endif /* js_Symbol_h */ diff --git a/js/public/TelemetryTimers.h b/js/public/TelemetryTimers.h new file mode 100644 index 0000000000..a58f9efcad --- /dev/null +++ b/js/public/TelemetryTimers.h @@ -0,0 +1,34 @@ +/* -*- 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 js_TelemetryTimers_h +#define js_TelemetryTimers_h + +#include "mozilla/TimeStamp.h" + +#include "jstypes.h" + +struct JS_PUBLIC_API JSContext; + +namespace JS { + +/** Timing information for telemetry purposes **/ +struct JSTimers { + mozilla::TimeDuration executionTime; // Total time spent executing + mozilla::TimeDuration delazificationTime; // Total time spent delazifying + mozilla::TimeDuration xdrEncodingTime; // Total time spent XDR encoding + mozilla::TimeDuration gcTime; // Total time spent in GC + mozilla::TimeDuration + protectTime; // Total time spent protecting JIT executable memory + mozilla::TimeDuration + baselineCompileTime; // Total time spent in baseline compiler +}; + +extern JS_PUBLIC_API JSTimers GetJSTimers(JSContext* cx); + +} // namespace JS + +#endif // js_TelemetryTimers_h diff --git a/js/public/TraceKind.h b/js/public/TraceKind.h new file mode 100644 index 0000000000..268db56957 --- /dev/null +++ b/js/public/TraceKind.h @@ -0,0 +1,273 @@ +/* -*- 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 js_TraceKind_h +#define js_TraceKind_h + +#include "mozilla/UniquePtr.h" + +#include "js/TypeDecls.h" + +// Forward declarations of all the types a TraceKind can denote. +class JSLinearString; + +namespace js { +class BaseScript; +class BaseShape; +class GetterSetter; +class PropMap; +class RegExpShared; +class Shape; +class Scope; +namespace jit { +class JitCode; +} // namespace jit +} // namespace js + +namespace JS { + +// When tracing a thing, the GC needs to know about the layout of the object it +// is looking at. There are a fixed number of different layouts that the GC +// knows about. The "trace kind" is a static map which tells which layout a GC +// thing has. +// +// Although this map is public, the details are completely hidden. Not all of +// the matching C++ types are exposed, and those that are, are opaque. +// +// See Value::gcKind() and JSTraceCallback in Tracer.h for more details. +enum class TraceKind { + // These trace kinds have a publicly exposed, although opaque, C++ type. + // Note: The order here is determined by our Value packing. Other users + // should sort alphabetically, for consistency. + // Note: Nursery allocatable kinds go first. See js::gc::NurseryTraceKinds. + Object = 0x00, + BigInt = 0x01, + String = 0x02, + Symbol = 0x03, + + // Shape details are exposed through JS_TraceShapeCycleCollectorChildren. + Shape = 0x04, + + BaseShape = 0x05, + + // The kind associated with a nullptr. + Null = 0x06, + + // The following kinds do not have an exposed C++ idiom. + JitCode, + Script, + Scope, + RegExpShared, + GetterSetter, + PropMap, +}; + +// GCCellPtr packs the trace kind into the low bits of the pointer for common +// kinds. +const static uintptr_t OutOfLineTraceKindMask = 0x07; +static_assert(uintptr_t(JS::TraceKind::Null) < OutOfLineTraceKindMask, + "GCCellPtr requires an inline representation for nullptr"); + +// When this header is imported inside SpiderMonkey, the class definitions are +// available and we can query those definitions to find the correct kind +// directly from the class hierarchy. +template <typename T> +struct MapTypeToTraceKind { + static const JS::TraceKind kind = T::TraceKind; +}; + +// When this header is used outside SpiderMonkey, the class definitions are not +// available, so the following table containing all public GC types is used. +// +// canBeGray: GC can mark things of this kind gray. The cycle collector +// traverses gray GC things when looking for cycles. +// inCCGraph: Things of this kind are represented as nodes in the CC graph. This +// also means they can be used as a keys in WeakMap. + +// clang-format off +#define JS_FOR_EACH_TRACEKIND(D) \ + /* name type canBeGray inCCGraph */ \ + D(BaseShape, js::BaseShape, true, false) \ + D(JitCode, js::jit::JitCode, true, false) \ + D(Scope, js::Scope, true, true) \ + D(Object, JSObject, true, true) \ + D(Script, js::BaseScript, true, true) \ + D(Shape, js::Shape, true, false) \ + D(String, JSString, false, false) \ + D(Symbol, JS::Symbol, false, false) \ + D(BigInt, JS::BigInt, false, false) \ + D(RegExpShared, js::RegExpShared, true, true) \ + D(GetterSetter, js::GetterSetter, true, true) \ + D(PropMap, js::PropMap, false, false) +// clang-format on + +// Returns true if the JS::TraceKind is represented as a node in cycle collector +// graph. +inline constexpr bool IsCCTraceKind(JS::TraceKind aKind) { + switch (aKind) { +#define JS_EXPAND_DEF(name, _1, _2, inCCGraph) \ + case JS::TraceKind::name: \ + return inCCGraph; + JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF); +#undef JS_EXPAND_DEF + default: + return false; + } +} + +// Helper for SFINAE to ensure certain methods are only used on appropriate base +// types. This avoids common footguns such as `Cell::is<JSFunction>()` which +// match any type of JSObject. +template <typename T> +struct IsBaseTraceType : std::false_type {}; + +#define JS_EXPAND_DEF(_, type, _1, _2) \ + template <> \ + struct IsBaseTraceType<type> : std::true_type {}; +JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF); +#undef JS_EXPAND_DEF + +template <typename T> +inline constexpr bool IsBaseTraceType_v = IsBaseTraceType<T>::value; + +// Map from all public types to their trace kind. +#define JS_EXPAND_DEF(name, type, _, _1) \ + template <> \ + struct MapTypeToTraceKind<type> { \ + static const JS::TraceKind kind = JS::TraceKind::name; \ + }; +JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF); +#undef JS_EXPAND_DEF + +template <> +struct MapTypeToTraceKind<JSLinearString> { + static const JS::TraceKind kind = JS::TraceKind::String; +}; +template <> +struct MapTypeToTraceKind<JSFunction> { + static const JS::TraceKind kind = JS::TraceKind::Object; +}; +template <> +struct MapTypeToTraceKind<JSScript> { + static const JS::TraceKind kind = JS::TraceKind::Script; +}; + +// RootKind is closely related to TraceKind. Whereas TraceKind's indices are +// laid out for convenient embedding as a pointer tag, the indicies of RootKind +// are designed for use as array keys via EnumeratedArray. +enum class RootKind : int8_t { +// These map 1:1 with trace kinds. +#define EXPAND_ROOT_KIND(name, _0, _1, _2) name, + JS_FOR_EACH_TRACEKIND(EXPAND_ROOT_KIND) +#undef EXPAND_ROOT_KIND + + // These tagged pointers are special-cased for performance. + Id, + Value, + + // Everything else. + Traceable, + + Limit +}; + +// Most RootKind correspond directly to a trace kind. +template <TraceKind traceKind> +struct MapTraceKindToRootKind {}; +#define JS_EXPAND_DEF(name, _0, _1, _2) \ + template <> \ + struct MapTraceKindToRootKind<JS::TraceKind::name> { \ + static const JS::RootKind kind = JS::RootKind::name; \ + }; +JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF) +#undef JS_EXPAND_DEF + +// Specify the RootKind for all types. Value and jsid map to special cases; +// Cell pointer types we can derive directly from the TraceKind; everything else +// should go in the Traceable list and use GCPolicy<T>::trace for tracing. +template <typename T> +struct MapTypeToRootKind { + static const JS::RootKind kind = JS::RootKind::Traceable; +}; +template <typename T> +struct MapTypeToRootKind<T*> { + static const JS::RootKind kind = + JS::MapTraceKindToRootKind<JS::MapTypeToTraceKind<T>::kind>::kind; +}; +template <> +struct MapTypeToRootKind<JS::Realm*> { + // Not a pointer to a GC cell. Use GCPolicy. + static const JS::RootKind kind = JS::RootKind::Traceable; +}; +template <typename T> +struct MapTypeToRootKind<mozilla::UniquePtr<T>> { + static const JS::RootKind kind = JS::MapTypeToRootKind<T>::kind; +}; +template <> +struct MapTypeToRootKind<JS::Value> { + static const JS::RootKind kind = JS::RootKind::Value; +}; +template <> +struct MapTypeToRootKind<jsid> { + static const JS::RootKind kind = JS::RootKind::Id; +}; + +// Fortunately, few places in the system need to deal with fully abstract +// cells. In those places that do, we generally want to move to a layout +// templated function as soon as possible. This template wraps the upcast +// for that dispatch. +// +// Given a call: +// +// DispatchTraceKindTyped(f, thing, traceKind, ... args) +// +// Downcast the |void *thing| to the specific type designated by |traceKind|, +// and pass it to the functor |f| along with |... args|, forwarded. Pass the +// type designated by |traceKind| as the functor's template argument. The +// |thing| parameter is optional; without it, we simply pass through |... args|. +template <typename F, typename... Args> +auto DispatchTraceKindTyped(F f, JS::TraceKind traceKind, Args&&... args) { + switch (traceKind) { +#define JS_EXPAND_DEF(name, type, _, _1) \ + case JS::TraceKind::name: \ + return f.template operator()<type>(std::forward<Args>(args)...); + JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF); +#undef JS_EXPAND_DEF + default: + MOZ_CRASH("Invalid trace kind in DispatchTraceKindTyped."); + } +} + +// Given a GC thing specified by pointer and trace kind, calls the functor |f| +// with a template argument of the actual type of the pointer and returns the +// result. +template <typename F> +auto MapGCThingTyped(void* thing, JS::TraceKind traceKind, F&& f) { + switch (traceKind) { +#define JS_EXPAND_DEF(name, type, _, _1) \ + case JS::TraceKind::name: \ + return f(static_cast<type*>(thing)); + JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF); +#undef JS_EXPAND_DEF + default: + MOZ_CRASH("Invalid trace kind in MapGCThingTyped."); + } +} + +// Given a GC thing specified by pointer and trace kind, calls the functor |f| +// with a template argument of the actual type of the pointer and ignores the +// result. +template <typename F> +void ApplyGCThingTyped(void* thing, JS::TraceKind traceKind, F&& f) { + // This function doesn't do anything but is supplied for symmetry with other + // MapGCThingTyped/ApplyGCThingTyped implementations that have to wrap the + // functor to return a dummy value that is ignored. + MapGCThingTyped(thing, traceKind, std::move(f)); +} + +} // namespace JS + +#endif // js_TraceKind_h diff --git a/js/public/TracingAPI.h b/js/public/TracingAPI.h new file mode 100644 index 0000000000..9c69eec160 --- /dev/null +++ b/js/public/TracingAPI.h @@ -0,0 +1,421 @@ +/* -*- 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 js_TracingAPI_h +#define js_TracingAPI_h + +#include "js/GCTypeMacros.h" +#include "js/HeapAPI.h" +#include "js/TraceKind.h" + +class JS_PUBLIC_API JSTracer; + +namespace JS { +class JS_PUBLIC_API CallbackTracer; +template <typename T> +class Heap; +template <typename T> +class TenuredHeap; + +/** Returns a static string equivalent of |kind|. */ +JS_PUBLIC_API const char* GCTraceKindToAscii(JS::TraceKind kind); + +/** Returns the base size in bytes of the GC thing of kind |kind|. */ +JS_PUBLIC_API size_t GCTraceKindSize(JS::TraceKind kind); + +// Kinds of JSTracer. +enum class TracerKind { + // Generic tracers: Internal tracers that have a different virtual method + // called for each edge kind. + Marking, + Tenuring, + Moving, + ClearEdges, + Sweeping, + MinorSweeping, + Barrier, + + // Callback tracers: General-purpose tracers that have a single virtual + // method called on every edge. + // + // Order is important. All callback kinds must follow this one. + Callback, + + // Specific kinds of callback tracer. + UnmarkGray, + VerifyTraceProtoAndIface, + CompartmentCheck, +}; + +enum class WeakMapTraceAction { + /** + * Do not trace into weak map keys or values during traversal. Users must + * handle weak maps manually. + */ + Skip, + + /** + * Do true ephemeron marking with a weak key lookup marking phase. This is + * the default for GCMarker. + */ + Expand, + + /** + * Trace through to all values, irrespective of whether the keys are live + * or not. Used for non-marking tracers. + */ + TraceValues, + + /** + * Trace through to all keys and values, irrespective of whether the keys + * are live or not. Used for non-marking tracers. + */ + TraceKeysAndValues +}; + +// Whether a tracer should trace weak edges. GCMarker sets this to Skip. +enum class WeakEdgeTraceAction { Skip, Trace }; + +struct TraceOptions { + JS::WeakMapTraceAction weakMapAction = WeakMapTraceAction::TraceValues; + JS::WeakEdgeTraceAction weakEdgeAction = WeakEdgeTraceAction::Trace; + + TraceOptions() = default; + TraceOptions(JS::WeakMapTraceAction weakMapActionArg, + JS::WeakEdgeTraceAction weakEdgeActionArg) + : weakMapAction(weakMapActionArg), weakEdgeAction(weakEdgeActionArg) {} + MOZ_IMPLICIT TraceOptions(JS::WeakMapTraceAction weakMapActionArg) + : weakMapAction(weakMapActionArg) {} + MOZ_IMPLICIT TraceOptions(JS::WeakEdgeTraceAction weakEdgeActionArg) + : weakEdgeAction(weakEdgeActionArg) {} +}; + +class AutoTracingIndex; + +// Optional context information that can be used to construct human readable +// descriptions of what is being traced. +class TracingContext { + public: + // Access to the tracing context: When tracing with a JS::CallbackTracer, we + // invoke the callback with the edge location and the type of target. This is + // useful for operating on the edge in the abstract or on the target thing, + // satisfying most common use cases. However, some tracers need additional + // detail about the specific edge that is being traced in order to be + // useful. Unfortunately, the raw pointer to the edge that we provide is not + // enough information to infer much of anything useful about that edge. + // + // In order to better support use cases that care in particular about edges -- + // as opposed to the target thing -- tracing implementations are responsible + // for providing extra context information about each edge they trace, as it + // is traced. This contains, at a minimum, an edge name and, when tracing an + // array, the index. Further specialization can be achieved (with some + // complexity), by associating a functor with the tracer so that, when + // requested, the user can generate totally custom edge descriptions. + + // Returns the current edge's index, if marked as part of an array of edges. + // This must be called only inside the trace callback. When not tracing an + // array, the value will be InvalidIndex. + constexpr static size_t InvalidIndex = size_t(-1); + size_t index() const { return index_; } + + // Build a description of this edge in the heap graph. This call may invoke + // the context functor, if set, which may inspect arbitrary areas of the + // heap. On the other hand, the description provided by this method may be + // substantially more accurate and useful than those provided by only the + // name and index. + void getEdgeName(const char* name, char* buffer, size_t bufferSize); + + // The trace implementation may associate a callback with one or more edges + // using AutoTracingDetails. This functor is called by getEdgeName and + // is responsible for providing a textual representation of the edge currently + // being traced. The callback has access to the full heap, including the + // currently set tracing context. + class Functor { + public: + virtual void operator()(TracingContext* tcx, char* buf, size_t bufsize) = 0; + }; + + private: + friend class AutoTracingIndex; + size_t index_ = InvalidIndex; + + friend class AutoTracingDetails; + Functor* functor_ = nullptr; +}; + +} // namespace JS + +class JS_PUBLIC_API JSTracer { + public: + // Return the runtime set on the tracer. + JSRuntime* runtime() const { return runtime_; } + + JS::TracerKind kind() const { return kind_; } + bool isGenericTracer() const { return kind_ < JS::TracerKind::Callback; } + bool isCallbackTracer() const { return kind_ >= JS::TracerKind::Callback; } + bool isMarkingTracer() const { return kind_ == JS::TracerKind::Marking; } + bool isTenuringTracer() const { return kind_ == JS::TracerKind::Tenuring; } + + inline JS::CallbackTracer* asCallbackTracer(); + + JS::WeakMapTraceAction weakMapAction() const { + return options_.weakMapAction; + } + bool traceWeakEdges() const { + return options_.weakEdgeAction == JS::WeakEdgeTraceAction::Trace; + } + + JS::TracingContext& context() { return context_; } + + // These methods are called when the tracer encounters an edge. Clients should + // override them to receive notifications when an edge of each type is + // visited. + // + // The caller updates the edge with the return value (if different). + // + // In C++, overriding a method hides all methods in the base class with that + // name, not just methods with that signature. Thus, the typed edge methods + // have to have distinct names to allow us to override them individually, + // which is freqently useful if, for example, we only want to process one type + // of edge. +#define DEFINE_ON_EDGE_METHOD(name, type, _1, _2) \ + virtual void on##name##Edge(type** thingp, const char* name) = 0; + JS_FOR_EACH_TRACEKIND(DEFINE_ON_EDGE_METHOD) +#undef DEFINE_ON_EDGE_METHOD + + protected: + JSTracer(JSRuntime* rt, JS::TracerKind kind, + JS::TraceOptions options = JS::TraceOptions()) + : runtime_(rt), kind_(kind), options_(options) {} + + private: + JSRuntime* const runtime_; + const JS::TracerKind kind_; + const JS::TraceOptions options_; + JS::TracingContext context_; +}; + +namespace js { + +// A CRTP helper class that implements a JSTracer by calling a template method +// on the derived tracer type for each edge kind. +template <typename T> +class GenericTracerImpl : public JSTracer { + public: + GenericTracerImpl(JSRuntime* rt, JS::TracerKind kind, + JS::TraceOptions options) + : JSTracer(rt, kind, options) {} + + private: + T* derived() { return static_cast<T*>(this); } + +#define DEFINE_ON_EDGE_METHOD(name, type, _1, _2) \ + void on##name##Edge(type** thingp, const char* name) final { \ + derived()->onEdge(thingp, name); \ + } + JS_FOR_EACH_TRACEKIND(DEFINE_ON_EDGE_METHOD) +#undef DEFINE_ON_EDGE_METHOD +}; + +} // namespace js + +namespace JS { + +class JS_PUBLIC_API CallbackTracer + : public js::GenericTracerImpl<CallbackTracer> { + public: + CallbackTracer(JSRuntime* rt, JS::TracerKind kind = JS::TracerKind::Callback, + JS::TraceOptions options = JS::TraceOptions()) + : GenericTracerImpl(rt, kind, options) { + MOZ_ASSERT(isCallbackTracer()); + } + CallbackTracer(JSContext* cx, JS::TracerKind kind = JS::TracerKind::Callback, + JS::TraceOptions options = JS::TraceOptions()); + + // Override this method to receive notification when a node in the GC + // heap graph is visited. + virtual void onChild(JS::GCCellPtr thing, const char* name) = 0; + + private: + template <typename T> + void onEdge(T** thingp, const char* name) { + onChild(JS::GCCellPtr(*thingp), name); + } + friend class js::GenericTracerImpl<CallbackTracer>; +}; + +// Set the index portion of the tracer's context for the current range. +class MOZ_RAII AutoTracingIndex { + JSTracer* trc_; + + public: + explicit AutoTracingIndex(JSTracer* trc, size_t initial = 0) : trc_(trc) { + MOZ_ASSERT(trc_->context().index_ == TracingContext::InvalidIndex); + trc_->context().index_ = initial; + } + ~AutoTracingIndex() { + MOZ_ASSERT(trc_->context().index_ != TracingContext::InvalidIndex); + trc_->context().index_ = TracingContext::InvalidIndex; + } + + void operator++() { + MOZ_ASSERT(trc_->context().index_ != TracingContext::InvalidIndex); + ++trc_->context().index_; + } +}; + +// Set a context callback for the trace callback to use, if it needs a detailed +// edge description. +class MOZ_RAII AutoTracingDetails { + JSTracer* trc_; + + public: + AutoTracingDetails(JSTracer* trc, TracingContext::Functor& func) : trc_(trc) { + MOZ_ASSERT(trc_->context().functor_ == nullptr); + trc_->context().functor_ = &func; + } + ~AutoTracingDetails() { + MOZ_ASSERT(trc_->context().functor_); + trc_->context().functor_ = nullptr; + } +}; + +// Save and clear tracing context when performing nested tracing. +class MOZ_RAII AutoClearTracingContext { + JSTracer* trc_; + TracingContext prev_; + + public: + explicit AutoClearTracingContext(JSTracer* trc) + : trc_(trc), prev_(trc->context()) { + trc_->context() = TracingContext(); + } + + ~AutoClearTracingContext() { trc_->context() = prev_; } +}; + +} // namespace JS + +JS::CallbackTracer* JSTracer::asCallbackTracer() { + MOZ_ASSERT(isCallbackTracer()); + return static_cast<JS::CallbackTracer*>(this); +} + +namespace js { + +class AbstractGeneratorObject; +class SavedFrame; + +namespace gc { + +#define JS_DECLARE_TRACE_EXTERNAL_EDGE(type) \ + extern JS_PUBLIC_API void TraceExternalEdge(JSTracer* trc, type* thingp, \ + const char* name); + +// Declare edge-tracing function overloads for public GC pointer types. +JS_FOR_EACH_PUBLIC_GC_POINTER_TYPE(JS_DECLARE_TRACE_EXTERNAL_EDGE) +JS_FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(JS_DECLARE_TRACE_EXTERNAL_EDGE) + +#undef JS_DECLARE_TRACE_EXTERNAL_EDGE + +} // namespace gc +} // namespace js + +namespace JS { + +// The JS::TraceEdge family of functions traces the given GC thing reference. +// This performs the tracing action configured on the given JSTracer: typically +// calling the JSTracer::callback or marking the thing as live. +// +// The argument to JS::TraceEdge is an in-out param: when the function returns, +// the garbage collector might have moved the GC thing. In this case, the +// reference passed to JS::TraceEdge will be updated to the thing's new +// location. Callers of this method are responsible for updating any state that +// is dependent on the object's address. For example, if the object's address +// is used as a key in a hashtable, then the object must be removed and +// re-inserted with the correct hash. +// +// Note that while |edgep| must never be null, it is fine for |*edgep| to be +// nullptr. + +template <typename T> +inline void TraceEdge(JSTracer* trc, JS::Heap<T>* thingp, const char* name) { + MOZ_ASSERT(thingp); + if (*thingp) { + js::gc::TraceExternalEdge(trc, thingp->unsafeGet(), name); + } +} + +template <typename T> +inline void TraceEdge(JSTracer* trc, JS::TenuredHeap<T>* thingp, + const char* name) { + MOZ_ASSERT(thingp); + if (T ptr = thingp->unbarrieredGetPtr()) { + js::gc::TraceExternalEdge(trc, &ptr, name); + thingp->setPtr(ptr); + } +} + +// Edges that are always traced as part of root marking do not require +// incremental barriers. |JS::TraceRoot| overloads allow for marking +// non-barriered pointers but assert that this happens during root marking. +// +// Note that while |edgep| must never be null, it is fine for |*edgep| to be +// nullptr. +#define JS_DECLARE_TRACE_ROOT(type) \ + extern JS_PUBLIC_API void TraceRoot(JSTracer* trc, type* edgep, \ + const char* name); + +// Declare edge-tracing function overloads for public GC pointer types. +JS_FOR_EACH_PUBLIC_GC_POINTER_TYPE(JS_DECLARE_TRACE_ROOT) +JS_FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(JS_DECLARE_TRACE_ROOT) + +// We also require overloads for these purely-internal types. These overloads +// ought not be in public headers, and they should use a different name in order +// to not be *actual* overloads, but for the moment we still declare them here. +JS_DECLARE_TRACE_ROOT(js::AbstractGeneratorObject*) +JS_DECLARE_TRACE_ROOT(js::SavedFrame*) + +#undef JS_DECLARE_TRACE_ROOT + +extern JS_PUBLIC_API void TraceChildren(JSTracer* trc, GCCellPtr thing); + +} // namespace JS + +namespace js { + +inline bool IsTracerKind(JSTracer* trc, JS::TracerKind kind) { + return trc->kind() == kind; +} + +// Trace an edge that is not a GC root and is not wrapped in a barriered +// wrapper for some reason. +// +// This method does not check if |*edgep| is non-null before tracing through +// it, so callers must check any nullable pointer before calling this method. +extern JS_PUBLIC_API void UnsafeTraceManuallyBarrieredEdge(JSTracer* trc, + JSObject** edgep, + const char* name); + +namespace gc { + +// Return true if the given edge is not live and is about to be swept. +template <typename T> +extern JS_PUBLIC_API bool TraceWeakEdge(JSTracer* trc, JS::Heap<T>* thingp); + +} // namespace gc + +#ifdef DEBUG +/* + * Return whether the runtime is currently being destroyed, for use in + * assertions. + */ +extern JS_PUBLIC_API bool RuntimeIsBeingDestroyed(); +#endif + +} // namespace js + +#endif /* js_TracingAPI_h */ diff --git a/js/public/Transcoding.h b/js/public/Transcoding.h new file mode 100644 index 0000000000..ca0828330a --- /dev/null +++ b/js/public/Transcoding.h @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * Structures and functions for transcoding compiled scripts and functions to + * and from memory. + */ + +#ifndef js_Transcoding_h +#define js_Transcoding_h + +#include "mozilla/Range.h" // mozilla::Range +#include "mozilla/Vector.h" // mozilla::Vector + +#include <stddef.h> // size_t +#include <stdint.h> // uint8_t, uint32_t + +#include "js/TypeDecls.h" + +namespace JS { + +class JS_PUBLIC_API ReadOnlyCompileOptions; + +using TranscodeBuffer = mozilla::Vector<uint8_t>; +using TranscodeRange = mozilla::Range<const uint8_t>; + +struct TranscodeSource final { + TranscodeSource(const TranscodeRange& range_, const char* file, uint32_t line) + : range(range_), filename(file), lineno(line) {} + + const TranscodeRange range; + const char* filename; + const uint32_t lineno; +}; + +using TranscodeSources = mozilla::Vector<TranscodeSource>; + +enum class TranscodeResult : uint8_t { + // Successful encoding / decoding. + Ok = 0, + + // A warning message, is set to the message out-param. + Failure = 0x10, + Failure_BadBuildId = Failure | 0x1, + Failure_AsmJSNotSupported = Failure | 0x2, + Failure_BadDecode = Failure | 0x3, + + // There is a pending exception on the context. + Throw = 0x20 +}; + +inline bool IsTranscodeFailureResult(const TranscodeResult result) { + uint8_t raw_result = static_cast<uint8_t>(result); + uint8_t raw_failure = static_cast<uint8_t>(TranscodeResult::Failure); + TranscodeResult masked = + static_cast<TranscodeResult>(raw_result & raw_failure); + return masked == TranscodeResult::Failure; +} + +static constexpr size_t BytecodeOffsetAlignment = 4; +static_assert(BytecodeOffsetAlignment <= alignof(std::max_align_t), + "Alignment condition requires a custom allocator."); + +// Align the bytecode offset for transcoding for the requirement. +inline size_t AlignTranscodingBytecodeOffset(size_t offset) { + size_t extra = offset % BytecodeOffsetAlignment; + if (extra == 0) { + return offset; + } + size_t padding = BytecodeOffsetAlignment - extra; + return offset + padding; +} + +inline bool IsTranscodingBytecodeOffsetAligned(size_t offset) { + return offset % BytecodeOffsetAlignment == 0; +} + +inline bool IsTranscodingBytecodeAligned(const void* offset) { + return IsTranscodingBytecodeOffsetAligned(size_t(offset)); +} + +// Finish incremental encoding started by JS::StartIncrementalEncoding. +// +// * Regular script case +// the |script| argument must be the top-level script returned from +// |JS::InstantiateGlobalStencil| with the same stencil +// +// * Module script case +// the |script| argument must be the script returned by +// |JS::GetModuleScript| called on the module returned by +// |JS::InstantiateModuleStencil| with the same stencil +// +// NOTE: |JS::GetModuleScript| doesn't work after evaluating the +// module script. For the case, use Handle<JSObject*> variant of +// this function below. +// +// The |buffer| argument of |FinishIncrementalEncoding| is used for appending +// the encoded bytecode into the buffer. If any of these functions failed, the +// content of |buffer| would be undefined. +// +// |buffer| contains encoded CompilationStencil. +// +// If the `buffer` isn't empty, the start of the `buffer` should meet +// IsTranscodingBytecodeAligned, and the length should meet +// IsTranscodingBytecodeOffsetAligned. +// +// NOTE: As long as IsTranscodingBytecodeOffsetAligned is met, that means +// there's JS::BytecodeOffsetAlignment+extra bytes in the buffer, +// IsTranscodingBytecodeAligned should be guaranteed to meet by +// malloc, used by MallocAllocPolicy in mozilla::Vector. +extern JS_PUBLIC_API bool FinishIncrementalEncoding(JSContext* cx, + Handle<JSScript*> script, + TranscodeBuffer& buffer); + +// Similar to |JS::FinishIncrementalEncoding|, but receives module obect. +// +// The |module| argument must be the module returned by +// |JS::InstantiateModuleStencil| with the same stencil that's passed to +// |JS::StartIncrementalEncoding|. +extern JS_PUBLIC_API bool FinishIncrementalEncoding(JSContext* cx, + Handle<JSObject*> module, + TranscodeBuffer& buffer); + +// Abort incremental encoding started by JS::StartIncrementalEncoding. +extern JS_PUBLIC_API void AbortIncrementalEncoding(Handle<JSScript*> script); +extern JS_PUBLIC_API void AbortIncrementalEncoding(Handle<JSObject*> module); + +// Check if the compile options and script's flag matches. +// +// JS::DecodeScript* and JS::DecodeOffThreadScript internally check this. +// +// JS::DecodeMultiStencilsOffThread checks some options shared across multiple +// scripts. Caller is responsible for checking each script with this API when +// using the decoded script instead of compiling a new script wiht the given +// options. +extern JS_PUBLIC_API bool CheckCompileOptionsMatch( + const ReadOnlyCompileOptions& options, JSScript* script); + +} // namespace JS + +#endif /* js_Transcoding_h */ diff --git a/js/public/TypeDecls.h b/js/public/TypeDecls.h new file mode 100644 index 0000000000..1686b19173 --- /dev/null +++ b/js/public/TypeDecls.h @@ -0,0 +1,151 @@ +/* -*- 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/. */ + +// This file contains public type declarations that are used *frequently*. If +// it doesn't occur at least 10 times in Gecko, it probably shouldn't be in +// here. +// +// It includes only: +// - forward declarations of structs and classes; +// - typedefs; +// - enums (maybe). +// It does *not* contain any struct or class definitions. + +#ifndef js_TypeDecls_h +#define js_TypeDecls_h + +#include <stdint.h> // uint8_t + +#include "jstypes.h" // JS_PUBLIC_API + +typedef uint8_t jsbytecode; + +class JS_PUBLIC_API JSAtom; +struct JS_PUBLIC_API JSContext; +struct JSClass; +class JS_PUBLIC_API JSFunction; +class JS_PUBLIC_API JSObject; +struct JS_PUBLIC_API JSRuntime; +class JS_PUBLIC_API JSScript; +class JS_PUBLIC_API JSString; + +struct JSPrincipals; + +namespace js { +class JS_PUBLIC_API TempAllocPolicy; +}; // namespace js + +namespace JS { + +class JS_PUBLIC_API GCContext; +class JS_PUBLIC_API PropertyKey; + +typedef unsigned char Latin1Char; + +class JS_PUBLIC_API Symbol; +class JS_PUBLIC_API BigInt; +#ifdef ENABLE_RECORD_TUPLE +class JS_PUBLIC_API RecordType; +class JS_PUBLIC_API TupleType; +#endif +class JS_PUBLIC_API Value; + +class JS_PUBLIC_API Compartment; +class JS_PUBLIC_API Realm; +struct JS_PUBLIC_API Runtime; +class JS_PUBLIC_API Zone; + +template <typename T> +class Handle; +template <typename T> +class MutableHandle; +template <typename T> +class Rooted; +template <typename T> +class PersistentRooted; +template <typename T> +class RootedVector; +template <typename T> +class PersistentRootedVector; +template <typename T, typename AllocPolicy = js::TempAllocPolicy> +class StackGCVector; + +typedef Handle<JSFunction*> HandleFunction; +typedef Handle<PropertyKey> HandleId; +typedef Handle<JSObject*> HandleObject; +typedef Handle<JSScript*> HandleScript; +typedef Handle<JSString*> HandleString; +typedef Handle<JS::Symbol*> HandleSymbol; +typedef Handle<JS::BigInt*> HandleBigInt; +typedef Handle<Value> HandleValue; +typedef Handle<StackGCVector<Value>> HandleValueVector; +typedef Handle<StackGCVector<JSObject*>> HandleObjectVector; +typedef Handle<StackGCVector<JS::PropertyKey>> HandleIdVector; + +typedef MutableHandle<JSFunction*> MutableHandleFunction; +typedef MutableHandle<PropertyKey> MutableHandleId; +typedef MutableHandle<JSObject*> MutableHandleObject; +typedef MutableHandle<JSScript*> MutableHandleScript; +typedef MutableHandle<JSString*> MutableHandleString; +typedef MutableHandle<JS::Symbol*> MutableHandleSymbol; +typedef MutableHandle<JS::BigInt*> MutableHandleBigInt; +typedef MutableHandle<Value> MutableHandleValue; +typedef MutableHandle<StackGCVector<Value>> MutableHandleValueVector; +typedef MutableHandle<StackGCVector<JSObject*>> MutableHandleObjectVector; +typedef MutableHandle<StackGCVector<JS::PropertyKey>> MutableHandleIdVector; + +typedef Rooted<JSObject*> RootedObject; +typedef Rooted<JSFunction*> RootedFunction; +typedef Rooted<JSScript*> RootedScript; +typedef Rooted<JSString*> RootedString; +typedef Rooted<JS::Symbol*> RootedSymbol; +typedef Rooted<JS::BigInt*> RootedBigInt; +typedef Rooted<PropertyKey> RootedId; +typedef Rooted<JS::Value> RootedValue; + +typedef RootedVector<JS::Value> RootedValueVector; +typedef RootedVector<JSObject*> RootedObjectVector; +typedef RootedVector<JS::PropertyKey> RootedIdVector; + +typedef PersistentRooted<JSFunction*> PersistentRootedFunction; +typedef PersistentRooted<PropertyKey> PersistentRootedId; +typedef PersistentRooted<JSObject*> PersistentRootedObject; +typedef PersistentRooted<JSScript*> PersistentRootedScript; +typedef PersistentRooted<JSString*> PersistentRootedString; +typedef PersistentRooted<JS::Symbol*> PersistentRootedSymbol; +typedef PersistentRooted<JS::BigInt*> PersistentRootedBigInt; +typedef PersistentRooted<Value> PersistentRootedValue; + +typedef PersistentRootedVector<PropertyKey> PersistentRootedIdVector; +typedef PersistentRootedVector<JSObject*> PersistentRootedObjectVector; + +template <typename T> +using HandleVector = Handle<StackGCVector<T>>; +template <typename T> +using MutableHandleVector = MutableHandle<StackGCVector<T>>; +} // namespace JS + +using jsid = JS::PropertyKey; + +#ifdef ENABLE_RECORD_TUPLE +// This takes 1 or 2 parameters. ... is just used so that +// it's possible to omit the comma when passing a single +// param: +// IF_RECORD_TUPLE(doThis) +// IF_RECORD_TUPLE(doThis, elseThis) +# define IF_RECORD_TUPLE(x, ...) x +#else +# define IF_RECORD_TUPLE(x, ...) __VA_ARGS__ +#endif + +// Follows the same pattern as IF_RECORD_TUPLE +#ifdef ENABLE_DECORATORS +# define IF_DECORATORS(x, ...) x +#else +# define IF_DECORATORS(x, ...) __VA_ARGS__ +#endif + +#endif /* js_TypeDecls_h */ diff --git a/js/public/UbiNode.h b/js/public/UbiNode.h new file mode 100644 index 0000000000..d6adc4e75c --- /dev/null +++ b/js/public/UbiNode.h @@ -0,0 +1,1210 @@ +/* -*- 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 js_UbiNode_h +#define js_UbiNode_h + +#include "mozilla/Alignment.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RangedPtr.h" +#include "mozilla/Variant.h" +#include "mozilla/Vector.h" + +#include <utility> + +#include "jspubtd.h" + +#include "js/AllocPolicy.h" +#include "js/HashTable.h" +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" +#include "js/UniquePtr.h" +#include "js/Value.h" + +// [SMDOC] ubi::Node (Heap Analysis framework) +// +// JS::ubi::Node is a pointer-like type designed for internal use by heap +// analysis tools. A ubi::Node can refer to: +// +// - a JS value, like a string, object, or symbol; +// - an internal SpiderMonkey structure, like a shape or a scope chain object +// - an instance of some embedding-provided type: in Firefox, an XPCOM +// object, or an internal DOM node class instance +// +// A ubi::Node instance provides metadata about its referent, and can +// enumerate its referent's outgoing edges, so you can implement heap analysis +// algorithms that walk the graph - finding paths between objects, or +// computing heap dominator trees, say - using ubi::Node, while remaining +// ignorant of the details of the types you're operating on. +// +// Of course, when it comes to presenting the results in a developer-facing +// tool, you'll need to stop being ignorant of those details, because you have +// to discuss the ubi::Nodes' referents with the developer. Here, ubi::Node +// can hand you dynamically checked, properly typed pointers to the original +// objects via the as<T> method, or generate descriptions of the referent +// itself. +// +// ubi::Node instances are lightweight (two-word) value types. Instances: +// - compare equal if and only if they refer to the same object; +// - have hash values that respect their equality relation; and +// - have serializations that are only equal if the ubi::Nodes are equal. +// +// A ubi::Node is only valid for as long as its referent is alive; if its +// referent goes away, the ubi::Node becomes a dangling pointer. A ubi::Node +// that refers to a GC-managed object is not automatically a GC root; if the +// GC frees or relocates its referent, the ubi::Node becomes invalid. A +// ubi::Node that refers to a reference-counted object does not bump the +// reference count. +// +// ubi::Node values require no supporting data structures, making them +// feasible for use in memory-constrained devices --- ideally, the memory +// requirements of the algorithm which uses them will be the limiting factor, +// not the demands of ubi::Node itself. +// +// One can construct a ubi::Node value given a pointer to a type that ubi::Node +// supports. In the other direction, one can convert a ubi::Node back to a +// pointer; these downcasts are checked dynamically. In particular, one can +// convert a 'JSContext*' to a ubi::Node, yielding a node with an outgoing edge +// for every root registered with the runtime; starting from this, one can walk +// the entire heap. (Of course, one could also start traversal at any other kind +// of type to which one has a pointer.) +// +// +// Extending ubi::Node To Handle Your Embedding's Types +// +// To add support for a new ubi::Node referent type R, you must define a +// specialization of the ubi::Concrete template, ubi::Concrete<R>, which +// inherits from ubi::Base. ubi::Node itself uses the specialization for +// compile-time information (i.e. the checked conversions between R * and +// ubi::Node), and the inheritance for run-time dispatching. +// +// +// ubi::Node Exposes Implementation Details +// +// In many cases, a JavaScript developer's view of their data differs +// substantially from its actual implementation. For example, while the +// ECMAScript specification describes objects as maps from property names to +// sets of attributes (like ECMAScript's [[Value]]), in practice many objects +// have only a pointer to a shape, shared with other similar objects, and +// indexed slots that contain the [[Value]] attributes. As another example, a +// string produced by concatenating two other strings may sometimes be +// represented by a "rope", a structure that points to the two original +// strings. +// +// We intend to use ubi::Node to write tools that report memory usage, so it's +// important that ubi::Node accurately portray how much memory nodes consume. +// Thus, for example, when data that apparently belongs to multiple nodes is +// in fact shared in a common structure, ubi::Node's graph uses a separate +// node for that shared structure, and presents edges to it from the data's +// apparent owners. For example, ubi::Node exposes SpiderMonkey objects' +// shapes and base shapes, and exposes rope string and substring structure, +// because these optimizations become visible when a tool reports how much +// memory a structure consumes. +// +// However, fine granularity is not a goal. When a particular object is the +// exclusive owner of a separate block of memory, ubi::Node may present the +// object and its block as a single node, and add their sizes together when +// reporting the node's size, as there is no meaningful loss of data in this +// case. Thus, for example, a ubi::Node referring to a JavaScript object, when +// asked for the object's size in bytes, includes the object's slot and +// element arrays' sizes in the total. There is no separate ubi::Node value +// representing the slot and element arrays, since they are owned exclusively +// by the object. +// +// +// Presenting Analysis Results To JavaScript Developers +// +// If an analysis provides its results in terms of ubi::Node values, a user +// interface presenting those results will generally need to clean them up +// before they can be understood by JavaScript developers. For example, +// JavaScript developers should not need to understand shapes, only JavaScript +// objects. Similarly, they should not need to understand the distinction +// between DOM nodes and the JavaScript shadow objects that represent them. +// +// +// Rooting Restrictions +// +// At present there is no way to root ubi::Node instances, so instances can't be +// live across any operation that might GC. Analyses using ubi::Node must either +// run to completion and convert their results to some other rootable type, or +// save their intermediate state in some rooted structure if they must GC before +// they complete. (For algorithms like path-finding and dominator tree +// computation, we implement the algorithm avoiding any operation that could +// cause a GC --- and use AutoCheckCannotGC to verify this.) +// +// If this restriction prevents us from implementing interesting tools, we may +// teach the GC how to root ubi::Nodes, fix up hash tables that use them as +// keys, etc. +// +// +// Hostile Graph Structure +// +// Analyses consuming ubi::Node graphs must be robust when presented with graphs +// that are deliberately constructed to exploit their weaknesses. When operating +// on live graphs, web content has control over the object graph, and less +// direct control over shape and string structure, and analyses should be +// prepared to handle extreme cases gracefully. For example, if an analysis were +// to use the C++ stack in a depth-first traversal, carefully constructed +// content could cause the analysis to overflow the stack. +// +// When ubi::Nodes refer to nodes deserialized from a heap snapshot, analyses +// must be even more careful: since snapshots often come from potentially +// compromised e10s content processes, even properties normally guaranteed by +// the platform (the proper linking of DOM nodes, for example) might be +// corrupted. While it is the deserializer's responsibility to check the basic +// structure of the snapshot file, the analyses should be prepared for ubi::Node +// graphs constructed from snapshots to be even more bizarre. + +namespace js { +class BaseScript; +} // namespace js + +namespace JS { + +class JS_PUBLIC_API AutoCheckCannotGC; + +using ZoneSet = + js::HashSet<Zone*, js::DefaultHasher<Zone*>, js::SystemAllocPolicy>; + +using CompartmentSet = + js::HashSet<Compartment*, js::DefaultHasher<Compartment*>, + js::SystemAllocPolicy>; + +namespace ubi { + +class Edge; +class EdgeRange; +class StackFrame; + +using mozilla::Maybe; +using mozilla::RangedPtr; +using mozilla::Variant; + +template <typename T> +using Vector = mozilla::Vector<T, 0, js::SystemAllocPolicy>; + +/*** ubi::StackFrame **********************************************************/ + +// Concrete JS::ubi::StackFrame instances backed by a live SavedFrame object +// store their strings as JSAtom*, while deserialized stack frames from offline +// heap snapshots store their strings as const char16_t*. In order to provide +// zero-cost accessors to these strings in a single interface that works with +// both cases, we use this variant type. +class JS_PUBLIC_API AtomOrTwoByteChars + : public Variant<JSAtom*, const char16_t*> { + using Base = Variant<JSAtom*, const char16_t*>; + + public: + template <typename T> + MOZ_IMPLICIT AtomOrTwoByteChars(T&& rhs) : Base(std::forward<T>(rhs)) {} + + template <typename T> + AtomOrTwoByteChars& operator=(T&& rhs) { + MOZ_ASSERT(this != &rhs, "self-move disallowed"); + this->~AtomOrTwoByteChars(); + new (this) AtomOrTwoByteChars(std::forward<T>(rhs)); + return *this; + } + + // Return the length of the given AtomOrTwoByteChars string. + size_t length(); + + // Copy the given AtomOrTwoByteChars string into the destination buffer, + // inflating if necessary. Does NOT null terminate. Returns the number of + // characters written to destination. + size_t copyToBuffer(RangedPtr<char16_t> destination, size_t length); +}; + +// The base class implemented by each ConcreteStackFrame<T> type. Subclasses +// must not add data members to this class. +class BaseStackFrame { + friend class StackFrame; + + BaseStackFrame(const StackFrame&) = delete; + BaseStackFrame& operator=(const StackFrame&) = delete; + + protected: + void* ptr; + explicit BaseStackFrame(void* ptr) : ptr(ptr) {} + + public: + // This is a value type that should not have a virtual destructor. Don't add + // destructors in subclasses! + + // Get a unique identifier for this StackFrame. The identifier is not valid + // across garbage collections. + virtual uint64_t identifier() const { return uint64_t(uintptr_t(ptr)); } + + // Get this frame's parent frame. + virtual StackFrame parent() const = 0; + + // Get this frame's line number. + virtual uint32_t line() const = 0; + + // Get this frame's column number. + virtual uint32_t column() const = 0; + + // Get this frame's source name. Never null. + virtual AtomOrTwoByteChars source() const = 0; + + // Get a unique per-process ID for this frame's source. Defaults to zero. + virtual uint32_t sourceId() const = 0; + + // Return this frame's function name if named, otherwise the inferred + // display name. Can be null. + virtual AtomOrTwoByteChars functionDisplayName() const = 0; + + // Returns true if this frame's function is system JavaScript running with + // trusted principals, false otherwise. + virtual bool isSystem() const = 0; + + // Return true if this frame's function is a self-hosted JavaScript builtin, + // false otherwise. + virtual bool isSelfHosted(JSContext* cx) const = 0; + + // Construct a SavedFrame stack for the stack starting with this frame and + // containing all of its parents. The SavedFrame objects will be placed into + // cx's current compartment. + // + // Note that the process of + // + // SavedFrame + // | + // V + // JS::ubi::StackFrame + // | + // V + // offline heap snapshot + // | + // V + // JS::ubi::StackFrame + // | + // V + // SavedFrame + // + // is lossy because we cannot serialize and deserialize the SavedFrame's + // principals in the offline heap snapshot, so JS::ubi::StackFrame + // simplifies the principals check into the boolean isSystem() state. This + // is fine because we only expose JS::ubi::Stack to devtools and chrome + // code, and not to the web platform. + [[nodiscard]] virtual bool constructSavedFrameStack( + JSContext* cx, MutableHandleObject outSavedFrameStack) const = 0; + + // Trace the concrete implementation of JS::ubi::StackFrame. + virtual void trace(JSTracer* trc) = 0; +}; + +// A traits template with a specialization for each backing type that implements +// the ubi::BaseStackFrame interface. Each specialization must be the a subclass +// of ubi::BaseStackFrame. +template <typename T> +class ConcreteStackFrame; + +// A JS::ubi::StackFrame represents a frame in a recorded stack. It can be +// backed either by a live SavedFrame object or by a structure deserialized from +// an offline heap snapshot. +// +// It is a value type that may be memcpy'd hither and thither without worrying +// about constructors or destructors, similar to POD types. +// +// Its lifetime is the same as the lifetime of the graph that is being analyzed +// by the JS::ubi::Node that the JS::ubi::StackFrame came from. That is, if the +// graph being analyzed is the live heap graph, the JS::ubi::StackFrame is only +// valid within the scope of an AutoCheckCannotGC; if the graph being analyzed +// is an offline heap snapshot, the JS::ubi::StackFrame is valid as long as the +// offline heap snapshot is alive. +class StackFrame { + // Storage in which we allocate BaseStackFrame subclasses. + mozilla::AlignedStorage2<BaseStackFrame> storage; + + BaseStackFrame* base() { return storage.addr(); } + const BaseStackFrame* base() const { return storage.addr(); } + + template <typename T> + void construct(T* ptr) { + static_assert(std::is_base_of_v<BaseStackFrame, ConcreteStackFrame<T>>, + "ConcreteStackFrame<T> must inherit from BaseStackFrame"); + static_assert( + sizeof(ConcreteStackFrame<T>) == sizeof(*base()), + "ubi::ConcreteStackFrame<T> specializations must be the same size as " + "ubi::BaseStackFrame"); + ConcreteStackFrame<T>::construct(base(), ptr); + } + struct ConstructFunctor; + + public: + StackFrame() { construct<void>(nullptr); } + + template <typename T> + MOZ_IMPLICIT StackFrame(T* ptr) { + construct(ptr); + } + + template <typename T> + StackFrame& operator=(T* ptr) { + construct(ptr); + return *this; + } + + // Constructors accepting SpiderMonkey's generic-pointer-ish types. + + template <typename T> + explicit StackFrame(const JS::Handle<T*>& handle) { + construct(handle.get()); + } + + template <typename T> + StackFrame& operator=(const JS::Handle<T*>& handle) { + construct(handle.get()); + return *this; + } + + template <typename T> + explicit StackFrame(const JS::Rooted<T*>& root) { + construct(root.get()); + } + + template <typename T> + StackFrame& operator=(const JS::Rooted<T*>& root) { + construct(root.get()); + return *this; + } + + // Because StackFrame is just a vtable pointer and an instance pointer, we + // can memcpy everything around instead of making concrete classes define + // virtual constructors. See the comment above Node's copy constructor for + // more details; that comment applies here as well. + StackFrame(const StackFrame& rhs) { + memcpy(storage.u.mBytes, rhs.storage.u.mBytes, sizeof(storage.u)); + } + + StackFrame& operator=(const StackFrame& rhs) { + memcpy(storage.u.mBytes, rhs.storage.u.mBytes, sizeof(storage.u)); + return *this; + } + + bool operator==(const StackFrame& rhs) const { + return base()->ptr == rhs.base()->ptr; + } + bool operator!=(const StackFrame& rhs) const { return !(*this == rhs); } + + explicit operator bool() const { return base()->ptr != nullptr; } + + // Copy this StackFrame's source name into the given |destination| + // buffer. Copy no more than |length| characters. The result is *not* null + // terminated. Returns how many characters were written into the buffer. + size_t source(RangedPtr<char16_t> destination, size_t length) const; + + // Copy this StackFrame's function display name into the given |destination| + // buffer. Copy no more than |length| characters. The result is *not* null + // terminated. Returns how many characters were written into the buffer. + size_t functionDisplayName(RangedPtr<char16_t> destination, + size_t length) const; + + // Get the size of the respective strings. 0 is returned for null strings. + size_t sourceLength(); + size_t functionDisplayNameLength(); + + // Methods that forward to virtual calls through BaseStackFrame. + + void trace(JSTracer* trc) { base()->trace(trc); } + uint64_t identifier() const { + auto id = base()->identifier(); + MOZ_ASSERT(JS::Value::isNumberRepresentable(id)); + return id; + } + uint32_t line() const { return base()->line(); } + uint32_t column() const { return base()->column(); } + AtomOrTwoByteChars source() const { return base()->source(); } + uint32_t sourceId() const { return base()->sourceId(); } + AtomOrTwoByteChars functionDisplayName() const { + return base()->functionDisplayName(); + } + StackFrame parent() const { return base()->parent(); } + bool isSystem() const { return base()->isSystem(); } + bool isSelfHosted(JSContext* cx) const { return base()->isSelfHosted(cx); } + [[nodiscard]] bool constructSavedFrameStack( + JSContext* cx, MutableHandleObject outSavedFrameStack) const { + return base()->constructSavedFrameStack(cx, outSavedFrameStack); + } + + struct HashPolicy { + using Lookup = JS::ubi::StackFrame; + + static js::HashNumber hash(const Lookup& lookup) { + return mozilla::HashGeneric(lookup.identifier()); + } + + static bool match(const StackFrame& key, const Lookup& lookup) { + return key == lookup; + } + + static void rekey(StackFrame& k, const StackFrame& newKey) { k = newKey; } + }; +}; + +// The ubi::StackFrame null pointer. Any attempt to operate on a null +// ubi::StackFrame crashes. +template <> +class ConcreteStackFrame<void> : public BaseStackFrame { + explicit ConcreteStackFrame(void* ptr) : BaseStackFrame(ptr) {} + + public: + static void construct(void* storage, void*) { + new (storage) ConcreteStackFrame(nullptr); + } + + uint64_t identifier() const override { return 0; } + void trace(JSTracer* trc) override {} + [[nodiscard]] bool constructSavedFrameStack( + JSContext* cx, MutableHandleObject out) const override { + out.set(nullptr); + return true; + } + + uint32_t line() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } + uint32_t column() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } + AtomOrTwoByteChars source() const override { + MOZ_CRASH("null JS::ubi::StackFrame"); + } + uint32_t sourceId() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } + AtomOrTwoByteChars functionDisplayName() const override { + MOZ_CRASH("null JS::ubi::StackFrame"); + } + StackFrame parent() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } + bool isSystem() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } + bool isSelfHosted(JSContext* cx) const override { + MOZ_CRASH("null JS::ubi::StackFrame"); + } +}; + +[[nodiscard]] JS_PUBLIC_API bool ConstructSavedFrameStackSlow( + JSContext* cx, JS::ubi::StackFrame& frame, + MutableHandleObject outSavedFrameStack); + +/*** ubi::Node + * ************************************************************************************/ + +// A concrete node specialization can claim its referent is a member of a +// particular "coarse type" which is less specific than the actual +// implementation type but generally more palatable for web developers. For +// example, JitCode can be considered to have a coarse type of "Script". This is +// used by some analyses for putting nodes into different buckets. The default, +// if a concrete specialization does not provide its own mapping to a CoarseType +// variant, is "Other". +// +// NB: the values associated with a particular enum variant must not change or +// be reused for new variants. Doing so will cause inspecting ubi::Nodes backed +// by an offline heap snapshot from an older SpiderMonkey/Firefox version to +// break. Consider this enum append only. +enum class CoarseType : uint32_t { + Other = 0, + Object = 1, + Script = 2, + String = 3, + DOMNode = 4, + + FIRST = Other, + LAST = DOMNode +}; + +/** + * Convert a CoarseType enum into a string. The string is statically allocated. + */ +JS_PUBLIC_API const char* CoarseTypeToString(CoarseType type); + +inline uint32_t CoarseTypeToUint32(CoarseType type) { + return static_cast<uint32_t>(type); +} + +inline bool Uint32IsValidCoarseType(uint32_t n) { + auto first = static_cast<uint32_t>(CoarseType::FIRST); + auto last = static_cast<uint32_t>(CoarseType::LAST); + MOZ_ASSERT(first < last); + return first <= n && n <= last; +} + +inline CoarseType Uint32ToCoarseType(uint32_t n) { + MOZ_ASSERT(Uint32IsValidCoarseType(n)); + return static_cast<CoarseType>(n); +} + +// The base class implemented by each ubi::Node referent type. Subclasses must +// not add data members to this class. +class JS_PUBLIC_API Base { + friend class Node; + + // For performance's sake, we'd prefer to avoid a virtual destructor; and + // an empty constructor seems consistent with the 'lightweight value type' + // visible behavior we're trying to achieve. But if the destructor isn't + // virtual, and a subclass overrides it, the subclass's destructor will be + // ignored. Is there a way to make the compiler catch that error? + + protected: + // Space for the actual pointer. Concrete subclasses should define a + // properly typed 'get' member function to access this. + void* ptr; + + explicit Base(void* ptr) : ptr(ptr) {} + + public: + bool operator==(const Base& rhs) const { + // Some compilers will indeed place objects of different types at + // the same address, so technically, we should include the vtable + // in this comparison. But it seems unlikely to cause problems in + // practice. + return ptr == rhs.ptr; + } + bool operator!=(const Base& rhs) const { return !(*this == rhs); } + + // An identifier for this node, guaranteed to be stable and unique for as + // long as this ubi::Node's referent is alive and at the same address. + // + // This is probably suitable for use in serializations, as it is an integral + // type. It may also help save memory when constructing HashSets of + // ubi::Nodes: since a uint64_t will always be smaller-or-equal-to the size + // of a ubi::Node, a HashSet<ubi::Node::Id> may use less space per element + // than a HashSet<ubi::Node>. + // + // (Note that 'unique' only means 'up to equality on ubi::Node'; see the + // caveats about multiple objects allocated at the same address for + // 'ubi::Node::operator=='.) + using Id = uint64_t; + virtual Id identifier() const { return Id(uintptr_t(ptr)); } + + // Returns true if this node is pointing to something on the live heap, as + // opposed to something from a deserialized core dump. Returns false, + // otherwise. + virtual bool isLive() const { return true; }; + + // Return the coarse-grained type-of-thing that this node represents. + virtual CoarseType coarseType() const { return CoarseType::Other; } + + // Return a human-readable name for the referent's type. The result should + // be statically allocated. (You can use u"strings" for this.) + // + // This must always return Concrete<T>::concreteTypeName; we use that + // pointer as a tag for this particular referent type. + virtual const char16_t* typeName() const = 0; + + // Return the size of this node, in bytes. Include any structures that this + // node owns exclusively that are not exposed as their own ubi::Nodes. + // |mallocSizeOf| should be a malloc block sizing function; see + // |mfbt/MemoryReporting.h|. + // + // Because we can use |JS::ubi::Node|s backed by a snapshot that was taken + // on a 64-bit platform when we are currently on a 32-bit platform, we + // cannot rely on |size_t| for node sizes. Instead, |Size| is uint64_t on + // all platforms. + using Size = uint64_t; + virtual Size size(mozilla::MallocSizeOf mallocSizeof) const { return 1; } + + // Return an EdgeRange that initially contains all the referent's outgoing + // edges. The caller takes ownership of the EdgeRange. + // + // If wantNames is true, compute names for edges. Doing so can be expensive + // in time and memory. + virtual js::UniquePtr<EdgeRange> edges(JSContext* cx, + bool wantNames) const = 0; + + // Return the Zone to which this node's referent belongs, or nullptr if the + // referent is not of a type allocated in SpiderMonkey Zones. + virtual JS::Zone* zone() const { return nullptr; } + + // Return the compartment for this node. Some ubi::Node referents are not + // associated with Compartments, such as JSStrings (which are associated + // with Zones). When the referent is not associated with a compartment, + // nullptr is returned. + virtual JS::Compartment* compartment() const { return nullptr; } + + // Return the realm for this node. Some ubi::Node referents are not + // associated with Realms, such as JSStrings (which are associated + // with Zones) or cross-compartment wrappers (which are associated with + // compartments). When the referent is not associated with a realm, + // nullptr is returned. + virtual JS::Realm* realm() const { return nullptr; } + + // Return whether this node's referent's allocation stack was captured. + virtual bool hasAllocationStack() const { return false; } + + // Get the stack recorded at the time this node's referent was + // allocated. This must only be called when hasAllocationStack() is true. + virtual StackFrame allocationStack() const { + MOZ_CRASH( + "Concrete classes that have an allocation stack must override both " + "hasAllocationStack and allocationStack."); + } + + // In some cases, Concrete<T> can return a more descriptive + // referent type name than simply `T`. This method returns an + // identifier as specific as is efficiently available. + // The string returned is borrowed from the ubi::Node's referent. + // If nothing more specific than typeName() is available, return nullptr. + virtual const char16_t* descriptiveTypeName() const { return nullptr; } + + // Methods for JSObject Referents + // + // These methods are only semantically valid if the referent is either a + // JSObject in the live heap, or represents a previously existing JSObject + // from some deserialized heap snapshot. + + // Return the object's [[Class]]'s name. + virtual const char* jsObjectClassName() const { return nullptr; } + + // Methods for CoarseType::Script referents + + // Return the script's source's filename if available. If unavailable, + // return nullptr. + virtual const char* scriptFilename() const { return nullptr; } + + private: + Base(const Base& rhs) = delete; + Base& operator=(const Base& rhs) = delete; +}; + +// A traits template with a specialization for each referent type that +// ubi::Node supports. The specialization must be the concrete subclass of Base +// that represents a pointer to the referent type. It must include these +// members: +// +// // The specific char16_t array returned by Concrete<T>::typeName(). +// static const char16_t concreteTypeName[]; +// +// // Construct an instance of this concrete class in |storage| referring +// // to |referent|. Implementations typically use a placement 'new'. +// // +// // In some cases, |referent| will contain dynamic type information that +// // identifies it a some more specific subclass of |Referent|. For +// // example, when |Referent| is |JSObject|, then |referent->getClass()| +// // could tell us that it's actually a JSFunction. Similarly, if +// // |Referent| is |nsISupports|, we would like a ubi::Node that knows its +// // final implementation type. +// // +// // So we delegate the actual construction to this specialization, which +// // knows Referent's details. +// static void construct(void* storage, Referent* referent); +template <typename Referent> +class Concrete; + +// A container for a Base instance; all members simply forward to the contained +// instance. This container allows us to pass ubi::Node instances by value. +class Node { + // Storage in which we allocate Base subclasses. + mozilla::AlignedStorage2<Base> storage; + Base* base() { return storage.addr(); } + const Base* base() const { return storage.addr(); } + + template <typename T> + void construct(T* ptr) { + static_assert( + sizeof(Concrete<T>) == sizeof(*base()), + "ubi::Base specializations must be the same size as ubi::Base"); + static_assert(std::is_base_of_v<Base, Concrete<T>>, + "ubi::Concrete<T> must inherit from ubi::Base"); + Concrete<T>::construct(base(), ptr); + } + struct ConstructFunctor; + + public: + Node() { construct<void>(nullptr); } + + template <typename T> + MOZ_IMPLICIT Node(T* ptr) { + construct(ptr); + } + template <typename T> + Node& operator=(T* ptr) { + construct(ptr); + return *this; + } + + // We can construct and assign from rooted forms of pointers. + template <typename T> + MOZ_IMPLICIT Node(const Rooted<T*>& root) { + construct(root.get()); + } + template <typename T> + Node& operator=(const Rooted<T*>& root) { + construct(root.get()); + return *this; + } + + // Constructors accepting SpiderMonkey's other generic-pointer-ish types. + // Note that we *do* want an implicit constructor here: JS::Value and + // JS::ubi::Node are both essentially tagged references to other sorts of + // objects, so letting conversions happen automatically is appropriate. + MOZ_IMPLICIT Node(JS::HandleValue value); + explicit Node(JS::GCCellPtr thing); + + // copy construction and copy assignment just use memcpy, since we know + // instances contain nothing but a vtable pointer and a data pointer. + // + // To be completely correct, concrete classes could provide a virtual + // 'construct' member function, which we could invoke on rhs to construct an + // instance in our storage. But this is good enough; there's no need to jump + // through vtables for copying and assignment that are just going to move + // two words around. The compiler knows how to optimize memcpy. + Node(const Node& rhs) { + memcpy(storage.u.mBytes, rhs.storage.u.mBytes, sizeof(storage.u)); + } + + Node& operator=(const Node& rhs) { + memcpy(storage.u.mBytes, rhs.storage.u.mBytes, sizeof(storage.u)); + return *this; + } + + bool operator==(const Node& rhs) const { return *base() == *rhs.base(); } + bool operator!=(const Node& rhs) const { return *base() != *rhs.base(); } + + explicit operator bool() const { return base()->ptr != nullptr; } + + bool isLive() const { return base()->isLive(); } + + // Get the canonical type name for the given type T. + template <typename T> + static const char16_t* canonicalTypeName() { + return Concrete<T>::concreteTypeName; + } + + template <typename T> + bool is() const { + return base()->typeName() == canonicalTypeName<T>(); + } + + template <typename T> + T* as() const { + MOZ_ASSERT(isLive()); + MOZ_ASSERT(this->is<T>()); + return static_cast<T*>(base()->ptr); + } + + template <typename T> + T* asOrNull() const { + MOZ_ASSERT(isLive()); + return this->is<T>() ? static_cast<T*>(base()->ptr) : nullptr; + } + + // If this node refers to something that can be represented as a JavaScript + // value that is safe to expose to JavaScript code, return that value. + // Otherwise return UndefinedValue(). JSStrings, JS::Symbols, and some (but + // not all!) JSObjects can be exposed. + JS::Value exposeToJS() const; + + CoarseType coarseType() const { return base()->coarseType(); } + const char16_t* typeName() const { return base()->typeName(); } + JS::Zone* zone() const { return base()->zone(); } + JS::Compartment* compartment() const { return base()->compartment(); } + JS::Realm* realm() const { return base()->realm(); } + const char* jsObjectClassName() const { return base()->jsObjectClassName(); } + const char16_t* descriptiveTypeName() const { + return base()->descriptiveTypeName(); + } + + const char* scriptFilename() const { return base()->scriptFilename(); } + + using Size = Base::Size; + Size size(mozilla::MallocSizeOf mallocSizeof) const { + auto size = base()->size(mallocSizeof); + MOZ_ASSERT( + size > 0, + "C++ does not have zero-sized types! Choose 1 if you just need a " + "conservative default."); + return size; + } + + js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames = true) const { + return base()->edges(cx, wantNames); + } + + bool hasAllocationStack() const { return base()->hasAllocationStack(); } + StackFrame allocationStack() const { return base()->allocationStack(); } + + using Id = Base::Id; + Id identifier() const { + auto id = base()->identifier(); + MOZ_ASSERT(JS::Value::isNumberRepresentable(id)); + return id; + } + + // A hash policy for ubi::Nodes. + // This simply uses the stock PointerHasher on the ubi::Node's pointer. + // We specialize DefaultHasher below to make this the default. + class HashPolicy { + typedef js::PointerHasher<void*> PtrHash; + + public: + typedef Node Lookup; + + static js::HashNumber hash(const Lookup& l) { + return PtrHash::hash(l.base()->ptr); + } + static bool match(const Node& k, const Lookup& l) { return k == l; } + static void rekey(Node& k, const Node& newKey) { k = newKey; } + }; +}; + +using NodeSet = + js::HashSet<Node, js::DefaultHasher<Node>, js::SystemAllocPolicy>; +using NodeSetPtr = mozilla::UniquePtr<NodeSet, JS::DeletePolicy<NodeSet>>; + +/*** Edge and EdgeRange *******************************************************/ + +using EdgeName = UniqueTwoByteChars; + +// An outgoing edge to a referent node. +class Edge { + public: + Edge() = default; + + // Construct an initialized Edge, taking ownership of |name|. + Edge(char16_t* name, const Node& referent) : name(name), referent(referent) {} + + // Move construction and assignment. + Edge(Edge&& rhs) : name(std::move(rhs.name)), referent(rhs.referent) {} + + Edge& operator=(Edge&& rhs) { + MOZ_ASSERT(&rhs != this); + this->~Edge(); + new (this) Edge(std::move(rhs)); + return *this; + } + + Edge(const Edge&) = delete; + Edge& operator=(const Edge&) = delete; + + // This edge's name. This may be nullptr, if Node::edges was called with + // false as the wantNames parameter. + // + // The storage is owned by this Edge, and will be freed when this Edge is + // destructed. You may take ownership of the name by `std::move`ing it + // out of the edge; it is just a UniquePtr. + // + // (In real life we'll want a better representation for names, to avoid + // creating tons of strings when the names follow a pattern; and we'll need + // to think about lifetimes carefully to ensure traversal stays cheap.) + EdgeName name = nullptr; + + // This edge's referent. + Node referent; +}; + +// EdgeRange is an abstract base class for iterating over a node's outgoing +// edges. (This is modeled after js::HashTable<K,V>::Range.) +// +// Concrete instances of this class need not be as lightweight as Node itself, +// since they're usually only instantiated while iterating over a particular +// object's edges. For example, a dumb implementation for JS Cells might use +// JS::TraceChildren to to get the outgoing edges, and then store them in an +// array internal to the EdgeRange. +class EdgeRange { + protected: + // The current front edge of this range, or nullptr if this range is empty. + Edge* front_; + + EdgeRange() : front_(nullptr) {} + + public: + virtual ~EdgeRange() = default; + + // True if there are no more edges in this range. + bool empty() const { return !front_; } + + // The front edge of this range. This is owned by the EdgeRange, and is + // only guaranteed to live until the next call to popFront, or until + // the EdgeRange is destructed. + const Edge& front() const { return *front_; } + Edge& front() { return *front_; } + + // Remove the front edge from this range. This should only be called if + // !empty(). + virtual void popFront() = 0; + + private: + EdgeRange(const EdgeRange&) = delete; + EdgeRange& operator=(const EdgeRange&) = delete; +}; + +typedef mozilla::Vector<Edge, 8, js::SystemAllocPolicy> EdgeVector; + +// An EdgeRange concrete class that holds a pre-existing vector of +// Edges. A PreComputedEdgeRange does not take ownership of its +// EdgeVector; it is up to the PreComputedEdgeRange's consumer to manage +// that lifetime. +class PreComputedEdgeRange : public EdgeRange { + EdgeVector& edges; + size_t i; + + void settle() { front_ = i < edges.length() ? &edges[i] : nullptr; } + + public: + explicit PreComputedEdgeRange(EdgeVector& edges) : edges(edges), i(0) { + settle(); + } + + void popFront() override { + MOZ_ASSERT(!empty()); + i++; + settle(); + } +}; + +/*** RootList *****************************************************************/ + +// RootList is a class that can be pointed to by a |ubi::Node|, creating a +// fictional root-of-roots which has edges to every GC root in the JS +// runtime. Having a single root |ubi::Node| is useful for algorithms written +// with the assumption that there aren't multiple roots (such as computing +// dominator trees) and you want a single point of entry. It also ensures that +// the roots themselves get visited by |ubi::BreadthFirst| (they would otherwise +// only be used as starting points). +// +// RootList::init itself causes a minor collection, but once the list of roots +// has been created, GC must not occur, as the referent ubi::Nodes are not +// stable across GC. It returns a [[nodiscard]] AutoCheckCannotGC token in order +// to enforce this. The token's lifetime must extend at least as long as the +// RootList itself. Note that the RootList does not itself contain a nogc field, +// which means that it is possible to store it somewhere that it can escape +// the init()'s nogc scope. Don't do that. (Or you could call some function +// and pass in the RootList and GC, but that would be caught.) +// +// Example usage: +// +// { +// JS::ubi::RootList rootList(cx); +// auto [ok, nogc] = rootList.init(); +// if (!ok()) { +// return false; +// } +// +// JS::ubi::Node root(&rootList); +// +// ... +// } +class MOZ_STACK_CLASS JS_PUBLIC_API RootList { + public: + JSContext* cx; + EdgeVector edges; + bool wantNames; + bool inited; + + explicit RootList(JSContext* cx, bool wantNames = false); + + // Find all GC roots. + [[nodiscard]] std::pair<bool, JS::AutoCheckCannotGC> init(); + // Find only GC roots in the provided set of |JS::Compartment|s. Note: it's + // important to take a CompartmentSet and not a RealmSet: objects in + // same-compartment realms can reference each other directly, without going + // through CCWs, so if we used a RealmSet here we would miss edges. + [[nodiscard]] std::pair<bool, JS::AutoCheckCannotGC> init( + CompartmentSet& debuggees); + // Find only GC roots in the given Debugger object's set of debuggee + // compartments. + [[nodiscard]] std::pair<bool, JS::AutoCheckCannotGC> init( + HandleObject debuggees); + + // Returns true if the RootList has been initialized successfully, false + // otherwise. + bool initialized() { return inited; } + + // Explicitly add the given Node as a root in this RootList. If wantNames is + // true, you must pass an edgeName. The RootList does not take ownership of + // edgeName. + [[nodiscard]] bool addRoot(Node node, const char16_t* edgeName = nullptr); +}; + +/*** Concrete classes for ubi::Node referent types ****************************/ + +template <> +class JS_PUBLIC_API Concrete<RootList> : public Base { + protected: + explicit Concrete(RootList* ptr) : Base(ptr) {} + RootList& get() const { return *static_cast<RootList*>(ptr); } + + public: + static void construct(void* storage, RootList* ptr) { + new (storage) Concrete(ptr); + } + + js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override; + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +// A reusable ubi::Concrete specialization base class for types supported by +// JS::TraceChildren. +template <typename Referent> +class JS_PUBLIC_API TracerConcrete : public Base { + JS::Zone* zone() const override; + + public: + js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override; + + protected: + explicit TracerConcrete(Referent* ptr) : Base(ptr) {} + Referent& get() const { return *static_cast<Referent*>(ptr); } +}; + +// For JS::TraceChildren-based types that have 'realm' and 'compartment' +// methods. +template <typename Referent> +class JS_PUBLIC_API TracerConcreteWithRealm : public TracerConcrete<Referent> { + typedef TracerConcrete<Referent> TracerBase; + JS::Compartment* compartment() const override; + JS::Realm* realm() const override; + + protected: + explicit TracerConcreteWithRealm(Referent* ptr) : TracerBase(ptr) {} +}; + +// Define specializations for some commonly-used public JSAPI types. +// These can use the generic templates above. +template <> +class JS_PUBLIC_API Concrete<JS::Symbol> : TracerConcrete<JS::Symbol> { + protected: + explicit Concrete(JS::Symbol* ptr) : TracerConcrete(ptr) {} + + public: + static void construct(void* storage, JS::Symbol* ptr) { + new (storage) Concrete(ptr); + } + + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +template <> +class JS_PUBLIC_API Concrete<JS::BigInt> : TracerConcrete<JS::BigInt> { + protected: + explicit Concrete(JS::BigInt* ptr) : TracerConcrete(ptr) {} + + public: + static void construct(void* storage, JS::BigInt* ptr) { + new (storage) Concrete(ptr); + } + + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +template <> +class JS_PUBLIC_API Concrete<js::BaseScript> + : TracerConcreteWithRealm<js::BaseScript> { + protected: + explicit Concrete(js::BaseScript* ptr) + : TracerConcreteWithRealm<js::BaseScript>(ptr) {} + + public: + static void construct(void* storage, js::BaseScript* ptr) { + new (storage) Concrete(ptr); + } + + CoarseType coarseType() const final { return CoarseType::Script; } + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + const char* scriptFilename() const final; + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +// The JSObject specialization. +template <> +class JS_PUBLIC_API Concrete<JSObject> : public TracerConcrete<JSObject> { + protected: + explicit Concrete(JSObject* ptr) : TracerConcrete<JSObject>(ptr) {} + + public: + static void construct(void* storage, JSObject* ptr); + + JS::Compartment* compartment() const override; + JS::Realm* realm() const override; + + const char* jsObjectClassName() const override; + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + bool hasAllocationStack() const override; + StackFrame allocationStack() const override; + + CoarseType coarseType() const final { return CoarseType::Object; } + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +// For JSString, we extend the generic template with a 'size' implementation. +template <> +class JS_PUBLIC_API Concrete<JSString> : TracerConcrete<JSString> { + protected: + explicit Concrete(JSString* ptr) : TracerConcrete<JSString>(ptr) {} + + public: + static void construct(void* storage, JSString* ptr) { + new (storage) Concrete(ptr); + } + + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + CoarseType coarseType() const final { return CoarseType::String; } + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +// The ubi::Node null pointer. Any attempt to operate on a null ubi::Node +// asserts. +template <> +class JS_PUBLIC_API Concrete<void> : public Base { + const char16_t* typeName() const override; + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override; + JS::Zone* zone() const override; + JS::Compartment* compartment() const override; + JS::Realm* realm() const override; + CoarseType coarseType() const final; + + explicit Concrete(void* ptr) : Base(ptr) {} + + public: + static void construct(void* storage, void* ptr) { + new (storage) Concrete(ptr); + } +}; + +// The |callback| callback is much like the |Concrete<T>::construct| method: a +// call to |callback| should construct an instance of the most appropriate +// JS::ubi::Base subclass for |obj| in |storage|. The callback may assume that +// |obj->getClass()->isDOMClass()|, and that |storage| refers to the +// sizeof(JS::ubi::Base) bytes of space that all ubi::Base implementations +// should require. + +// Set |cx|'s runtime hook for constructing ubi::Nodes for DOM classes to +// |callback|. +void SetConstructUbiNodeForDOMObjectCallback(JSContext* cx, + void (*callback)(void*, + JSObject*)); + +} // namespace ubi +} // namespace JS + +namespace mozilla { + +// Make ubi::Node::HashPolicy the default hash policy for ubi::Node. +template <> +struct DefaultHasher<JS::ubi::Node> : JS::ubi::Node::HashPolicy {}; +template <> +struct DefaultHasher<JS::ubi::StackFrame> : JS::ubi::StackFrame::HashPolicy {}; + +} // namespace mozilla + +#endif // js_UbiNode_h diff --git a/js/public/UbiNodeBreadthFirst.h b/js/public/UbiNodeBreadthFirst.h new file mode 100644 index 0000000000..fc2318a153 --- /dev/null +++ b/js/public/UbiNodeBreadthFirst.h @@ -0,0 +1,271 @@ +/* -*- 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 js_UbiNodeBreadthFirst_h +#define js_UbiNodeBreadthFirst_h + +#include "js/HashTable.h" +#include "js/UbiNode.h" +#include "js/Vector.h" + +namespace JS { +namespace ubi { + +// A breadth-first traversal template for graphs of ubi::Nodes. +// +// No GC may occur while an instance of this template is live. +// +// The provided Handler type should have two members: +// +// typename NodeData; +// +// The value type of |BreadthFirst<Handler>::visited|, the HashMap of +// ubi::Nodes that have been visited so far. Since the algorithm needs a +// hash table like this for its own use anyway, it is simple to let +// Handler store its own metadata about each node in the same table. +// +// For example, if you want to find a shortest path to each node from any +// traversal starting point, your |NodeData| type could record the first +// edge to reach each node, and the node from which it originates. Then, +// when the traversal is complete, you can walk backwards from any node +// to some starting point, and the path recorded will be a shortest path. +// +// This type must have a default constructor. If this type owns any other +// resources, move constructors and assignment operators are probably a +// good idea, too. +// +// bool operator() (BreadthFirst& traversal, +// Node origin, const Edge& edge, +// Handler::NodeData* referentData, bool first); +// +// The visitor function, called to report that we have traversed +// |edge| from |origin|. This is called once for each edge we traverse. +// As this is a breadth-first search, any prior calls to the visitor +// function were for origin nodes not further from the start nodes than +// |origin|. +// +// |traversal| is this traversal object, passed along for convenience. +// +// |referentData| is a pointer to the value of the entry in +// |traversal.visited| for |edge.referent|; the visitor function can +// store whatever metadata it likes about |edge.referent| there. +// +// |first| is true if this is the first time we have visited an edge +// leading to |edge.referent|. This could be stored in NodeData, but +// the algorithm knows whether it has just created the entry in +// |traversal.visited|, so it passes it along for convenience. +// +// The visitor function may call |traversal.abandonReferent()| if it +// doesn't want to traverse the outgoing edges of |edge.referent|. You can +// use this to limit the traversal to a given portion of the graph: it will +// never visit nodes reachable only through nodes that you have abandoned. +// Note that |abandonReferent| must be called the first time the given node +// is reached; that is, |first| must be true. +// +// The visitor function may call |doNotMarkReferentAsVisited()| if it +// does not want a node to be considered 'visited' (and added to the +// 'visited' set). This is useful when the visitor has custom logic to +// determine whether an edge is 'interesting'. +// +// The visitor function may call |traversal.stop()| if it doesn't want +// to visit any more nodes at all. +// +// The visitor function may consult |traversal.visited| for information +// about other nodes, but it should not add or remove entries. +// +// The visitor function should return true on success, or false if an +// error occurs. A false return value terminates the traversal +// immediately, and causes BreadthFirst<Handler>::traverse to return +// false. +template <typename Handler> +struct BreadthFirst { + // Construct a breadth-first traversal object that reports the nodes it + // reaches to |handler|. The traversal asserts that no GC happens in its + // runtime during its lifetime. + // + // We do nothing with noGC, other than require it to exist, with a lifetime + // that encloses our own. + BreadthFirst(JSContext* cx, Handler& handler, const JS::AutoRequireNoGC& noGC) + : wantNames(true), + cx(cx), + visited(), + handler(handler), + pending(), + traversalBegun(false), + stopRequested(false), + abandonRequested(false), + markReferentAsVisited(false) {} + + // Add |node| as a starting point for the traversal. You may add + // as many starting points as you like. Return false on OOM. + bool addStart(Node node) { return pending.append(node); } + + // Add |node| as a starting point for the traversal (see addStart) and also + // add it to the |visited| set. Return false on OOM. + bool addStartVisited(Node node) { + typename NodeMap::AddPtr ptr = visited.lookupForAdd(node); + if (!ptr && !visited.add(ptr, node, typename Handler::NodeData())) { + return false; + } + return addStart(node); + } + + // True if the handler wants us to compute edge names; doing so can be + // expensive in time and memory. True by default. + bool wantNames; + + // Traverse the graph in breadth-first order, starting at the given + // start nodes, applying |handler::operator()| for each edge traversed + // as described above. + // + // This should be called only once per instance of this class. + // + // Return false on OOM or error return from |handler::operator()|. + bool traverse() { + MOZ_ASSERT(!traversalBegun); + traversalBegun = true; + + // While there are pending nodes, visit them. + while (!pending.empty()) { + Node origin = pending.front(); + pending.popFront(); + + // Get a range containing all origin's outgoing edges. + auto range = origin.edges(cx, wantNames); + if (!range) { + return false; + } + + // Traverse each edge. + for (; !range->empty(); range->popFront()) { + MOZ_ASSERT(!stopRequested); + + Edge& edge = range->front(); + typename NodeMap::AddPtr a = visited.lookupForAdd(edge.referent); + bool first = !a; + + // Pass a pointer to a stack-allocated NodeData if the referent is not + // in |visited|. + typename Handler::NodeData nodeData; + typename Handler::NodeData* nodeDataPtr = + first ? &nodeData : &a->value(); + + // Report this edge to the visitor function. + markReferentAsVisited = true; + if (!handler(*this, origin, edge, nodeDataPtr, first)) { + return false; + } + + if (first && markReferentAsVisited) { + // This is the first time we've reached |edge.referent| and the + // handler wants it marked as visited. + if (!visited.add(a, edge.referent, std::move(nodeData))) { + return false; + } + } + + if (stopRequested) { + return true; + } + + // Arrange to traverse this edge's referent's outgoing edges + // later --- unless |handler| asked us not to. + if (abandonRequested) { + // Skip the enqueue; reset flag for future iterations. + abandonRequested = false; + } else if (first) { + if (!pending.append(edge.referent)) { + return false; + } + } + } + } + + return true; + } + + // Stop traversal, and return true from |traverse| without visiting any + // more nodes. Only |handler::operator()| should call this function; it + // may do so to stop the traversal early, without returning false and + // then making |traverse|'s caller disambiguate that result from a real + // error. + void stop() { stopRequested = true; } + + // Request that the current edge's referent's outgoing edges not be + // traversed. This must be called the first time that referent is reached. + // Other edges *to* that referent will still be traversed. + void abandonReferent() { abandonRequested = true; } + + // Request the the current edge's referent not be added to the |visited| set + // if this is the first time we're visiting it. + void doNotMarkReferentAsVisited() { markReferentAsVisited = false; } + + // The context with which we were constructed. + JSContext* cx; + + // A map associating each node N that we have reached with a + // Handler::NodeData, for |handler|'s use. This is public, so that + // |handler| can access it to see the traversal thus far. + using NodeMap = js::HashMap<Node, typename Handler::NodeData, + js::DefaultHasher<Node>, js::SystemAllocPolicy>; + NodeMap visited; + + private: + // Our handler object. + Handler& handler; + + // A queue template. Appending and popping the front are constant time. + // Wasted space is never more than some recent actual population plus the + // current population. + template <typename T> + class Queue { + js::Vector<T, 0, js::SystemAllocPolicy> head, tail; + size_t frontIndex; + + public: + Queue() : head(), tail(), frontIndex(0) {} + bool empty() { return frontIndex >= head.length(); } + T& front() { + MOZ_ASSERT(!empty()); + return head[frontIndex]; + } + void popFront() { + MOZ_ASSERT(!empty()); + frontIndex++; + if (frontIndex >= head.length()) { + head.clearAndFree(); + head.swap(tail); + frontIndex = 0; + } + } + bool append(const T& elt) { + return frontIndex == 0 ? head.append(elt) : tail.append(elt); + } + }; + + // A queue of nodes that we have reached, but whose outgoing edges we + // have not yet traversed. Nodes reachable in fewer edges are enqueued + // earlier. + Queue<Node> pending; + + // True if our traverse function has been called. + bool traversalBegun; + + // True if we've been asked to stop the traversal. + bool stopRequested; + + // True if we've been asked to abandon the current edge's referent. + bool abandonRequested; + + // True if the node should be added to the |visited| set after calling the + // handler. + bool markReferentAsVisited; +}; + +} // namespace ubi +} // namespace JS + +#endif // js_UbiNodeBreadthFirst_h diff --git a/js/public/UbiNodeCensus.h b/js/public/UbiNodeCensus.h new file mode 100644 index 0000000000..4086300580 --- /dev/null +++ b/js/public/UbiNodeCensus.h @@ -0,0 +1,231 @@ +/* -*- 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 js_UbiNodeCensus_h +#define js_UbiNodeCensus_h + +#include "js/UbiNode.h" +#include "js/UbiNodeBreadthFirst.h" + +// A census is a ubi::Node traversal that assigns each node to one or more +// buckets, and returns a report with the size of each bucket. +// +// We summarize the results of a census with counts broken down according to +// criteria selected by the API consumer code that is requesting the census. For +// example, the following breakdown might give an interesting overview of the +// heap: +// +// - all nodes +// - objects +// - objects with a specific [[Class]] * +// - strings +// - scripts +// - DOM nodes +// - nsINodes with a specific name (found in nsINode::NodeName()) * +// - all other Node types +// - nodes with a specific ubi::Node::typeName * +// +// Obviously, the parts of this tree marked with * represent many separate +// counts, depending on how many distinct [[Class]] values and ubi::Node type +// names we encounter. +// +// The supported types of breakdowns are documented in +// js/src/doc/Debugger/Debugger.Memory.md. +// +// When we parse the 'breakdown' argument to takeCensus, we build a tree of +// CountType nodes. For example, for the breakdown shown in the +// Debugger.Memory.prototype.takeCensus, documentation: +// +// { +// by: "coarseType", +// objects: { by: "objectClass" }, +// other: { by: "internalType" }, +// domNode: { by: "descriptiveType" } +// } +// +// we would build the following tree of CountType subclasses: +// +// ByCoarseType +// objects: ByObjectClass +// each class: SimpleCount +// scripts: SimpleCount +// strings: SimpleCount +// other: ByUbinodeType +// each type: SimpleCount +// domNode: ByDomObjectClass +// each type: SimpleCount +// +// The interior nodes are all breakdown types that categorize nodes according to +// one characteristic or another; and the leaf nodes are all SimpleType. +// +// Each CountType has its own concrete C++ type that holds the counts it +// produces. SimpleCount::Count just holds totals. ByObjectClass::Count has a +// hash table whose keys are object class names and whose values are counts of +// some other type (in the example above, SimpleCount). +// +// To keep actual count nodes small, they have no vtable. Instead, each count +// points to its CountType, which knows how to carry out all the operations we +// need on a Count. A CountType can produce new count nodes; process nodes as we +// visit them; build a JS object reporting the results; and destruct count +// nodes. + +namespace JS { +namespace ubi { + +struct Census; + +class CountBase; + +struct CountDeleter { + JS_PUBLIC_API void operator()(CountBase*); +}; + +using CountBasePtr = js::UniquePtr<CountBase, CountDeleter>; + +// Abstract base class for CountType nodes. +struct CountType { + explicit CountType() = default; + virtual ~CountType() = default; + + // Destruct a count tree node that this type instance constructed. + virtual void destructCount(CountBase& count) = 0; + + // Return a fresh node for the count tree that categorizes nodes according + // to this type. Return a nullptr on OOM. + virtual CountBasePtr makeCount() = 0; + + // Trace |count| and all its children, for garbage collection. + virtual void traceCount(CountBase& count, JSTracer* trc) = 0; + + // Implement the 'count' method for counts returned by this CountType + // instance's 'newCount' method. + [[nodiscard]] virtual bool count(CountBase& count, + mozilla::MallocSizeOf mallocSizeOf, + const Node& node) = 0; + + // Implement the 'report' method for counts returned by this CountType + // instance's 'newCount' method. + [[nodiscard]] virtual bool report(JSContext* cx, CountBase& count, + MutableHandleValue report) = 0; +}; + +using CountTypePtr = js::UniquePtr<CountType>; + +// An abstract base class for count tree nodes. +class CountBase { + // In lieu of a vtable, each CountBase points to its type, which + // carries not only the implementations of the CountBase methods, but also + // additional parameters for the type's behavior, as specified in the + // breakdown argument passed to takeCensus. + CountType& type; + + protected: + ~CountBase() = default; + + public: + explicit CountBase(CountType& type) + : type(type), total_(0), smallestNodeIdCounted_(SIZE_MAX) {} + + // Categorize and count |node| as appropriate for this count's type. + [[nodiscard]] bool count(mozilla::MallocSizeOf mallocSizeOf, + const Node& node) { + total_++; + + auto id = node.identifier(); + if (id < smallestNodeIdCounted_) { + smallestNodeIdCounted_ = id; + } + +#ifdef DEBUG + size_t oldTotal = total_; +#endif + + bool ret = type.count(*this, mallocSizeOf, node); + + MOZ_ASSERT(total_ == oldTotal, + "CountType::count should not increment total_, CountBase::count " + "handles that"); + + return ret; + } + + // Construct a JavaScript object reporting the counts recorded in this + // count, and store it in |report|. Return true on success, or false on + // failure. + [[nodiscard]] bool report(JSContext* cx, MutableHandleValue report) { + return type.report(cx, *this, report); + } + + // Down-cast this CountBase to its true type, based on its 'type' member, + // and run its destructor. + void destruct() { return type.destructCount(*this); } + + // Trace this count for garbage collection. + void trace(JSTracer* trc) { type.traceCount(*this, trc); } + + size_t total_; + + // The smallest JS::ubi::Node::identifier() passed to this instance's + // count() method. This provides a stable way to sort sets. + Node::Id smallestNodeIdCounted_; +}; + +using RootedCount = JS::Rooted<CountBasePtr>; + +// Common data for a census traversal, shared across all CountType nodes. +struct Census { + JSContext* const cx; + // If the targetZones set is non-empty, then only consider nodes whose zone + // is an element of the set. If the targetZones set is empty, then nodes in + // all zones are considered. + JS::ZoneSet targetZones; + + explicit Census(JSContext* cx) : cx(cx) {} +}; + +// A BreadthFirst handler type that conducts a census, using a CountBase to +// categorize and count each node. +class CensusHandler { + Census& census; + JS::Handle<CountBasePtr> rootCount; + mozilla::MallocSizeOf mallocSizeOf; + + public: + CensusHandler(Census& census, JS::Handle<CountBasePtr> rootCount, + mozilla::MallocSizeOf mallocSizeOf) + : census(census), rootCount(rootCount), mallocSizeOf(mallocSizeOf) {} + + [[nodiscard]] bool report(JSContext* cx, MutableHandleValue report) { + return rootCount->report(cx, report); + } + + // This class needs to retain no per-node data. + class NodeData {}; + + [[nodiscard]] JS_PUBLIC_API bool operator()( + BreadthFirst<CensusHandler>& traversal, Node origin, const Edge& edge, + NodeData* referentData, bool first); +}; + +using CensusTraversal = BreadthFirst<CensusHandler>; + +// Examine the census options supplied by the API consumer, and (among other +// things) use that to build a CountType tree. +[[nodiscard]] JS_PUBLIC_API bool ParseCensusOptions(JSContext* cx, + Census& census, + HandleObject options, + CountTypePtr& outResult); + +// Parse the breakdown language (as described in +// js/src/doc/Debugger/Debugger.Memory.md) into a CountTypePtr. A null pointer +// is returned on error and is reported to the cx. +JS_PUBLIC_API CountTypePtr ParseBreakdown(JSContext* cx, + HandleValue breakdownValue); + +} // namespace ubi +} // namespace JS + +#endif // js_UbiNodeCensus_h diff --git a/js/public/UbiNodeDominatorTree.h b/js/public/UbiNodeDominatorTree.h new file mode 100644 index 0000000000..7b534aa11c --- /dev/null +++ b/js/public/UbiNodeDominatorTree.h @@ -0,0 +1,691 @@ +/* -*- 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 js_UbiNodeDominatorTree_h +#define js_UbiNodeDominatorTree_h + +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" + +#include <utility> + +#include "js/AllocPolicy.h" +#include "js/UbiNode.h" +#include "js/UbiNodePostOrder.h" +#include "js/Utility.h" +#include "js/Vector.h" + +namespace JS { +namespace ubi { + +/** + * In a directed graph with a root node `R`, a node `A` is said to "dominate" a + * node `B` iff every path from `R` to `B` contains `A`. A node `A` is said to + * be the "immediate dominator" of a node `B` iff it dominates `B`, is not `B` + * itself, and does not dominate any other nodes which also dominate `B` in + * turn. + * + * If we take every node from a graph `G` and create a new graph `T` with edges + * to each node from its immediate dominator, then `T` is a tree (each node has + * only one immediate dominator, or none if it is the root). This tree is called + * a "dominator tree". + * + * This class represents a dominator tree constructed from a `JS::ubi::Node` + * heap graph. The domination relationship and dominator trees are useful tools + * for analyzing heap graphs because they tell you: + * + * - Exactly what could be reclaimed by the GC if some node `A` became + * unreachable: those nodes which are dominated by `A`, + * + * - The "retained size" of a node in the heap graph, in contrast to its + * "shallow size". The "shallow size" is the space taken by a node itself, + * not counting anything it references. The "retained size" of a node is its + * shallow size plus the size of all the things that would be collected if + * the original node wasn't (directly or indirectly) referencing them. In + * other words, the retained size is the shallow size of a node plus the + * shallow sizes of every other node it dominates. For example, the root + * node in a binary tree might have a small shallow size that does not take + * up much space itself, but it dominates the rest of the binary tree and + * its retained size is therefore significant (assuming no external + * references into the tree). + * + * The simple, engineered algorithm presented in "A Simple, Fast Dominance + * Algorithm" by Cooper el al[0] is used to find dominators and construct the + * dominator tree. This algorithm runs in O(n^2) time, but is faster in practice + * than alternative algorithms with better theoretical running times, such as + * Lengauer-Tarjan which runs in O(e * log(n)). The big caveat to that statement + * is that Cooper et al found it is faster in practice *on control flow graphs* + * and I'm not convinced that this property also holds on *heap* graphs. That + * said, the implementation of this algorithm is *much* simpler than + * Lengauer-Tarjan and has been found to be fast enough at least for the time + * being. + * + * [0]: http://www.cs.rice.edu/~keith/EMBED/dom.pdf + */ +class JS_PUBLIC_API DominatorTree { + private: + // Types. + + using PredecessorSets = js::HashMap<Node, NodeSetPtr, js::DefaultHasher<Node>, + js::SystemAllocPolicy>; + using NodeToIndexMap = js::HashMap<Node, uint32_t, js::DefaultHasher<Node>, + js::SystemAllocPolicy>; + class DominatedSets; + + public: + class DominatedSetRange; + + /** + * A pointer to an immediately dominated node. + * + * Don't use this type directly; it is no safer than regular pointers. This + * is only for use indirectly with range-based for loops and + * `DominatedSetRange`. + * + * @see JS::ubi::DominatorTree::getDominatedSet + */ + class DominatedNodePtr { + friend class DominatedSetRange; + + const JS::ubi::Vector<Node>& postOrder; + const uint32_t* ptr; + + DominatedNodePtr(const JS::ubi::Vector<Node>& postOrder, + const uint32_t* ptr) + : postOrder(postOrder), ptr(ptr) {} + + public: + bool operator!=(const DominatedNodePtr& rhs) const { + return ptr != rhs.ptr; + } + void operator++() { ptr++; } + const Node& operator*() const { return postOrder[*ptr]; } + }; + + /** + * A range of immediately dominated `JS::ubi::Node`s for use with + * range-based for loops. + * + * @see JS::ubi::DominatorTree::getDominatedSet + */ + class DominatedSetRange { + friend class DominatedSets; + + const JS::ubi::Vector<Node>& postOrder; + const uint32_t* beginPtr; + const uint32_t* endPtr; + + DominatedSetRange(JS::ubi::Vector<Node>& postOrder, const uint32_t* begin, + const uint32_t* end) + : postOrder(postOrder), beginPtr(begin), endPtr(end) { + MOZ_ASSERT(begin <= end); + } + + public: + DominatedNodePtr begin() const { + MOZ_ASSERT(beginPtr <= endPtr); + return DominatedNodePtr(postOrder, beginPtr); + } + + DominatedNodePtr end() const { return DominatedNodePtr(postOrder, endPtr); } + + size_t length() const { + MOZ_ASSERT(beginPtr <= endPtr); + return endPtr - beginPtr; + } + + /** + * Safely skip ahead `n` dominators in the range, in O(1) time. + * + * Example usage: + * + * mozilla::Maybe<DominatedSetRange> range = + * myDominatorTree.getDominatedSet(myNode); + * if (range.isNothing()) { + * // Handle unknown nodes however you see fit... + * return false; + * } + * + * // Don't care about the first ten, for whatever reason. + * range->skip(10); + * for (const JS::ubi::Node& dominatedNode : *range) { + * // ... + * } + */ + void skip(size_t n) { + beginPtr += n; + if (beginPtr > endPtr) { + beginPtr = endPtr; + } + } + }; + + private: + /** + * The set of all dominated sets in a dominator tree. + * + * Internally stores the sets in a contiguous array, with a side table of + * indices into that contiguous array to denote the start index of each + * individual set. + */ + class DominatedSets { + JS::ubi::Vector<uint32_t> dominated; + JS::ubi::Vector<uint32_t> indices; + + DominatedSets(JS::ubi::Vector<uint32_t>&& dominated, + JS::ubi::Vector<uint32_t>&& indices) + : dominated(std::move(dominated)), indices(std::move(indices)) {} + + public: + // DominatedSets is not copy-able. + DominatedSets(const DominatedSets& rhs) = delete; + DominatedSets& operator=(const DominatedSets& rhs) = delete; + + // DominatedSets is move-able. + DominatedSets(DominatedSets&& rhs) + : dominated(std::move(rhs.dominated)), indices(std::move(rhs.indices)) { + MOZ_ASSERT(this != &rhs, "self-move not allowed"); + } + DominatedSets& operator=(DominatedSets&& rhs) { + this->~DominatedSets(); + new (this) DominatedSets(std::move(rhs)); + return *this; + } + + /** + * Create the DominatedSets given the mapping of a node index to its + * immediate dominator. Returns `Some` on success, `Nothing` on OOM + * failure. + */ + static mozilla::Maybe<DominatedSets> Create( + const JS::ubi::Vector<uint32_t>& doms) { + auto length = doms.length(); + MOZ_ASSERT(length < UINT32_MAX); + + // Create a vector `dominated` holding a flattened set of buckets of + // immediately dominated children nodes, with a lookup table + // `indices` mapping from each node to the beginning of its bucket. + // + // This has three phases: + // + // 1. Iterate over the full set of nodes and count up the size of + // each bucket. These bucket sizes are temporarily stored in the + // `indices` vector. + // + // 2. Convert the `indices` vector to store the cumulative sum of + // the sizes of all buckets before each index, resulting in a + // mapping from node index to one past the end of that node's + // bucket. + // + // 3. Iterate over the full set of nodes again, filling in bucket + // entries from the end of the bucket's range to its + // beginning. This decrements each index as a bucket entry is + // filled in. After having filled in all of a bucket's entries, + // the index points to the start of the bucket. + + JS::ubi::Vector<uint32_t> dominated; + JS::ubi::Vector<uint32_t> indices; + if (!dominated.growBy(length) || !indices.growBy(length)) { + return mozilla::Nothing(); + } + + // 1 + memset(indices.begin(), 0, length * sizeof(uint32_t)); + for (uint32_t i = 0; i < length; i++) { + indices[doms[i]]++; + } + + // 2 + uint32_t sumOfSizes = 0; + for (uint32_t i = 0; i < length; i++) { + sumOfSizes += indices[i]; + MOZ_ASSERT(sumOfSizes <= length); + indices[i] = sumOfSizes; + } + + // 3 + for (uint32_t i = 0; i < length; i++) { + auto idxOfDom = doms[i]; + indices[idxOfDom]--; + dominated[indices[idxOfDom]] = i; + } + +#ifdef DEBUG + // Assert that our buckets are non-overlapping and don't run off the + // end of the vector. + uint32_t lastIndex = 0; + for (uint32_t i = 0; i < length; i++) { + MOZ_ASSERT(indices[i] >= lastIndex); + MOZ_ASSERT(indices[i] < length); + lastIndex = indices[i]; + } +#endif + + return mozilla::Some( + DominatedSets(std::move(dominated), std::move(indices))); + } + + /** + * Get the set of nodes immediately dominated by the node at + * `postOrder[nodeIndex]`. + */ + DominatedSetRange dominatedSet(JS::ubi::Vector<Node>& postOrder, + uint32_t nodeIndex) const { + MOZ_ASSERT(postOrder.length() == indices.length()); + MOZ_ASSERT(nodeIndex < indices.length()); + auto end = nodeIndex == indices.length() - 1 + ? dominated.end() + : &dominated[indices[nodeIndex + 1]]; + return DominatedSetRange(postOrder, &dominated[indices[nodeIndex]], end); + } + }; + + private: + // Data members. + JS::ubi::Vector<Node> postOrder; + NodeToIndexMap nodeToPostOrderIndex; + JS::ubi::Vector<uint32_t> doms; + DominatedSets dominatedSets; + mozilla::Maybe<JS::ubi::Vector<JS::ubi::Node::Size>> retainedSizes; + + private: + // We use `UNDEFINED` as a sentinel value in the `doms` vector to signal + // that we haven't found any dominators for the node at the corresponding + // index in `postOrder` yet. + static const uint32_t UNDEFINED = UINT32_MAX; + + DominatorTree(JS::ubi::Vector<Node>&& postOrder, + NodeToIndexMap&& nodeToPostOrderIndex, + JS::ubi::Vector<uint32_t>&& doms, DominatedSets&& dominatedSets) + : postOrder(std::move(postOrder)), + nodeToPostOrderIndex(std::move(nodeToPostOrderIndex)), + doms(std::move(doms)), + dominatedSets(std::move(dominatedSets)), + retainedSizes(mozilla::Nothing()) {} + + static uint32_t intersect(JS::ubi::Vector<uint32_t>& doms, uint32_t finger1, + uint32_t finger2) { + while (finger1 != finger2) { + if (finger1 < finger2) { + finger1 = doms[finger1]; + } else if (finger2 < finger1) { + finger2 = doms[finger2]; + } + } + return finger1; + } + + // Do the post order traversal of the heap graph and populate our + // predecessor sets. + [[nodiscard]] static bool doTraversal(JSContext* cx, AutoCheckCannotGC& noGC, + const Node& root, + JS::ubi::Vector<Node>& postOrder, + PredecessorSets& predecessorSets) { + uint32_t nodeCount = 0; + auto onNode = [&](const Node& node) { + nodeCount++; + if (MOZ_UNLIKELY(nodeCount == UINT32_MAX)) { + return false; + } + return postOrder.append(node); + }; + + auto onEdge = [&](const Node& origin, const Edge& edge) { + auto p = predecessorSets.lookupForAdd(edge.referent); + if (!p) { + mozilla::UniquePtr<NodeSet, DeletePolicy<NodeSet>> set( + js_new<NodeSet>()); + if (!set || !predecessorSets.add(p, edge.referent, std::move(set))) { + return false; + } + } + MOZ_ASSERT(p && p->value()); + return p->value()->put(origin); + }; + + PostOrder traversal(cx, noGC); + return traversal.addStart(root) && traversal.traverse(onNode, onEdge); + } + + // Populates the given `map` with an entry for each node to its index in + // `postOrder`. + [[nodiscard]] static bool mapNodesToTheirIndices( + JS::ubi::Vector<Node>& postOrder, NodeToIndexMap& map) { + MOZ_ASSERT(map.empty()); + MOZ_ASSERT(postOrder.length() < UINT32_MAX); + uint32_t length = postOrder.length(); + if (!map.reserve(length)) { + return false; + } + for (uint32_t i = 0; i < length; i++) { + map.putNewInfallible(postOrder[i], i); + } + return true; + } + + // Convert the Node -> NodeSet predecessorSets to a index -> Vector<index> + // form. + [[nodiscard]] static bool convertPredecessorSetsToVectors( + const Node& root, JS::ubi::Vector<Node>& postOrder, + PredecessorSets& predecessorSets, NodeToIndexMap& nodeToPostOrderIndex, + JS::ubi::Vector<JS::ubi::Vector<uint32_t>>& predecessorVectors) { + MOZ_ASSERT(postOrder.length() < UINT32_MAX); + uint32_t length = postOrder.length(); + + MOZ_ASSERT(predecessorVectors.length() == 0); + if (!predecessorVectors.growBy(length)) { + return false; + } + + for (uint32_t i = 0; i < length - 1; i++) { + auto& node = postOrder[i]; + MOZ_ASSERT(node != root, + "Only the last node should be root, since this was a post " + "order traversal."); + + auto ptr = predecessorSets.lookup(node); + MOZ_ASSERT(ptr, + "Because this isn't the root, it had better have " + "predecessors, or else how " + "did we even find it."); + + auto& predecessors = ptr->value(); + if (!predecessorVectors[i].reserve(predecessors->count())) { + return false; + } + for (auto range = predecessors->all(); !range.empty(); range.popFront()) { + auto ptr = nodeToPostOrderIndex.lookup(range.front()); + MOZ_ASSERT(ptr); + predecessorVectors[i].infallibleAppend(ptr->value()); + } + } + predecessorSets.clearAndCompact(); + return true; + } + + // Initialize `doms` such that the immediate dominator of the `root` is the + // `root` itself and all others are `UNDEFINED`. + [[nodiscard]] static bool initializeDominators( + JS::ubi::Vector<uint32_t>& doms, uint32_t length) { + MOZ_ASSERT(doms.length() == 0); + if (!doms.growByUninitialized(length)) { + return false; + } + doms[length - 1] = length - 1; + for (uint32_t i = 0; i < length - 1; i++) { + doms[i] = UNDEFINED; + } + return true; + } + + void assertSanity() const { + MOZ_ASSERT(postOrder.length() == doms.length()); + MOZ_ASSERT(postOrder.length() == nodeToPostOrderIndex.count()); + MOZ_ASSERT_IF(retainedSizes.isSome(), + postOrder.length() == retainedSizes->length()); + } + + [[nodiscard]] bool computeRetainedSizes(mozilla::MallocSizeOf mallocSizeOf) { + MOZ_ASSERT(retainedSizes.isNothing()); + auto length = postOrder.length(); + + retainedSizes.emplace(); + if (!retainedSizes->growBy(length)) { + retainedSizes = mozilla::Nothing(); + return false; + } + + // Iterate in forward order so that we know all of a node's children in + // the dominator tree have already had their retained size + // computed. Then we can simply say that the retained size of a node is + // its shallow size (JS::ubi::Node::size) plus the retained sizes of its + // immediate children in the tree. + + for (uint32_t i = 0; i < length; i++) { + auto size = postOrder[i].size(mallocSizeOf); + + for (const auto& dominated : dominatedSets.dominatedSet(postOrder, i)) { + // The root node dominates itself, but shouldn't contribute to + // its own retained size. + if (dominated == postOrder[length - 1]) { + MOZ_ASSERT(i == length - 1); + continue; + } + + auto ptr = nodeToPostOrderIndex.lookup(dominated); + MOZ_ASSERT(ptr); + auto idxOfDominated = ptr->value(); + MOZ_ASSERT(idxOfDominated < i); + size += retainedSizes.ref()[idxOfDominated]; + } + + retainedSizes.ref()[i] = size; + } + + return true; + } + + public: + // DominatorTree is not copy-able. + DominatorTree(const DominatorTree&) = delete; + DominatorTree& operator=(const DominatorTree&) = delete; + + // DominatorTree is move-able. + DominatorTree(DominatorTree&& rhs) + : postOrder(std::move(rhs.postOrder)), + nodeToPostOrderIndex(std::move(rhs.nodeToPostOrderIndex)), + doms(std::move(rhs.doms)), + dominatedSets(std::move(rhs.dominatedSets)), + retainedSizes(std::move(rhs.retainedSizes)) { + MOZ_ASSERT(this != &rhs, "self-move is not allowed"); + } + DominatorTree& operator=(DominatorTree&& rhs) { + this->~DominatorTree(); + new (this) DominatorTree(std::move(rhs)); + return *this; + } + + /** + * Construct a `DominatorTree` of the heap graph visible from `root`. The + * `root` is also used as the root of the resulting dominator tree. + * + * The resulting `DominatorTree` instance must not outlive the + * `JS::ubi::Node` graph it was constructed from. + * + * - For `JS::ubi::Node` graphs backed by the live heap graph, this means + * that the `DominatorTree`'s lifetime _must_ be contained within the + * scope of the provided `AutoCheckCannotGC` reference because a GC will + * invalidate the nodes. + * + * - For `JS::ubi::Node` graphs backed by some other offline structure + * provided by the embedder, the resulting `DominatorTree`'s lifetime is + * bounded by that offline structure's lifetime. + * + * In practice, this means that within SpiderMonkey we must treat + * `DominatorTree` as if it were backed by the live heap graph and trust + * that embedders with knowledge of the graph's implementation will do the + * Right Thing. + * + * Returns `mozilla::Nothing()` on OOM failure. It is the caller's + * responsibility to handle and report the OOM. + */ + static mozilla::Maybe<DominatorTree> Create(JSContext* cx, + AutoCheckCannotGC& noGC, + const Node& root) { + JS::ubi::Vector<Node> postOrder; + PredecessorSets predecessorSets; + if (!doTraversal(cx, noGC, root, postOrder, predecessorSets)) { + return mozilla::Nothing(); + } + + MOZ_ASSERT(postOrder.length() < UINT32_MAX); + uint32_t length = postOrder.length(); + MOZ_ASSERT(postOrder[length - 1] == root); + + // From here on out we wish to avoid hash table lookups, and we use + // indices into `postOrder` instead of actual nodes wherever + // possible. This greatly improves the performance of this + // implementation, but we have to pay a little bit of upfront cost to + // convert our data structures to play along first. + + NodeToIndexMap nodeToPostOrderIndex(postOrder.length()); + if (!mapNodesToTheirIndices(postOrder, nodeToPostOrderIndex)) { + return mozilla::Nothing(); + } + + JS::ubi::Vector<JS::ubi::Vector<uint32_t>> predecessorVectors; + if (!convertPredecessorSetsToVectors(root, postOrder, predecessorSets, + nodeToPostOrderIndex, + predecessorVectors)) + return mozilla::Nothing(); + + JS::ubi::Vector<uint32_t> doms; + if (!initializeDominators(doms, length)) { + return mozilla::Nothing(); + } + + bool changed = true; + while (changed) { + changed = false; + + // Iterate over the non-root nodes in reverse post order. + for (uint32_t indexPlusOne = length - 1; indexPlusOne > 0; + indexPlusOne--) { + MOZ_ASSERT(postOrder[indexPlusOne - 1] != root); + + // Take the intersection of every predecessor's dominator set; + // that is the current best guess at the immediate dominator for + // this node. + + uint32_t newIDomIdx = UNDEFINED; + + auto& predecessors = predecessorVectors[indexPlusOne - 1]; + auto range = predecessors.all(); + for (; !range.empty(); range.popFront()) { + auto idx = range.front(); + if (doms[idx] != UNDEFINED) { + newIDomIdx = idx; + break; + } + } + + MOZ_ASSERT(newIDomIdx != UNDEFINED, + "Because the root is initialized to dominate itself and is " + "the first " + "node in every path, there must exist a predecessor to this " + "node that " + "also has a dominator."); + + for (; !range.empty(); range.popFront()) { + auto idx = range.front(); + if (doms[idx] != UNDEFINED) { + newIDomIdx = intersect(doms, newIDomIdx, idx); + } + } + + // If the immediate dominator changed, we will have to do + // another pass of the outer while loop to continue the forward + // dataflow. + if (newIDomIdx != doms[indexPlusOne - 1]) { + doms[indexPlusOne - 1] = newIDomIdx; + changed = true; + } + } + } + + auto maybeDominatedSets = DominatedSets::Create(doms); + if (maybeDominatedSets.isNothing()) { + return mozilla::Nothing(); + } + + return mozilla::Some( + DominatorTree(std::move(postOrder), std::move(nodeToPostOrderIndex), + std::move(doms), std::move(*maybeDominatedSets))); + } + + /** + * Get the root node for this dominator tree. + */ + const Node& root() const { return postOrder[postOrder.length() - 1]; } + + /** + * Return the immediate dominator of the given `node`. If `node` was not + * reachable from the `root` that this dominator tree was constructed from, + * then return the null `JS::ubi::Node`. + */ + Node getImmediateDominator(const Node& node) const { + assertSanity(); + auto ptr = nodeToPostOrderIndex.lookup(node); + if (!ptr) { + return Node(); + } + + auto idx = ptr->value(); + MOZ_ASSERT(idx < postOrder.length()); + return postOrder[doms[idx]]; + } + + /** + * Get the set of nodes immediately dominated by the given `node`. If `node` + * is not a member of this dominator tree, return `Nothing`. + * + * Example usage: + * + * mozilla::Maybe<DominatedSetRange> range = + * myDominatorTree.getDominatedSet(myNode); + * if (range.isNothing()) { + * // Handle unknown node however you see fit... + * return false; + * } + * + * for (const JS::ubi::Node& dominatedNode : *range) { + * // Do something with each immediately dominated node... + * } + */ + mozilla::Maybe<DominatedSetRange> getDominatedSet(const Node& node) { + assertSanity(); + auto ptr = nodeToPostOrderIndex.lookup(node); + if (!ptr) { + return mozilla::Nothing(); + } + + auto idx = ptr->value(); + MOZ_ASSERT(idx < postOrder.length()); + return mozilla::Some(dominatedSets.dominatedSet(postOrder, idx)); + } + + /** + * Get the retained size of the given `node`. The size is placed in + * `outSize`, or 0 if `node` is not a member of the dominator tree. Returns + * false on OOM failure, leaving `outSize` unchanged. + */ + [[nodiscard]] bool getRetainedSize(const Node& node, + mozilla::MallocSizeOf mallocSizeOf, + Node::Size& outSize) { + assertSanity(); + auto ptr = nodeToPostOrderIndex.lookup(node); + if (!ptr) { + outSize = 0; + return true; + } + + if (retainedSizes.isNothing() && !computeRetainedSizes(mallocSizeOf)) { + return false; + } + + auto idx = ptr->value(); + MOZ_ASSERT(idx < postOrder.length()); + outSize = retainedSizes.ref()[idx]; + return true; + } +}; + +} // namespace ubi +} // namespace JS + +#endif // js_UbiNodeDominatorTree_h diff --git a/js/public/UbiNodePostOrder.h b/js/public/UbiNodePostOrder.h new file mode 100644 index 0000000000..10d8835be9 --- /dev/null +++ b/js/public/UbiNodePostOrder.h @@ -0,0 +1,191 @@ +/* -*- 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 js_UbiNodePostOrder_h +#define js_UbiNodePostOrder_h + +#include "mozilla/Attributes.h" + +#include <utility> + +#include "js/UbiNode.h" +#include "js/Vector.h" + +namespace js { +class SystemAllocPolicy; +} + +namespace JS { +class AutoCheckCannotGC; + +namespace ubi { + +/** + * A post-order depth-first traversal of `ubi::Node` graphs. + * + * No GC may occur while an instance of `PostOrder` is live. + * + * The `NodeVisitor` type provided to `PostOrder::traverse` must have the + * following member: + * + * bool operator()(Node& node) + * + * The node visitor method. This method is called once for each `node` + * reachable from the start set in post-order. + * + * This visitor function should return true on success, or false if an error + * occurs. A false return value terminates the traversal immediately, and + * causes `PostOrder::traverse` to return false. + * + * The `EdgeVisitor` type provided to `PostOrder::traverse` must have the + * following member: + * + * bool operator()(Node& origin, Edge& edge) + * + * The edge visitor method. This method is called once for each outgoing + * `edge` from `origin` that is reachable from the start set. + * + * NB: UNLIKE NODES, THERE IS NO GUARANTEED ORDER IN WHICH EDGES AND THEIR + * ORIGINS ARE VISITED! + * + * This visitor function should return true on success, or false if an error + * occurs. A false return value terminates the traversal immediately, and + * causes `PostOrder::traverse` to return false. + */ +struct PostOrder { + private: + struct OriginAndEdges { + Node origin; + EdgeVector edges; + + OriginAndEdges(const Node& node, EdgeVector&& edges) + : origin(node), edges(std::move(edges)) {} + + OriginAndEdges(const OriginAndEdges& rhs) = delete; + OriginAndEdges& operator=(const OriginAndEdges& rhs) = delete; + + OriginAndEdges(OriginAndEdges&& rhs) + : origin(rhs.origin), edges(std::move(rhs.edges)) { + MOZ_ASSERT(&rhs != this, "self-move disallowed"); + } + + OriginAndEdges& operator=(OriginAndEdges&& rhs) { + this->~OriginAndEdges(); + new (this) OriginAndEdges(std::move(rhs)); + return *this; + } + }; + + using Stack = js::Vector<OriginAndEdges, 256, js::SystemAllocPolicy>; + using Set = js::HashSet<Node, js::DefaultHasher<Node>, js::SystemAllocPolicy>; + + JSContext* cx; + Set seen; + Stack stack; +#ifdef DEBUG + bool traversed; +#endif + + private: + [[nodiscard]] bool fillEdgesFromRange(EdgeVector& edges, + js::UniquePtr<EdgeRange>& range) { + MOZ_ASSERT(range); + for (; !range->empty(); range->popFront()) { + if (!edges.append(std::move(range->front()))) { + return false; + } + } + return true; + } + + [[nodiscard]] bool pushForTraversing(const Node& node) { + EdgeVector edges; + auto range = node.edges(cx, /* wantNames */ false); + return range && fillEdgesFromRange(edges, range) && + stack.append(OriginAndEdges(node, std::move(edges))); + } + + public: + // Construct a post-order traversal object. + // + // The traversal asserts that no GC happens in its runtime during its + // lifetime via the `AutoCheckCannotGC&` parameter. We do nothing with it, + // other than require it to exist with a lifetime that encloses our own. + PostOrder(JSContext* cx, AutoCheckCannotGC&) + : cx(cx), + seen(), + stack() +#ifdef DEBUG + , + traversed(false) +#endif + { + } + + // Add `node` as a starting point for the traversal. You may add + // as many starting points as you like. Returns false on OOM. + [[nodiscard]] bool addStart(const Node& node) { + if (!seen.put(node)) { + return false; + } + return pushForTraversing(node); + } + + // Traverse the graph in post-order, starting with the set of nodes passed + // to `addStart` and applying `onNode::operator()` for each node in the + // graph and `onEdge::operator()` for each edge in the graph, as described + // above. + // + // This should be called only once per instance of this class. + // + // Return false on OOM or error return from `onNode::operator()` or + // `onEdge::operator()`. + template <typename NodeVisitor, typename EdgeVisitor> + [[nodiscard]] bool traverse(NodeVisitor onNode, EdgeVisitor onEdge) { +#ifdef DEBUG + MOZ_ASSERT(!traversed, "Can only traverse() once!"); + traversed = true; +#endif + + while (!stack.empty()) { + auto& origin = stack.back().origin; + auto& edges = stack.back().edges; + + if (edges.empty()) { + if (!onNode(origin)) { + return false; + } + stack.popBack(); + continue; + } + + Edge edge = std::move(edges.back()); + edges.popBack(); + + if (!onEdge(origin, edge)) { + return false; + } + + auto ptr = seen.lookupForAdd(edge.referent); + // We've already seen this node, don't follow its edges. + if (ptr) { + continue; + } + + // Mark the referent as seen and follow its edges. + if (!seen.add(ptr, edge.referent) || !pushForTraversing(edge.referent)) { + return false; + } + } + + return true; + } +}; + +} // namespace ubi +} // namespace JS + +#endif // js_UbiNodePostOrder_h diff --git a/js/public/UbiNodeShortestPaths.h b/js/public/UbiNodeShortestPaths.h new file mode 100644 index 0000000000..35745ae9f9 --- /dev/null +++ b/js/public/UbiNodeShortestPaths.h @@ -0,0 +1,344 @@ +/* -*- 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 js_UbiNodeShortestPaths_h +#define js_UbiNodeShortestPaths_h + +#include "mozilla/Maybe.h" + +#include <utility> + +#include "js/AllocPolicy.h" +#include "js/GCAPI.h" +#include "js/UbiNode.h" +#include "js/UbiNodeBreadthFirst.h" +#include "js/UniquePtr.h" + +namespace JS { +namespace ubi { + +/** + * A back edge along a path in the heap graph. + */ +struct JS_PUBLIC_API BackEdge { + private: + Node predecessor_; + EdgeName name_; + + public: + using Ptr = js::UniquePtr<BackEdge>; + + BackEdge() : predecessor_(), name_(nullptr) {} + + [[nodiscard]] bool init(const Node& predecessor, Edge& edge) { + MOZ_ASSERT(!predecessor_); + MOZ_ASSERT(!name_); + + predecessor_ = predecessor; + name_ = std::move(edge.name); + return true; + } + + BackEdge(const BackEdge&) = delete; + BackEdge& operator=(const BackEdge&) = delete; + + BackEdge(BackEdge&& rhs) + : predecessor_(rhs.predecessor_), name_(std::move(rhs.name_)) { + MOZ_ASSERT(&rhs != this); + } + + BackEdge& operator=(BackEdge&& rhs) { + this->~BackEdge(); + new (this) BackEdge(std::move(rhs)); + return *this; + } + + Ptr clone() const; + + const EdgeName& name() const { return name_; } + EdgeName& name() { return name_; } + + const JS::ubi::Node& predecessor() const { return predecessor_; } +}; + +/** + * A path is a series of back edges from which we discovered a target node. + */ +using Path = JS::ubi::Vector<BackEdge*>; + +/** + * The `JS::ubi::ShortestPaths` type represents a collection of up to N shortest + * retaining paths for each of a target set of nodes, starting from the same + * root node. + */ +struct JS_PUBLIC_API ShortestPaths { + private: + // Types, type aliases, and data members. + + using BackEdgeVector = JS::ubi::Vector<BackEdge::Ptr>; + using NodeToBackEdgeVectorMap = + js::HashMap<Node, BackEdgeVector, js::DefaultHasher<Node>, + js::SystemAllocPolicy>; + + struct Handler; + using Traversal = BreadthFirst<Handler>; + + /** + * A `JS::ubi::BreadthFirst` traversal handler that records back edges for + * how we reached each node, allowing us to reconstruct the shortest + * retaining paths after the traversal. + */ + struct Handler { + using NodeData = BackEdge; + + ShortestPaths& shortestPaths; + size_t totalMaxPathsToRecord; + size_t totalPathsRecorded; + + explicit Handler(ShortestPaths& shortestPaths) + : shortestPaths(shortestPaths), + totalMaxPathsToRecord(shortestPaths.targets_.count() * + shortestPaths.maxNumPaths_), + totalPathsRecorded(0) {} + + bool operator()(Traversal& traversal, const JS::ubi::Node& origin, + JS::ubi::Edge& edge, BackEdge* back, bool first) { + MOZ_ASSERT(back); + MOZ_ASSERT(origin == shortestPaths.root_ || + traversal.visited.has(origin)); + MOZ_ASSERT(totalPathsRecorded < totalMaxPathsToRecord); + + if (first && !back->init(origin, edge)) { + return false; + } + + if (!shortestPaths.targets_.has(edge.referent)) { + return true; + } + + // If `first` is true, then we moved the edge's name into `back` in + // the above call to `init`. So clone that back edge to get the + // correct edge name. If `first` is not true, then our edge name is + // still in `edge`. This accounts for the asymmetry between + // `back->clone()` in the first branch, and the `init` call in the + // second branch. + + if (first) { + BackEdgeVector paths; + if (!paths.reserve(shortestPaths.maxNumPaths_)) { + return false; + } + auto cloned = back->clone(); + if (!cloned) { + return false; + } + paths.infallibleAppend(std::move(cloned)); + if (!shortestPaths.paths_.putNew(edge.referent, std::move(paths))) { + return false; + } + totalPathsRecorded++; + } else { + auto ptr = shortestPaths.paths_.lookup(edge.referent); + MOZ_ASSERT(ptr, + "This isn't the first time we have seen the target node " + "`edge.referent`. " + "We should have inserted it into shortestPaths.paths_ the " + "first time we " + "saw it."); + + if (ptr->value().length() < shortestPaths.maxNumPaths_) { + auto thisBackEdge = js::MakeUnique<BackEdge>(); + if (!thisBackEdge || !thisBackEdge->init(origin, edge)) { + return false; + } + ptr->value().infallibleAppend(std::move(thisBackEdge)); + totalPathsRecorded++; + } + } + + MOZ_ASSERT(totalPathsRecorded <= totalMaxPathsToRecord); + if (totalPathsRecorded == totalMaxPathsToRecord) { + traversal.stop(); + } + + return true; + } + }; + + // The maximum number of paths to record for each node. + uint32_t maxNumPaths_; + + // The root node we are starting the search from. + Node root_; + + // The set of nodes we are searching for paths to. + NodeSet targets_; + + // The resulting paths. + NodeToBackEdgeVectorMap paths_; + + // Need to keep alive the traversal's back edges so we can walk them later + // when the traversal is over when recreating the shortest paths. + Traversal::NodeMap backEdges_; + + private: + // Private methods. + + ShortestPaths(uint32_t maxNumPaths, const Node& root, NodeSet&& targets) + : maxNumPaths_(maxNumPaths), + root_(root), + targets_(std::move(targets)), + paths_(targets_.count()), + backEdges_() { + MOZ_ASSERT(maxNumPaths_ > 0); + MOZ_ASSERT(root_); + } + + public: + // Public methods. + + ShortestPaths(ShortestPaths&& rhs) + : maxNumPaths_(rhs.maxNumPaths_), + root_(rhs.root_), + targets_(std::move(rhs.targets_)), + paths_(std::move(rhs.paths_)), + backEdges_(std::move(rhs.backEdges_)) { + MOZ_ASSERT(this != &rhs, "self-move is not allowed"); + } + + ShortestPaths& operator=(ShortestPaths&& rhs) { + this->~ShortestPaths(); + new (this) ShortestPaths(std::move(rhs)); + return *this; + } + + ShortestPaths(const ShortestPaths&) = delete; + ShortestPaths& operator=(const ShortestPaths&) = delete; + + /** + * Construct a new `JS::ubi::ShortestPaths`, finding up to `maxNumPaths` + * shortest retaining paths for each target node in `targets` starting from + * `root`. + * + * The resulting `ShortestPaths` instance must not outlive the + * `JS::ubi::Node` graph it was constructed from. + * + * - For `JS::ubi::Node` graphs backed by the live heap graph, this means + * that the `ShortestPaths`'s lifetime _must_ be contained within the + * scope of the provided `AutoCheckCannotGC` reference because a GC will + * invalidate the nodes. + * + * - For `JS::ubi::Node` graphs backed by some other offline structure + * provided by the embedder, the resulting `ShortestPaths`'s lifetime is + * bounded by that offline structure's lifetime. + * + * Returns `mozilla::Nothing()` on OOM failure. It is the caller's + * responsibility to handle and report the OOM. + */ + static mozilla::Maybe<ShortestPaths> Create(JSContext* cx, + AutoCheckCannotGC& noGC, + uint32_t maxNumPaths, + const Node& root, + NodeSet&& targets) { + MOZ_ASSERT(targets.count() > 0); + MOZ_ASSERT(maxNumPaths > 0); + + ShortestPaths paths(maxNumPaths, root, std::move(targets)); + + Handler handler(paths); + Traversal traversal(cx, handler, noGC); + traversal.wantNames = true; + if (!traversal.addStart(root) || !traversal.traverse()) { + return mozilla::Nothing(); + } + + // Take ownership of the back edges we created while traversing the + // graph so that we can follow them from `paths_` and don't + // use-after-free. + paths.backEdges_ = std::move(traversal.visited); + + return mozilla::Some(std::move(paths)); + } + + /** + * Get an iterator over each target node we searched for retaining paths + * for. The returned iterator must not outlive the `ShortestPaths` + * instance. + */ + NodeSet::Iterator targetIter() const { return targets_.iter(); } + + /** + * Invoke the provided functor/lambda/callable once for each retaining path + * discovered for `target`. The `func` is passed a single `JS::ubi::Path&` + * argument, which contains each edge along the path ordered starting from + * the root and ending at the target, and must not outlive the scope of the + * call. + * + * Note that it is possible that we did not find any paths from the root to + * the given target, in which case `func` will not be invoked. + */ + template <class Func> + [[nodiscard]] bool forEachPath(const Node& target, Func func) { + MOZ_ASSERT(targets_.has(target)); + + auto ptr = paths_.lookup(target); + + // We didn't find any paths to this target, so nothing to do here. + if (!ptr) { + return true; + } + + MOZ_ASSERT(ptr->value().length() <= maxNumPaths_); + + Path path; + for (const auto& backEdge : ptr->value()) { + path.clear(); + + if (!path.append(backEdge.get())) { + return false; + } + + Node here = backEdge->predecessor(); + MOZ_ASSERT(here); + + while (here != root_) { + auto p = backEdges_.lookup(here); + MOZ_ASSERT(p); + if (!path.append(&p->value())) { + return false; + } + here = p->value().predecessor(); + MOZ_ASSERT(here); + } + + path.reverse(); + + if (!func(path)) { + return false; + } + } + + return true; + } +}; + +#ifdef DEBUG +// A helper function to dump the first `maxNumPaths` shortest retaining paths to +// `node` from the GC roots. Useful when GC things you expect to have been +// reclaimed by the collector haven't been! +// +// Usage: +// +// JSObject* foo = ...; +// JS::ubi::dumpPaths(rt, JS::ubi::Node(foo)); +JS_PUBLIC_API void dumpPaths(JSRuntime* rt, Node node, + uint32_t maxNumPaths = 10); +#endif + +} // namespace ubi +} // namespace JS + +#endif // js_UbiNodeShortestPaths_h diff --git a/js/public/UbiNodeUtils.h b/js/public/UbiNodeUtils.h new file mode 100644 index 0000000000..bd48086af8 --- /dev/null +++ b/js/public/UbiNodeUtils.h @@ -0,0 +1,51 @@ +/* -*- 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 js_UbiNodeUtils_h +#define js_UbiNodeUtils_h + +#include "jspubtd.h" + +#include "js/UbiNode.h" +#include "js/UniquePtr.h" + +using JS::ubi::Edge; +using JS::ubi::EdgeRange; +using JS::ubi::EdgeVector; + +namespace JS { +namespace ubi { +// An EdgeRange concrete class that simply holds a vector of Edges, +// populated by the addTracerEdges method. +class SimpleEdgeRange : public EdgeRange { + EdgeVector edges; + size_t i; + + protected: + void settle() { front_ = i < edges.length() ? &edges[i] : nullptr; } + + public: + explicit SimpleEdgeRange() : edges(), i(0) {} + + bool addTracerEdges(JSRuntime* rt, void* thing, JS::TraceKind kind, + bool wantNames); + + bool addEdge(Edge edge) { + if (!edges.append(std::move(edge))) return false; + settle(); + return true; + } + + void popFront() override { + i++; + settle(); + } +}; + +} // namespace ubi +} // namespace JS + +#endif // js_UbiNodeUtils_h diff --git a/js/public/UniquePtr.h b/js/public/UniquePtr.h new file mode 100644 index 0000000000..d6bbb9ae61 --- /dev/null +++ b/js/public/UniquePtr.h @@ -0,0 +1,56 @@ +/* -*- 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 js_UniquePtr_h +#define js_UniquePtr_h + +#include "mozilla/UniquePtr.h" + +#include "js/Utility.h" + +namespace js { + +// Replacement for mozilla::UniquePtr that defaults to JS::DeletePolicy. +template <typename T, typename D = JS::DeletePolicy<T>> +using UniquePtr = mozilla::UniquePtr<T, D>; + +namespace detail { + +template <typename T> +struct UniqueSelector { + typedef UniquePtr<T> SingleObject; +}; + +template <typename T> +struct UniqueSelector<T[]> { + typedef UniquePtr<T[]> UnknownBound; +}; + +template <typename T, decltype(sizeof(int)) N> +struct UniqueSelector<T[N]> { + typedef UniquePtr<T[N]> KnownBound; +}; + +} // namespace detail + +// Replacement for mozilla::MakeUnique that correctly calls js_new and produces +// a js::UniquePtr. +template <typename T, typename... Args> +typename detail::UniqueSelector<T>::SingleObject MakeUnique(Args&&... aArgs) { + return UniquePtr<T>(js_new<T>(std::forward<Args>(aArgs)...)); +} + +template <typename T> +typename detail::UniqueSelector<T>::UnknownBound MakeUnique( + decltype(sizeof(int)) aN) = delete; + +template <typename T, typename... Args> +typename detail::UniqueSelector<T>::KnownBound MakeUnique(Args&&... aArgs) = + delete; + +} // namespace js + +#endif /* js_UniquePtr_h */ diff --git a/js/public/Utility.h b/js/public/Utility.h new file mode 100644 index 0000000000..8927d65d48 --- /dev/null +++ b/js/public/Utility.h @@ -0,0 +1,680 @@ +/* -*- 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 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/TemplateLib.h" +#include "mozilla/UniquePtr.h" + +#include <stdlib.h> +#include <string.h> +#include <type_traits> +#include <utility> + +#include "jstypes.h" +#include "mozmemory.h" +#include "js/TypeDecls.h" + +/* The public JS engine namespace. */ +namespace JS {} + +/* The mozilla-shared reusable template/utility namespace. */ +namespace mozilla {} + +/* The private JS engine namespace. */ +namespace js {} + +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_COMPILE_TIER1, // 2 + THREAD_TYPE_WASM_COMPILE_TIER2, // 3 + THREAD_TYPE_ION, // 4 + THREAD_TYPE_PARSE, // 5 + THREAD_TYPE_COMPRESS, // 6 + THREAD_TYPE_GCPARALLEL, // 7 + THREAD_TYPE_PROMISE_TASK, // 8 + THREAD_TYPE_ION_FREE, // 9 + THREAD_TYPE_WASM_GENERATOR_TIER2, // 10 + THREAD_TYPE_WORKER, // 11 + THREAD_TYPE_DELAZIFY, // 12 + THREAD_TYPE_DELAZIFY_FREE, // 13 + 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 util/Utility.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_GENERATOR_TIER2; + +extern bool InitThreadType(void); +extern void SetThreadType(ThreadType); +extern JS_PUBLIC_API uint32_t GetThreadType(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. + */ +class FailureSimulator { + public: + enum class Kind : uint8_t { Nothing, OOM, StackOOM, Interrupt }; + + private: + Kind kind_ = Kind::Nothing; + uint32_t targetThread_ = 0; + uint64_t maxChecks_ = UINT64_MAX; + uint64_t counter_ = 0; + bool failAlways_ = true; + bool inUnsafeRegion_ = false; + + public: + uint64_t maxChecks() const { return maxChecks_; } + uint64_t counter() const { return counter_; } + void setInUnsafeRegion(bool b) { + MOZ_ASSERT(inUnsafeRegion_ != b); + inUnsafeRegion_ = b; + } + uint32_t targetThread() const { return targetThread_; } + bool isThreadSimulatingAny() const { + return targetThread_ && targetThread_ == js::oom::GetThreadType() && + !inUnsafeRegion_; + } + bool isThreadSimulating(Kind kind) const { + return kind_ == kind && isThreadSimulatingAny(); + } + bool isSimulatedFailure(Kind kind) const { + if (!isThreadSimulating(kind)) { + return false; + } + return counter_ == maxChecks_ || (counter_ > maxChecks_ && failAlways_); + } + bool hadFailure(Kind kind) const { + return kind_ == kind && counter_ >= maxChecks_; + } + bool shouldFail(Kind kind) { + if (!isThreadSimulating(kind)) { + return false; + } + counter_++; + if (isSimulatedFailure(kind)) { + JS_OOM_CALL_BP_FUNC(); + return true; + } + return false; + } + + void simulateFailureAfter(Kind kind, uint64_t checks, uint32_t thread, + bool always); + void reset(); +}; +extern JS_PUBLIC_DATA FailureSimulator simulator; + +inline bool IsSimulatedOOMAllocation() { + return simulator.isSimulatedFailure(FailureSimulator::Kind::OOM); +} + +inline bool ShouldFailWithOOM() { + return simulator.shouldFail(FailureSimulator::Kind::OOM); +} + +inline bool HadSimulatedOOM() { + return simulator.hadFailure(FailureSimulator::Kind::OOM); +} + +/* + * Out of stack space testing support, similar to OOM testing functions. + */ + +inline bool IsSimulatedStackOOMCheck() { + return simulator.isSimulatedFailure(FailureSimulator::Kind::StackOOM); +} + +inline bool ShouldFailWithStackOOM() { + return simulator.shouldFail(FailureSimulator::Kind::StackOOM); +} + +inline bool HadSimulatedStackOOM() { + return simulator.hadFailure(FailureSimulator::Kind::StackOOM); +} + +/* + * Interrupt testing support, similar to OOM testing functions. + */ + +inline bool IsSimulatedInterruptCheck() { + return simulator.isSimulatedFailure(FailureSimulator::Kind::Interrupt); +} + +inline bool ShouldFailWithInterrupt() { + return simulator.shouldFail(FailureSimulator::Kind::Interrupt); +} + +inline bool HadSimulatedInterrupt() { + return simulator.hadFailure(FailureSimulator::Kind::Interrupt); +} + +} /* 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_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_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 */ + +# ifdef FUZZING +namespace js { +namespace oom { +extern JS_PUBLIC_DATA size_t largeAllocLimit; +extern void InitLargeAllocLimit(); +} /* namespace oom */ +} /* namespace js */ + +# define JS_CHECK_LARGE_ALLOC(x) \ + do { \ + if (js::oom::largeAllocLimit && x > js::oom::largeAllocLimit) { \ + if (getenv("MOZ_FUZZ_CRASH_ON_LARGE_ALLOC")) { \ + MOZ_CRASH("Large allocation"); \ + } else { \ + return nullptr; \ + } \ + } \ + } while (0) +# else +# define JS_CHECK_LARGE_ALLOC(x) \ + do { \ + } while (0) +# endif + +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 mozilla::Atomic<AnnotateOOMAllocationSizeCallback, mozilla::Relaxed> + annotateOOMSizeCallback; + static void setAnnotateOOMAllocationSizeCallback( + AnnotateOOMAllocationSizeCallback callback) { + annotateOOMSizeCallback = callback; + } + +# if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) + AutoEnterOOMUnsafeRegion() + : oomEnabled_(oom::simulator.isThreadSimulatingAny()) { + if (oomEnabled_) { + MOZ_ALWAYS_TRUE(owner_.compareExchange(nullptr, this)); + oom::simulator.setInUnsafeRegion(true); + } + } + + ~AutoEnterOOMUnsafeRegion() { + if (oomEnabled_) { + oom::simulator.setInUnsafeRegion(false); + MOZ_ALWAYS_TRUE(owner_.compareExchange(this, nullptr)); + } + } + + private: + // Used to catch concurrent use from other threads. + static mozilla::Atomic<AutoEnterOOMUnsafeRegion*> owner_; + + bool oomEnabled_; +# endif +}; + +} /* namespace js */ + +// Malloc allocation. + +namespace js { + +extern JS_PUBLIC_DATA arena_id_t MallocArena; +extern JS_PUBLIC_DATA arena_id_t ArrayBufferContentsArena; +extern JS_PUBLIC_DATA arena_id_t StringBufferArena; + +extern void InitMallocAllocator(); +extern void ShutDownMallocAllocator(); + +// This is a no-op if built without MOZ_MEMORY and MOZ_DEBUG. +extern void AssertJSStringBufferInCorrectArena(const void* ptr); + +} /* namespace js */ + +static inline void* js_arena_malloc(arena_id_t arena, size_t bytes) { + JS_OOM_POSSIBLY_FAIL(); + JS_CHECK_LARGE_ALLOC(bytes); + 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 bytes) { + JS_OOM_POSSIBLY_FAIL(); + JS_CHECK_LARGE_ALLOC(bytes); + return moz_arena_calloc(arena, bytes, 1); +} + +static inline void* js_arena_calloc(arena_id_t arena, size_t nmemb, + size_t size) { + JS_OOM_POSSIBLY_FAIL(); + JS_CHECK_LARGE_ALLOC(nmemb * size); + return moz_arena_calloc(arena, nmemb, size); +} + +static inline void* js_calloc(size_t bytes) { + return js_arena_calloc(js::MallocArena, bytes); +} + +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(); + JS_CHECK_LARGE_ALLOC(bytes); + 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) { + // Bug 1784164: 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(). All other memory should go through a different allocator and + // deallocator. + free(p); +} +#endif /* JS_USE_CUSTOM_ALLOCATOR */ + +#include <new> + +/* + * [SMDOC] 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 JS::GCContext provided to the finalizer: + * + * JS::GCContext::{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* MOZ_HEAP_ALLOCATOR NEWNAME(Args&&... args) { \ + static_assert( \ + alignof(T) <= alignof(max_align_t), \ + "over-aligned type is not supported by JS_DECLARE_NEW_METHODS"); \ + void* memory = ALLOCATOR(sizeof(T)); \ + return MOZ_LIKELY(memory) ? new (memory) T(std::forward<Args>(args)...) \ + : nullptr; \ + } + +/* + * Given a class which should provide a 'new' method that takes an arena as + * its first argument, add JS_DECLARE_NEW_ARENA_METHODS + * (see js::MallocProvider for an example). + * + * Note: Do not add a ; at the end of a use of JS_DECLARE_NEW_ARENA_METHODS, + * or the build will break. + */ +#define JS_DECLARE_NEW_ARENA_METHODS(NEWNAME, ALLOCATOR, QUALIFIERS) \ + template <class T, typename... Args> \ + QUALIFIERS T* MOZ_HEAP_ALLOCATOR NEWNAME(arena_id_t arena, Args&&... args) { \ + static_assert( \ + alignof(T) <= alignof(max_align_t), \ + "over-aligned type is not supported by JS_DECLARE_NEW_ARENA_METHODS"); \ + void* memory = ALLOCATOR(arena, 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>> MOZ_HEAP_ALLOCATOR \ + MAKENAME(Args&&... args) { \ + 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) +JS_DECLARE_NEW_ARENA_METHODS(js_arena_new, js_arena_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> +[[nodiscard]] 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> +[[nodiscard]] 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_arena_realloc(arena_id_t arena, 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_arena_realloc(arena, prior, bytes)); +} + +template <class T> +static MOZ_ALWAYS_INLINE T* js_pod_realloc(T* prior, size_t oldSize, + size_t newSize) { + return js_pod_arena_realloc<T>(js::MallocArena, prior, oldSize, newSize); +} + +namespace JS { + +template <typename T> +struct DeletePolicy { + constexpr DeletePolicy() = default; + + template <typename U> + MOZ_IMPLICIT DeletePolicy( + DeletePolicy<U> other, + std::enable_if_t<std::is_convertible_v<U*, T*>, int> 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)); } +}; + +using UniqueChars = mozilla::UniquePtr<char[], JS::FreePolicy>; +using UniqueTwoByteChars = mozilla::UniquePtr<char16_t[], JS::FreePolicy>; +using UniqueLatin1Chars = mozilla::UniquePtr<JS::Latin1Char[], JS::FreePolicy>; +using UniqueWideChars = mozilla::UniquePtr<wchar_t[], JS::FreePolicy>; + +} // 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 */ diff --git a/js/public/Value.h b/js/public/Value.h new file mode 100644 index 0000000000..de8db0fed8 --- /dev/null +++ b/js/public/Value.h @@ -0,0 +1,1528 @@ +/* -*- 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/. */ + +/* JS::Value implementation. */ + +#ifndef js_Value_h +#define js_Value_h + +#include "mozilla/Attributes.h" +#include "mozilla/Casting.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" + +#include <limits> /* for std::numeric_limits */ +#include <type_traits> + +#include "jstypes.h" + +#include "js/HeapAPI.h" +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" + +namespace JS { +class JS_PUBLIC_API Value; +} + +// [SMDOC] JS::Value Boxing Formats +// +// JS::Value is a 64-bit value, on all architectures. It is conceptually a +// discriminated union of all the types of values that can be represented in SM: +// - Object Pointers +// - 64 bit IEEE 754 floats +// - 32-bit integer values +// - and quite a few more (see JSValueType) +// +// The ECMAScript standard specifies that ECMAScript numbers are IEEE 64-bit +// floating-point values. A JS::Value can represent any JavaScript number +// value directly, without referring to additional storage, or represent an +// object, string, or other ECMAScript value, and remember which type it is. +// +// This may seem surprising: how can a 64-bit type hold all the 64-bit IEEE +// values, and still distinguish them from objects, strings, and so on, +// which have 64-bit addresses ? +// +// This is possible for two reasons: +// +// - First, ECMAScript implementations aren't required to distinguish all +// the values the IEEE 64-bit format can represent. +// +// The IEEE 754 format for floating point numbers specifies that every +// floating-point value whose 11-bit exponent field is all ones, and whose +// 52-bit fraction field is non-zero, has the value NaN. EMCAScript requires +// only one NaN value. This means we can use one IEEE NaN to represent +// ECMAScript's NaN, and use all the other 2^52-2 NaN bitstrings to +// represent the other ECMAScript values. +// +// - Second, on the 64 bit architectures we suppport, only the +// lower 48 bits of an address are currently significant. The upper sixteen +// bits are required to be the sign-extension of bit 48. Furthermore, user +// code always runs in "positive addresses": those in which bit 48 is zero. So +// we only actually need 47 bits to store all possible object or string +// addresses, even on 64-bit platforms. +// +// Our memory initialization system ensures that all pointers we will store in +// objects use only 47 bits. See js::gc::MapAlignedPagesRandom. +// +// The introduction of 5-level page tables, supporting 57-bit virtual +// addresses, is a potential complication. For now, large addresses are +// opt-in, and we simply don't use them. +// +// With a 52-bit fraction field, and 47 bits needed for the 'payload', we +// have up to five bits left to store a 'tag' value, to indicate which +// branch of our discriminated union is live. (In practice, one of those +// bits is used up to simplify NaN representation; see micro-optimization 5 +// below.) +// +// Thus, we define JS::Value representations in terms of the IEEE 64-bit +// floating-point format: +// +// - Any bitstring that IEEE calls a number or an infinity represents that +// ECMAScript number. +// +// - Any bitstring that IEEE calls a NaN represents either an ECMAScript NaN +// or a non-number ECMAScript value, as determined by a tag field stored +// towards the most significant end of the fraction field (exactly where +// depends on the address size). If the tag field indicates that this +// JS::Value is an object, the fraction field's least significant end +// holds the address of a JSObject; if a string, the address of a +// JSString; and so on. +// +// To enforce this invariant, anywhere that may provide a numerical value +// which may have a non-canonical NaN value (NaN, but not the one we've chosen +// for ECMAScript) we must convert that to the canonical NaN. See +// JS::CanonicalizeNaN. +// +// We have two boxing modes defined: NUNBOX32 and PUNBOX64.The first is +// "NaN unboxed boxing" (or Nunboxing), as non-Number payload are stored +// unaltered in the lower bits. The second is "Packed NaN boxing" (or +// punboxing), which is 'logically like nunboxing, but with all the unused bits +// sucked out' [1], as we rely on unused bits of the payload to pack the +// payload in the lower bits using Nunboxing. +// +// - In NUNBOX32 the tag is stored in the least-significant bits of the high +// word of the NaN. Since it's used on 32-bit systems, this has the nice +// property that boxed values are simply stored in the low-word of the 8-byte +// NaN. +// +// - In PUNBOX64, since we need to store more pointer bits (47, see above), the +// tag is stored in the 5 most significant bits of the fraction adjacent to +// the exponent. +// +// Tag values are carefully ordered to support a set of micro-optimizations. In +// particular: +// +// 1. Object is the highest tag, to simplify isPrimitive checks. (See +// ValueUpperExclPrimitiveTag) +// 2. Numbers (Double and Int32) are the lowest tags, to simplify isNumber +// checks. (See ValueUpperInclNumberTag) +// 3. Non-GC tags are ordered before GC-tags, to simplify isGCThing checks. (See +// ValueLowerInclGCThingTag) +// 4. The tags for Object and Null differ by a single flipped bit, to simplify +// toObjectOrNull. (See ValueObjectOrNullBit) +// 5. In PUNBOX64, the most significant bit of every non-Double tag is always +// set. This is to simplify isDouble checks. Note that the highest bitstring +// that corresponds to a non-NaN double is -Infinity: +// 0xfff0_0000_0000_0000 +// But the canonical hardware NaN (produced by, for example, 0/0) is: +// 0x?ff8_0000_0000_0000 +// on all platforms with JIT support*. (The most significant bit is the sign +// bit; it is 1 on x86, but 0 on ARM.) The most significant bit of the +// fraction field is set, which corresponds to the most significant of the 5 +// tag bits. Because we only use tags that have the high bit set, any Value +// represented by a bitstring less than or equal to 0xfff8_..._0000 is a +// Double. (If we wanted to use all five bits, we could define 0x10 as +// JSVAL_TYPE_NAN, and mask off the most significant bit of the tag for +// IsDouble checks. This is not yet necessary, because we still have room +// left to allocate new tags.) +// +// * But see JS_NONCANONICAL_HARDWARE_NAN below. +// +// [1]: +// https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations#969f63bbe4eb912778c9da85feb0f5763e7a7862 + +/* JS::Value can store a full int32_t. */ +#define JSVAL_INT_BITS 32 +#define JSVAL_INT_MIN ((int32_t)0x80000000) +#define JSVAL_INT_MAX ((int32_t)0x7fffffff) + +#if defined(JS_NUNBOX32) +# define JSVAL_TAG_SHIFT 32 +#elif defined(JS_PUNBOX64) +# define JSVAL_TAG_SHIFT 47 +#endif + +// Use enums so that printing a JS::Value in the debugger shows nice +// symbolic type tags. + +enum JSValueType : uint8_t { + JSVAL_TYPE_DOUBLE = 0x00, + JSVAL_TYPE_INT32 = 0x01, + JSVAL_TYPE_BOOLEAN = 0x02, + JSVAL_TYPE_UNDEFINED = 0x03, + JSVAL_TYPE_NULL = 0x04, + JSVAL_TYPE_MAGIC = 0x05, + JSVAL_TYPE_STRING = 0x06, + JSVAL_TYPE_SYMBOL = 0x07, + JSVAL_TYPE_PRIVATE_GCTHING = 0x08, + JSVAL_TYPE_BIGINT = 0x09, +#ifdef ENABLE_RECORD_TUPLE + JSVAL_TYPE_EXTENDED_PRIMITIVE = 0x0b, +#endif + JSVAL_TYPE_OBJECT = 0x0c, + + // This type never appears in a Value; it's only an out-of-band value. + JSVAL_TYPE_UNKNOWN = 0x20 +}; + +namespace JS { +enum class ValueType : uint8_t { + Double = JSVAL_TYPE_DOUBLE, + Int32 = JSVAL_TYPE_INT32, + Boolean = JSVAL_TYPE_BOOLEAN, + Undefined = JSVAL_TYPE_UNDEFINED, + Null = JSVAL_TYPE_NULL, + Magic = JSVAL_TYPE_MAGIC, + String = JSVAL_TYPE_STRING, + Symbol = JSVAL_TYPE_SYMBOL, + PrivateGCThing = JSVAL_TYPE_PRIVATE_GCTHING, + BigInt = JSVAL_TYPE_BIGINT, +#ifdef ENABLE_RECORD_TUPLE + ExtendedPrimitive = JSVAL_TYPE_EXTENDED_PRIMITIVE, +#endif + Object = JSVAL_TYPE_OBJECT, +}; +} // namespace JS + +static_assert(sizeof(JSValueType) == 1, + "compiler typed enum support is apparently buggy"); + +#if defined(JS_NUNBOX32) + +enum JSValueTag : uint32_t { + JSVAL_TAG_CLEAR = 0xFFFFFF80, + JSVAL_TAG_INT32 = JSVAL_TAG_CLEAR | JSVAL_TYPE_INT32, + JSVAL_TAG_UNDEFINED = JSVAL_TAG_CLEAR | JSVAL_TYPE_UNDEFINED, + JSVAL_TAG_NULL = JSVAL_TAG_CLEAR | JSVAL_TYPE_NULL, + JSVAL_TAG_BOOLEAN = JSVAL_TAG_CLEAR | JSVAL_TYPE_BOOLEAN, + JSVAL_TAG_MAGIC = JSVAL_TAG_CLEAR | JSVAL_TYPE_MAGIC, + JSVAL_TAG_STRING = JSVAL_TAG_CLEAR | JSVAL_TYPE_STRING, + JSVAL_TAG_SYMBOL = JSVAL_TAG_CLEAR | JSVAL_TYPE_SYMBOL, + JSVAL_TAG_PRIVATE_GCTHING = JSVAL_TAG_CLEAR | JSVAL_TYPE_PRIVATE_GCTHING, + JSVAL_TAG_BIGINT = JSVAL_TAG_CLEAR | JSVAL_TYPE_BIGINT, +# ifdef ENABLE_RECORD_TUPLE + JSVAL_TAG_EXTENDED_PRIMITIVE = + JSVAL_TAG_CLEAR | JSVAL_TYPE_EXTENDED_PRIMITIVE, +# endif + JSVAL_TAG_OBJECT = JSVAL_TAG_CLEAR | JSVAL_TYPE_OBJECT +}; + +static_assert(sizeof(JSValueTag) == sizeof(uint32_t), + "compiler typed enum support is apparently buggy"); + +#elif defined(JS_PUNBOX64) + +enum JSValueTag : uint32_t { + JSVAL_TAG_MAX_DOUBLE = 0x1FFF0, + JSVAL_TAG_INT32 = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_INT32, + JSVAL_TAG_UNDEFINED = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_UNDEFINED, + JSVAL_TAG_NULL = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_NULL, + JSVAL_TAG_BOOLEAN = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_BOOLEAN, + JSVAL_TAG_MAGIC = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_MAGIC, + JSVAL_TAG_STRING = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_STRING, + JSVAL_TAG_SYMBOL = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_SYMBOL, + JSVAL_TAG_PRIVATE_GCTHING = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_PRIVATE_GCTHING, + JSVAL_TAG_BIGINT = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_BIGINT, +# ifdef ENABLE_RECORD_TUPLE + JSVAL_TAG_EXTENDED_PRIMITIVE = + JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_EXTENDED_PRIMITIVE, +# endif + JSVAL_TAG_OBJECT = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_OBJECT +}; + +static_assert(sizeof(JSValueTag) == sizeof(uint32_t), + "compiler typed enum support is apparently buggy"); + +enum JSValueShiftedTag : uint64_t { + // See Bug 584653 for why we include 0xFFFFFFFF. + JSVAL_SHIFTED_TAG_MAX_DOUBLE = + ((uint64_t(JSVAL_TAG_MAX_DOUBLE) << JSVAL_TAG_SHIFT) | 0xFFFFFFFF), + JSVAL_SHIFTED_TAG_INT32 = (uint64_t(JSVAL_TAG_INT32) << JSVAL_TAG_SHIFT), + JSVAL_SHIFTED_TAG_UNDEFINED = + (uint64_t(JSVAL_TAG_UNDEFINED) << JSVAL_TAG_SHIFT), + JSVAL_SHIFTED_TAG_NULL = (uint64_t(JSVAL_TAG_NULL) << JSVAL_TAG_SHIFT), + JSVAL_SHIFTED_TAG_BOOLEAN = (uint64_t(JSVAL_TAG_BOOLEAN) << JSVAL_TAG_SHIFT), + JSVAL_SHIFTED_TAG_MAGIC = (uint64_t(JSVAL_TAG_MAGIC) << JSVAL_TAG_SHIFT), + JSVAL_SHIFTED_TAG_STRING = (uint64_t(JSVAL_TAG_STRING) << JSVAL_TAG_SHIFT), + JSVAL_SHIFTED_TAG_SYMBOL = (uint64_t(JSVAL_TAG_SYMBOL) << JSVAL_TAG_SHIFT), + JSVAL_SHIFTED_TAG_PRIVATE_GCTHING = + (uint64_t(JSVAL_TAG_PRIVATE_GCTHING) << JSVAL_TAG_SHIFT), + JSVAL_SHIFTED_TAG_BIGINT = (uint64_t(JSVAL_TAG_BIGINT) << JSVAL_TAG_SHIFT), +# ifdef ENABLE_RECORD_TUPLE + JSVAL_SHIFTED_TAG_EXTENDED_PRIMITIVE = + (uint64_t(JSVAL_TYPE_EXTENDED_PRIMITIVE) << JSVAL_TAG_SHIFT), +# endif + JSVAL_SHIFTED_TAG_OBJECT = (uint64_t(JSVAL_TAG_OBJECT) << JSVAL_TAG_SHIFT) +}; + +static_assert(sizeof(JSValueShiftedTag) == sizeof(uint64_t), + "compiler typed enum support is apparently buggy"); + +#endif + +namespace JS { +namespace detail { + +#if defined(JS_NUNBOX32) + +constexpr JSValueTag ValueTypeToTag(JSValueType type) { + return static_cast<JSValueTag>(JSVAL_TAG_CLEAR | + std::underlying_type_t<JSValueType>(type)); +} + +constexpr bool ValueIsDouble(uint64_t bits) { + return uint32_t(bits >> JSVAL_TAG_SHIFT) <= uint32_t(JSVAL_TAG_CLEAR); +} + +constexpr JSValueTag ValueUpperExclPrimitiveTag = JSVAL_TAG_OBJECT; +constexpr JSValueTag ValueUpperInclNumberTag = JSVAL_TAG_INT32; +constexpr JSValueTag ValueLowerInclGCThingTag = JSVAL_TAG_STRING; + +#elif defined(JS_PUNBOX64) + +constexpr JSValueTag ValueTypeToTag(JSValueType type) { + return static_cast<JSValueTag>(JSVAL_TAG_MAX_DOUBLE | + std::underlying_type_t<JSValueType>(type)); +} + +constexpr bool ValueIsDouble(uint64_t bits) { + return bits <= JSVAL_SHIFTED_TAG_MAX_DOUBLE; +} + +constexpr uint64_t ValueTagMask = 0xFFFF'8000'0000'0000; + +// This should only be used in toGCThing. See the 'Spectre mitigations' comment. +constexpr uint64_t ValueGCThingPayloadMask = 0x0000'7FFF'FFFF'FFFF; + +// Mask used to combine an unbox operation with getting the chunk base. +constexpr uint64_t ValueGCThingPayloadChunkMask = + ValueGCThingPayloadMask & ~js::gc::ChunkMask; + +constexpr uint64_t ValueTypeToShiftedTag(JSValueType type) { + return static_cast<uint64_t>(ValueTypeToTag(type)) << JSVAL_TAG_SHIFT; +} +# define JSVAL_TYPE_TO_SHIFTED_TAG(type) \ + (JS::detail::ValueTypeToShiftedTag(type)) + +constexpr JSValueTag ValueUpperExclPrimitiveTag = JSVAL_TAG_OBJECT; +constexpr JSValueTag ValueUpperInclNumberTag = JSVAL_TAG_INT32; +constexpr JSValueTag ValueLowerInclGCThingTag = JSVAL_TAG_STRING; + +constexpr uint64_t ValueUpperExclShiftedPrimitiveTag = JSVAL_SHIFTED_TAG_OBJECT; +constexpr uint64_t ValueUpperExclShiftedNumberTag = JSVAL_SHIFTED_TAG_BOOLEAN; +constexpr uint64_t ValueLowerInclShiftedGCThingTag = JSVAL_SHIFTED_TAG_STRING; + +// JSVAL_TYPE_OBJECT and JSVAL_TYPE_NULL differ by one bit. We can use this to +// implement toObjectOrNull more efficiently. +constexpr uint64_t ValueObjectOrNullBit = 0x8ULL << JSVAL_TAG_SHIFT; +static_assert( + (JSVAL_SHIFTED_TAG_NULL ^ JSVAL_SHIFTED_TAG_OBJECT) == ValueObjectOrNullBit, + "ValueObjectOrNullBit must be consistent with object and null tags"); + +constexpr uint64_t IsValidUserModePointer(uint64_t bits) { + // All 64-bit platforms that we support actually have a 48-bit address space + // for user-mode pointers, with the top 16 bits all set to zero. + return (bits & 0xFFFF'0000'0000'0000) == 0; +} + +#endif /* JS_PUNBOX64 */ + +} // namespace detail +} // namespace JS + +#define JSVAL_TYPE_TO_TAG(type) (JS::detail::ValueTypeToTag(type)) + +enum JSWhyMagic { + /** a hole in a native object's elements */ + JS_ELEMENTS_HOLE, + + /** there is not a pending iterator value */ + JS_NO_ITER_VALUE, + + /** exception value thrown when closing a generator */ + JS_GENERATOR_CLOSING, + + /** used in debug builds to catch tracing errors */ + JS_ARG_POISON, + + /** an empty subnode in the AST serializer */ + JS_SERIALIZE_NO_NODE, + + /** magic value passed to natives to indicate construction */ + JS_IS_CONSTRUCTING, + + /** see class js::HashableValue */ + JS_HASH_KEY_EMPTY, + + /** error while running Ion code */ + JS_ION_ERROR, + + /** missing recover instruction result */ + JS_ION_BAILOUT, + + /** optimized out slot */ + JS_OPTIMIZED_OUT, + + /** uninitialized lexical bindings that produce ReferenceError on touch. */ + JS_UNINITIALIZED_LEXICAL, + + /** arguments object can't be created because environment is dead. */ + JS_MISSING_ARGUMENTS, + + /** for local use */ + JS_GENERIC_MAGIC, + + /** + * When an error object is created without the error cause argument, we set + * the error's cause slot to this magic value. + */ + JS_ERROR_WITHOUT_CAUSE, + + JS_WHY_MAGIC_COUNT +}; + +namespace js { +static inline JS::Value PoisonedObjectValue(uintptr_t poison); +#ifdef ENABLE_RECORD_TUPLE +// Re-defined in vm/RecordTupleBoxShared.h. We cannot include that +// file because it circularly includes this one. +bool IsExtendedPrimitive(const JSObject& obj); +namespace gc { +bool MaybeForwardedIsExtendedPrimitive(const JSObject& obj); +} // namespace gc +#endif +} // namespace js + +namespace JS { + +namespace detail { + +// IEEE-754 bit pattern for double-precision positive infinity. +constexpr int InfinitySignBit = 0; +constexpr uint64_t InfinityBits = + mozilla::InfinityBits<double, detail::InfinitySignBit>::value; + +// This is a quiet NaN on IEEE-754[2008] compatible platforms, including X86, +// ARM, SPARC, RISC-V and modern MIPS. +// +// Note: The default sign bit for a hardware synthesized NaN differs between X86 +// and ARM. Both values are considered compatible values on both +// platforms. +constexpr int CanonicalizedNaNSignBit = 0; +constexpr uint64_t CanonicalizedNaNSignificand = 0x8000000000000; + +#if defined(__sparc__) +// Some architectures (not to name names) generate NaNs with bit patterns that +// are incompatible with JS::Value's bit pattern restrictions. Instead we must +// canonicalize all hardware values before storing in JS::Value. +# define JS_NONCANONICAL_HARDWARE_NAN +#endif + +#if defined(__mips__) && !defined(__mips_nan_2008) +// These builds may run on hardware that has differing polarity of the signaling +// NaN bit. While the kernel may handle the trap for us, it is a performance +// issue so instead we compute the NaN to use on startup. The runtime value must +// still meet `ValueIsDouble` requirements which are checked on startup. + +// In particular, we expect one of the following values on MIPS: +// - 0x7FF7FFFFFFFFFFFF Legacy +// - 0x7FF8000000000000 IEEE-754[2008] +# define JS_RUNTIME_CANONICAL_NAN +#endif + +#if defined(JS_RUNTIME_CANONICAL_NAN) +extern uint64_t CanonicalizedNaNBits; +#else +constexpr uint64_t CanonicalizedNaNBits = + mozilla::SpecificNaNBits<double, detail::CanonicalizedNaNSignBit, + detail::CanonicalizedNaNSignificand>::value; +#endif +} // namespace detail + +// Return a quiet NaN that is compatible with JS::Value restrictions. +static MOZ_ALWAYS_INLINE double GenericNaN() { +#if !defined(JS_RUNTIME_CANONICAL_NAN) + static_assert(detail::ValueIsDouble(detail::CanonicalizedNaNBits), + "Canonical NaN must be compatible with JS::Value"); +#endif + + return mozilla::BitwiseCast<double>(detail::CanonicalizedNaNBits); +} + +// Return the infinity the engine uses +static MOZ_ALWAYS_INLINE double Infinity() { + return mozilla::BitwiseCast<double>(detail::InfinityBits); +} + +// Convert an arbitrary double to one compatible with JS::Value representation +// by replacing any NaN value with a canonical one. +static MOZ_ALWAYS_INLINE double CanonicalizeNaN(double d) { + if (MOZ_UNLIKELY(std::isnan(d))) { + return GenericNaN(); + } + return d; +} + +/** + * [SMDOC] JS::Value type + * + * JS::Value is the interface for a single JavaScript Engine value. A few + * general notes on JS::Value: + * + * - JS::Value has setX() and isX() members for X in + * + * { Int32, Double, String, Symbol, BigInt, Boolean, Undefined, Null, + * Object, Magic } + * + * JS::Value also contains toX() for each of the non-singleton types. + * + * - Magic is a singleton type whose payload contains either a JSWhyMagic + * "reason" for the magic value or a uint32_t value. By providing JSWhyMagic + * values when creating and checking for magic values, it is possible to + * assert, at runtime, that only magic values with the expected reason flow + * through a particular value. For example, if cx->exception has a magic + * value, the reason must be JS_GENERATOR_CLOSING. + * + * - The JS::Value operations are preferred. The JSVAL_* operations remain for + * compatibility; they may be removed at some point. These operations mostly + * provide similar functionality. But there are a few key differences. One + * is that JS::Value gives null a separate type. + * Also, to help prevent mistakenly boxing a nullable JSObject* as an object, + * Value::setObject takes a JSObject&. (Conversely, Value::toObject returns a + * JSObject&.) A convenience member Value::setObjectOrNull is provided. + * + * - Note that JS::Value is 8 bytes on 32 and 64-bit architectures. Thus, on + * 32-bit user code should avoid copying jsval/JS::Value as much as possible, + * preferring to pass by const Value&. + * + * Spectre mitigations + * =================== + * To mitigate Spectre attacks, we do the following: + * + * - On 64-bit platforms, when unboxing a Value, we XOR the bits with the + * expected type tag (instead of masking the payload bits). This guarantees + * that toString, toObject, toSymbol will return an invalid pointer (because + * some high bits will be set) when called on a Value with a different type + * tag. + * + * - On 32-bit platforms,when unboxing an object/string/symbol Value, we use a + * conditional move (not speculated) to zero the payload register if the type + * doesn't match. + */ +class alignas(8) Value { + private: + uint64_t asBits_; + + public: + constexpr Value() : asBits_(bitsFromTagAndPayload(JSVAL_TAG_UNDEFINED, 0)) {} + + private: + explicit constexpr Value(uint64_t asBits) : asBits_(asBits) {} + + static uint64_t bitsFromDouble(double d) { +#if defined(JS_NONCANONICAL_HARDWARE_NAN) + d = CanonicalizeNaN(d); +#endif + return mozilla::BitwiseCast<uint64_t>(d); + } + + static_assert(sizeof(JSValueType) == 1, + "type bits must fit in a single byte"); + static_assert(sizeof(JSValueTag) == 4, + "32-bit Value's tag_ must have size 4 to complement the " + "payload union's size 4"); + static_assert(sizeof(JSWhyMagic) <= 4, + "32-bit Value's JSWhyMagic payload field must not inflate " + "the payload beyond 4 bytes"); + + public: +#if defined(JS_NUNBOX32) + using PayloadType = uint32_t; +#elif defined(JS_PUNBOX64) + using PayloadType = uint64_t; +#endif + + static constexpr uint64_t bitsFromTagAndPayload(JSValueTag tag, + PayloadType payload) { + return (uint64_t(tag) << JSVAL_TAG_SHIFT) | payload; + } + + static constexpr Value fromTagAndPayload(JSValueTag tag, + PayloadType payload) { + return fromRawBits(bitsFromTagAndPayload(tag, payload)); + } + + static constexpr Value fromRawBits(uint64_t asBits) { return Value(asBits); } + + static constexpr Value fromInt32(int32_t i) { + return fromTagAndPayload(JSVAL_TAG_INT32, uint32_t(i)); + } + + static Value fromDouble(double d) { return fromRawBits(bitsFromDouble(d)); } + + /** + * Returns false if creating a NumberValue containing the given type would + * be lossy, true otherwise. + */ + template <typename T> + static bool isNumberRepresentable(const T t) { + return T(double(t)) == t; + } + + /*** Mutators ***/ + + void setNull() { + asBits_ = bitsFromTagAndPayload(JSVAL_TAG_NULL, 0); + MOZ_ASSERT(isNull()); + } + + void setUndefined() { + asBits_ = bitsFromTagAndPayload(JSVAL_TAG_UNDEFINED, 0); + MOZ_ASSERT(isUndefined()); + } + + void setInt32(int32_t i) { + asBits_ = bitsFromTagAndPayload(JSVAL_TAG_INT32, uint32_t(i)); + MOZ_ASSERT(toInt32() == i); + } + + void setDouble(double d) { + asBits_ = bitsFromDouble(d); + MOZ_ASSERT(isDouble()); + } + + void setString(JSString* str) { + MOZ_ASSERT(js::gc::IsCellPointerValid(str)); + asBits_ = bitsFromTagAndPayload(JSVAL_TAG_STRING, PayloadType(str)); + MOZ_ASSERT(toString() == str); + } + + void setSymbol(JS::Symbol* sym) { + MOZ_ASSERT(js::gc::IsCellPointerValid(sym)); + asBits_ = bitsFromTagAndPayload(JSVAL_TAG_SYMBOL, PayloadType(sym)); + MOZ_ASSERT(toSymbol() == sym); + } + + void setBigInt(JS::BigInt* bi) { + MOZ_ASSERT(js::gc::IsCellPointerValid(bi)); + asBits_ = bitsFromTagAndPayload(JSVAL_TAG_BIGINT, PayloadType(bi)); + MOZ_ASSERT(toBigInt() == bi); + } + + void setObject(JSObject& obj) { + MOZ_ASSERT(js::gc::IsCellPointerValid(&obj)); +#ifdef ENABLE_RECORD_TUPLE + MOZ_ASSERT(!js::gc::MaybeForwardedIsExtendedPrimitive(obj)); +#endif + setObjectNoCheck(&obj); + MOZ_ASSERT(&toObject() == &obj); + } + +#ifdef ENABLE_RECORD_TUPLE + void setExtendedPrimitive(JSObject& obj) { + MOZ_ASSERT(js::gc::IsCellPointerValid(&obj)); + MOZ_ASSERT(js::gc::MaybeForwardedIsExtendedPrimitive(obj)); + asBits_ = + bitsFromTagAndPayload(JSVAL_TAG_EXTENDED_PRIMITIVE, PayloadType(&obj)); + MOZ_ASSERT(&toExtendedPrimitive() == &obj); + } +#endif + + private: + void setObjectNoCheck(JSObject* obj) { + asBits_ = bitsFromTagAndPayload(JSVAL_TAG_OBJECT, PayloadType(obj)); + } + + friend inline Value js::PoisonedObjectValue(uintptr_t poison); + + public: + void setBoolean(bool b) { + asBits_ = bitsFromTagAndPayload(JSVAL_TAG_BOOLEAN, uint32_t(b)); + MOZ_ASSERT(toBoolean() == b); + } + + void setMagic(JSWhyMagic why) { + asBits_ = bitsFromTagAndPayload(JSVAL_TAG_MAGIC, uint32_t(why)); + MOZ_ASSERT(whyMagic() == why); + } + + void setMagicUint32(uint32_t payload) { + MOZ_ASSERT(payload >= JS_WHY_MAGIC_COUNT, + "This should only be used for non-standard magic values"); + asBits_ = bitsFromTagAndPayload(JSVAL_TAG_MAGIC, payload); + MOZ_ASSERT(magicUint32() == payload); + } + + void setNumber(float f) { + int32_t i; + if (mozilla::NumberIsInt32(f, &i)) { + setInt32(i); + return; + } + + setDouble(double(f)); + } + + void setNumber(double d) { + int32_t i; + if (mozilla::NumberIsInt32(d, &i)) { + setInt32(i); + return; + } + + setDouble(d); + } + + template <typename T> + void setNumber(const T t) { + static_assert(std::is_integral<T>::value, "must be integral type"); + MOZ_ASSERT(isNumberRepresentable(t), "value creation would be lossy"); + + if constexpr (std::numeric_limits<T>::is_signed) { + if constexpr (sizeof(t) <= sizeof(int32_t)) { + setInt32(int32_t(t)); + } else { + if (JSVAL_INT_MIN <= t && t <= JSVAL_INT_MAX) { + setInt32(int32_t(t)); + } else { + setDouble(double(t)); + } + } + } else { + if constexpr (sizeof(t) <= sizeof(uint16_t)) { + setInt32(int32_t(t)); + } else { + if (t <= JSVAL_INT_MAX) { + setInt32(int32_t(t)); + } else { + setDouble(double(t)); + } + } + } + } + + void setObjectOrNull(JSObject* arg) { + if (arg) { + setObject(*arg); + } else { + setNull(); + } + } + + void swap(Value& rhs) { + uint64_t tmp = rhs.asBits_; + rhs.asBits_ = asBits_; + asBits_ = tmp; + } + + private: + JSValueTag toTag() const { return JSValueTag(asBits_ >> JSVAL_TAG_SHIFT); } + + template <typename T, JSValueTag Tag> + T* unboxGCPointer() const { + MOZ_ASSERT((asBits_ & js::gc::CellAlignMask) == 0, + "GC pointer is not aligned. Is this memory corruption?"); +#if defined(JS_NUNBOX32) + uintptr_t payload = uint32_t(asBits_); + return reinterpret_cast<T*>(payload); +#elif defined(JS_PUNBOX64) + // Note: the 'Spectre mitigations' comment at the top of this class + // explains why we use XOR here. + constexpr uint64_t shiftedTag = uint64_t(Tag) << JSVAL_TAG_SHIFT; + return reinterpret_cast<T*>(uintptr_t(asBits_ ^ shiftedTag)); +#endif + } + + public: + /*** JIT-only interfaces to interact with and create raw Values ***/ +#if defined(JS_NUNBOX32) + PayloadType toNunboxPayload() const { return uint32_t(asBits_); } + + JSValueTag toNunboxTag() const { return toTag(); } +#elif defined(JS_PUNBOX64) + const void* bitsAsPunboxPointer() const { + return reinterpret_cast<void*>(asBits_); + } +#endif + + /*** Value type queries ***/ + + /* + * N.B. GCC, in some but not all cases, chooses to emit signed comparison + * of JSValueTag even though its underlying type has been forced to be + * uint32_t. Thus, all comparisons should explicitly cast operands to + * uint32_t. + */ + + bool isUndefined() const { +#if defined(JS_NUNBOX32) + return toTag() == JSVAL_TAG_UNDEFINED; +#elif defined(JS_PUNBOX64) + return asBits_ == JSVAL_SHIFTED_TAG_UNDEFINED; +#endif + } + + bool isNull() const { +#if defined(JS_NUNBOX32) + return toTag() == JSVAL_TAG_NULL; +#elif defined(JS_PUNBOX64) + return asBits_ == JSVAL_SHIFTED_TAG_NULL; +#endif + } + + bool isNullOrUndefined() const { return isNull() || isUndefined(); } + + bool isInt32() const { return toTag() == JSVAL_TAG_INT32; } + + bool isInt32(int32_t i32) const { + return asBits_ == bitsFromTagAndPayload(JSVAL_TAG_INT32, uint32_t(i32)); + } + + bool isDouble() const { return detail::ValueIsDouble(asBits_); } + + bool isNumber() const { +#if defined(JS_NUNBOX32) + MOZ_ASSERT(toTag() != JSVAL_TAG_CLEAR); + return uint32_t(toTag()) <= uint32_t(detail::ValueUpperInclNumberTag); +#elif defined(JS_PUNBOX64) + return asBits_ < detail::ValueUpperExclShiftedNumberTag; +#endif + } + + bool isString() const { return toTag() == JSVAL_TAG_STRING; } + + bool isSymbol() const { return toTag() == JSVAL_TAG_SYMBOL; } + + bool isBigInt() const { return toTag() == JSVAL_TAG_BIGINT; } + + bool isObject() const { +#if defined(JS_NUNBOX32) + return toTag() == JSVAL_TAG_OBJECT; +#elif defined(JS_PUNBOX64) + MOZ_ASSERT((asBits_ >> JSVAL_TAG_SHIFT) <= JSVAL_TAG_OBJECT); + return asBits_ >= JSVAL_SHIFTED_TAG_OBJECT; +#endif + } + +#ifdef ENABLE_RECORD_TUPLE + bool isExtendedPrimitive() const { + return toTag() == JSVAL_TAG_EXTENDED_PRIMITIVE; + } +#endif + + bool hasObjectPayload() const { + return isObject() || IF_RECORD_TUPLE(isExtendedPrimitive(), false); + } + + bool isPrimitive() const { +#if defined(JS_NUNBOX32) + return uint32_t(toTag()) < uint32_t(detail::ValueUpperExclPrimitiveTag); +#elif defined(JS_PUNBOX64) + return asBits_ < detail::ValueUpperExclShiftedPrimitiveTag; +#endif + } + + bool isObjectOrNull() const { return isObject() || isNull(); } + + bool isNumeric() const { return isNumber() || isBigInt(); } + + bool isGCThing() const { +#if defined(JS_NUNBOX32) + /* gcc sometimes generates signed < without explicit casts. */ + return uint32_t(toTag()) >= uint32_t(detail::ValueLowerInclGCThingTag); +#elif defined(JS_PUNBOX64) + return asBits_ >= detail::ValueLowerInclShiftedGCThingTag; +#endif + } + + bool isBoolean() const { return toTag() == JSVAL_TAG_BOOLEAN; } + + bool isTrue() const { + return asBits_ == bitsFromTagAndPayload(JSVAL_TAG_BOOLEAN, uint32_t(true)); + } + + bool isFalse() const { + return asBits_ == bitsFromTagAndPayload(JSVAL_TAG_BOOLEAN, uint32_t(false)); + } + + bool isMagic() const { return toTag() == JSVAL_TAG_MAGIC; } + + bool isMagic(JSWhyMagic why) const { + if (!isMagic()) { + return false; + } + MOZ_RELEASE_ASSERT(whyMagic() == why); + return true; + } + + JS::TraceKind traceKind() const { + MOZ_ASSERT(isGCThing()); + static_assert((JSVAL_TAG_STRING & 0x03) == size_t(JS::TraceKind::String), + "Value type tags must correspond with JS::TraceKinds."); + static_assert((JSVAL_TAG_SYMBOL & 0x03) == size_t(JS::TraceKind::Symbol), + "Value type tags must correspond with JS::TraceKinds."); + static_assert((JSVAL_TAG_OBJECT & 0x03) == size_t(JS::TraceKind::Object), + "Value type tags must correspond with JS::TraceKinds."); + static_assert((JSVAL_TAG_BIGINT & 0x03) == size_t(JS::TraceKind::BigInt), + "Value type tags must correspond with JS::TraceKinds."); + if (MOZ_UNLIKELY(isPrivateGCThing())) { + return JS::GCThingTraceKind(toGCThing()); + } +#ifdef ENABLE_RECORD_TUPLE + if (isExtendedPrimitive()) { + return JS::TraceKind::Object; + } +#endif + return JS::TraceKind(toTag() & 0x03); + } + + JSWhyMagic whyMagic() const { + MOZ_ASSERT(magicUint32() < JS_WHY_MAGIC_COUNT); + return static_cast<JSWhyMagic>(magicUint32()); + } + + uint32_t magicUint32() const { + MOZ_ASSERT(isMagic()); + return uint32_t(asBits_); + } + + /*** Comparison ***/ + + bool operator==(const Value& rhs) const { return asBits_ == rhs.asBits_; } + + bool operator!=(const Value& rhs) const { return asBits_ != rhs.asBits_; } + + friend inline bool SameType(const Value& lhs, const Value& rhs); + + /*** Extract the value's typed payload ***/ + + int32_t toInt32() const { + MOZ_ASSERT(isInt32()); + return int32_t(asBits_); + } + + double toDouble() const { + MOZ_ASSERT(isDouble()); + return mozilla::BitwiseCast<double>(asBits_); + } + + double toNumber() const { + MOZ_ASSERT(isNumber()); + return isDouble() ? toDouble() : double(toInt32()); + } + + JSString* toString() const { + MOZ_ASSERT(isString()); + return unboxGCPointer<JSString, JSVAL_TAG_STRING>(); + } + + JS::Symbol* toSymbol() const { + MOZ_ASSERT(isSymbol()); + return unboxGCPointer<JS::Symbol, JSVAL_TAG_SYMBOL>(); + } + + JS::BigInt* toBigInt() const { + MOZ_ASSERT(isBigInt()); + return unboxGCPointer<JS::BigInt, JSVAL_TAG_BIGINT>(); + } + + JSObject& toObject() const { + MOZ_ASSERT(isObject()); +#if defined(JS_PUNBOX64) + MOZ_ASSERT((asBits_ & detail::ValueGCThingPayloadMask) != 0); +#endif + return *unboxGCPointer<JSObject, JSVAL_TAG_OBJECT>(); + } + + JSObject* toObjectOrNull() const { + MOZ_ASSERT(isObjectOrNull()); +#if defined(JS_NUNBOX32) + return reinterpret_cast<JSObject*>(uintptr_t(asBits_)); +#elif defined(JS_PUNBOX64) + // Note: the 'Spectre mitigations' comment at the top of this class + // explains why we use XOR here and in other to* methods. + uint64_t ptrBits = + (asBits_ ^ JSVAL_SHIFTED_TAG_OBJECT) & ~detail::ValueObjectOrNullBit; + MOZ_ASSERT((ptrBits & 0x7) == 0); + return reinterpret_cast<JSObject*>(ptrBits); +#endif + } + +#ifdef ENABLE_RECORD_TUPLE + JSObject& toExtendedPrimitive() const { + MOZ_ASSERT(isExtendedPrimitive()); +# if defined(JS_PUNBOX64) + MOZ_ASSERT((asBits_ & detail::ValueGCThingPayloadMask) != 0); +# endif + return *unboxGCPointer<JSObject, JSVAL_TAG_EXTENDED_PRIMITIVE>(); + } +#endif + + JSObject& getObjectPayload() const { +#ifdef ENABLE_RECORD_TUPLE + return isExtendedPrimitive() ? toExtendedPrimitive() : toObject(); +#else + return toObject(); +#endif + } + + js::gc::Cell* toGCThing() const { + MOZ_ASSERT(isGCThing()); +#if defined(JS_NUNBOX32) + return reinterpret_cast<js::gc::Cell*>(uintptr_t(asBits_)); +#elif defined(JS_PUNBOX64) + uint64_t ptrBits = asBits_ & detail::ValueGCThingPayloadMask; + MOZ_ASSERT((ptrBits & 0x7) == 0); + return reinterpret_cast<js::gc::Cell*>(ptrBits); +#endif + } + + GCCellPtr toGCCellPtr() const { return GCCellPtr(toGCThing(), traceKind()); } + + bool toBoolean() const { + MOZ_ASSERT(isBoolean()); +#if defined(JS_NUNBOX32) + return bool(toNunboxPayload()); +#elif defined(JS_PUNBOX64) + return bool(asBits_ & 0x1); +#endif + } + + constexpr uint64_t asRawBits() const { return asBits_; } + + JSValueType extractNonDoubleType() const { + uint32_t type = toTag() & 0xF; + MOZ_ASSERT(type > JSVAL_TYPE_DOUBLE); + return JSValueType(type); + } + + JS::ValueType type() const { + if (isDouble()) { + return JS::ValueType::Double; + } + + JSValueType type = extractNonDoubleType(); + MOZ_ASSERT(type <= JSVAL_TYPE_OBJECT); + return JS::ValueType(type); + } + + /* + * Private API + * + * Private setters/getters allow the caller to read/write arbitrary + * word-size pointers or uint32s. After storing to a value with + * setPrivateX, it is the caller's responsibility to only read using + * toPrivateX. Private values are given a type which ensures they + * aren't marked by the GC. + */ + + void setPrivate(void* ptr) { +#if defined(JS_PUNBOX64) + MOZ_ASSERT(detail::IsValidUserModePointer(uintptr_t(ptr))); +#endif + asBits_ = uintptr_t(ptr); + MOZ_ASSERT(isDouble()); + } + + void* toPrivate() const { + MOZ_ASSERT(isDouble()); +#if defined(JS_PUNBOX64) + MOZ_ASSERT(detail::IsValidUserModePointer(asBits_)); +#endif + return reinterpret_cast<void*>(uintptr_t(asBits_)); + } + + void setPrivateUint32(uint32_t ui) { + MOZ_ASSERT(uint32_t(int32_t(ui)) == ui); + setInt32(int32_t(ui)); + } + + uint32_t toPrivateUint32() const { return uint32_t(toInt32()); } + + /* + * Private GC Thing API + * + * Non-JSObject, JSString, and JS::Symbol cells may be put into the 64-bit + * payload as private GC things. Such Values are considered isGCThing(), and + * as such, automatically marked. Their traceKind() is gotten via their + * cells. + */ + + void setPrivateGCThing(js::gc::Cell* cell) { + MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::String, + "Private GC thing Values must not be strings. Make a " + "StringValue instead."); + MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::Symbol, + "Private GC thing Values must not be symbols. Make a " + "SymbolValue instead."); + MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::BigInt, + "Private GC thing Values must not be BigInts. Make a " + "BigIntValue instead."); + MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::Object, + "Private GC thing Values must not be objects. Make an " + "ObjectValue instead."); + + MOZ_ASSERT(js::gc::IsCellPointerValid(cell)); +#if defined(JS_PUNBOX64) + // VisualStudio cannot contain parenthesized C++ style cast and shift + // inside decltype in template parameter: + // AssertionConditionType<decltype((uintptr_t(x) >> 1))> + // It throws syntax error. + MOZ_ASSERT((((uintptr_t)cell) >> JSVAL_TAG_SHIFT) == 0); +#endif + asBits_ = + bitsFromTagAndPayload(JSVAL_TAG_PRIVATE_GCTHING, PayloadType(cell)); + } + + bool isPrivateGCThing() const { return toTag() == JSVAL_TAG_PRIVATE_GCTHING; } +} JS_HAZ_GC_POINTER MOZ_NON_PARAM; + +static_assert(sizeof(Value) == 8, + "Value size must leave three tag bits, be a binary power, and " + "is ubiquitously depended upon everywhere"); + +static MOZ_ALWAYS_INLINE void ExposeValueToActiveJS(const Value& v) { +#ifdef DEBUG + Value tmp = v; + MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarrieredSlow(&tmp)); +#endif + if (v.isGCThing()) { + js::gc::ExposeGCThingToActiveJS(v.toGCCellPtr()); + } +} + +/************************************************************************/ + +static inline MOZ_MAY_CALL_AFTER_MUST_RETURN Value NullValue() { + Value v; + v.setNull(); + return v; +} + +static constexpr Value UndefinedValue() { return Value(); } + +static constexpr Value Int32Value(int32_t i32) { return Value::fromInt32(i32); } + +static inline Value DoubleValue(double dbl) { + Value v; + v.setDouble(dbl); + return v; +} + +static inline Value CanonicalizedDoubleValue(double d) { + return Value::fromDouble(CanonicalizeNaN(d)); +} + +static inline Value NaNValue() { + return Value::fromRawBits(detail::CanonicalizedNaNBits); +} + +static inline Value InfinityValue() { + return Value::fromRawBits(detail::InfinityBits); +} + +static inline Value Float32Value(float f) { + Value v; + v.setDouble(f); + return v; +} + +static inline Value StringValue(JSString* str) { + Value v; + v.setString(str); + return v; +} + +static inline Value SymbolValue(JS::Symbol* sym) { + Value v; + v.setSymbol(sym); + return v; +} + +static inline Value BigIntValue(JS::BigInt* bi) { + Value v; + v.setBigInt(bi); + return v; +} + +static inline Value BooleanValue(bool boo) { + Value v; + v.setBoolean(boo); + return v; +} + +static inline Value TrueValue() { + Value v; + v.setBoolean(true); + return v; +} + +static inline Value FalseValue() { + Value v; + v.setBoolean(false); + return v; +} + +static inline Value ObjectValue(JSObject& obj) { + Value v; + v.setObject(obj); + return v; +} + +#ifdef ENABLE_RECORD_TUPLE +static inline Value ExtendedPrimitiveValue(JSObject& obj) { + Value v; + v.setExtendedPrimitive(obj); + return v; +} +#endif + +static inline Value MagicValue(JSWhyMagic why) { + Value v; + v.setMagic(why); + return v; +} + +static inline Value MagicValueUint32(uint32_t payload) { + Value v; + v.setMagicUint32(payload); + return v; +} + +static constexpr Value NumberValue(uint32_t i) { + return i <= JSVAL_INT_MAX ? Int32Value(int32_t(i)) + : Value::fromDouble(double(i)); +} + +template <typename T> +static inline Value NumberValue(const T t) { + Value v; + v.setNumber(t); + return v; +} + +static inline Value ObjectOrNullValue(JSObject* obj) { + Value v; + v.setObjectOrNull(obj); + return v; +} + +static inline Value PrivateValue(void* ptr) { + Value v; + v.setPrivate(ptr); + return v; +} + +static inline Value PrivateValue(uintptr_t ptr) { + return PrivateValue(reinterpret_cast<void*>(ptr)); +} + +static inline Value PrivateUint32Value(uint32_t ui) { + Value v; + v.setPrivateUint32(ui); + return v; +} + +static inline Value PrivateGCThingValue(js::gc::Cell* cell) { + Value v; + v.setPrivateGCThing(cell); + return v; +} + +inline bool SameType(const Value& lhs, const Value& rhs) { +#if defined(JS_NUNBOX32) + JSValueTag ltag = lhs.toTag(), rtag = rhs.toTag(); + return ltag == rtag || (ltag < JSVAL_TAG_CLEAR && rtag < JSVAL_TAG_CLEAR); +#elif defined(JS_PUNBOX64) + return (lhs.isDouble() && rhs.isDouble()) || + (((lhs.asBits_ ^ rhs.asBits_) & 0xFFFF800000000000ULL) == 0); +#endif +} + +} // namespace JS + +/************************************************************************/ + +namespace JS { +JS_PUBLIC_API void HeapValuePostWriteBarrier(Value* valuep, const Value& prev, + const Value& next); +JS_PUBLIC_API void HeapValueWriteBarriers(Value* valuep, const Value& prev, + const Value& next); + +template <> +struct GCPolicy<JS::Value> { + static void trace(JSTracer* trc, Value* v, const char* name) { + // This should only be called as part of root marking since that's the only + // time we should trace unbarriered GC thing pointers. This will assert if + // called at other times. + TraceRoot(trc, v, name); + } + static bool isTenured(const Value& thing) { + return !thing.isGCThing() || !IsInsideNursery(thing.toGCThing()); + } + static bool isValid(const Value& value) { + return !value.isGCThing() || js::gc::IsCellPointerValid(value.toGCThing()); + } +}; + +} // namespace JS + +namespace js { + +template <> +struct BarrierMethods<JS::Value> { + static gc::Cell* asGCThingOrNull(const JS::Value& v) { + return v.isGCThing() ? v.toGCThing() : nullptr; + } + static void postWriteBarrier(JS::Value* v, const JS::Value& prev, + const JS::Value& next) { + JS::HeapValuePostWriteBarrier(v, prev, next); + } + static void exposeToJS(const JS::Value& v) { JS::ExposeValueToActiveJS(v); } + static void readBarrier(const JS::Value& v) { + if (v.isGCThing()) { + js::gc::IncrementalReadBarrier(v.toGCCellPtr()); + } + } +}; + +template <class Wrapper> +class MutableValueOperations; + +/** + * A class designed for CRTP use in implementing the non-mutating parts of the + * Value interface in Value-like classes. Wrapper must be a class inheriting + * ValueOperations<Wrapper> with a visible get() method returning a const + * reference to the Value abstracted by Wrapper. + */ +template <class Wrapper> +class WrappedPtrOperations<JS::Value, Wrapper> { + const JS::Value& value() const { + return static_cast<const Wrapper*>(this)->get(); + } + + public: + bool isUndefined() const { return value().isUndefined(); } + bool isNull() const { return value().isNull(); } + bool isBoolean() const { return value().isBoolean(); } + bool isTrue() const { return value().isTrue(); } + bool isFalse() const { return value().isFalse(); } + bool isNumber() const { return value().isNumber(); } + bool isInt32() const { return value().isInt32(); } + bool isInt32(int32_t i32) const { return value().isInt32(i32); } + bool isDouble() const { return value().isDouble(); } + bool isString() const { return value().isString(); } + bool isSymbol() const { return value().isSymbol(); } + bool isBigInt() const { return value().isBigInt(); } + bool isObject() const { return value().isObject(); } +#ifdef ENABLE_RECORD_TUPLE + bool isExtendedPrimitive() const { return value().isExtendedPrimitive(); } +#endif + bool hasObjectPayload() const { return value().hasObjectPayload(); } + bool isMagic() const { return value().isMagic(); } + bool isMagic(JSWhyMagic why) const { return value().isMagic(why); } + bool isGCThing() const { return value().isGCThing(); } + bool isPrivateGCThing() const { return value().isPrivateGCThing(); } + bool isPrimitive() const { return value().isPrimitive(); } + + bool isNullOrUndefined() const { return value().isNullOrUndefined(); } + bool isObjectOrNull() const { return value().isObjectOrNull(); } + bool isNumeric() const { return value().isNumeric(); } + + bool toBoolean() const { return value().toBoolean(); } + double toNumber() const { return value().toNumber(); } + int32_t toInt32() const { return value().toInt32(); } + double toDouble() const { return value().toDouble(); } + JSString* toString() const { return value().toString(); } + JS::Symbol* toSymbol() const { return value().toSymbol(); } + JS::BigInt* toBigInt() const { return value().toBigInt(); } + JSObject& toObject() const { return value().toObject(); } + JSObject* toObjectOrNull() const { return value().toObjectOrNull(); } +#ifdef ENABLE_RECORD_TUPLE + JSObject& toExtendedPrimitive() const { + return value().toExtendedPrimitive(); + } +#endif + JSObject& getObjectPayload() const { return value().getObjectPayload(); } + JS::GCCellPtr toGCCellPtr() const { return value().toGCCellPtr(); } + gc::Cell* toGCThing() const { return value().toGCThing(); } + JS::TraceKind traceKind() const { return value().traceKind(); } + void* toPrivate() const { return value().toPrivate(); } + uint32_t toPrivateUint32() const { return value().toPrivateUint32(); } + + uint64_t asRawBits() const { return value().asRawBits(); } + JSValueType extractNonDoubleType() const { + return value().extractNonDoubleType(); + } + JS::ValueType type() const { return value().type(); } + + JSWhyMagic whyMagic() const { return value().whyMagic(); } + uint32_t magicUint32() const { return value().magicUint32(); } +}; + +/** + * A class designed for CRTP use in implementing all the mutating parts of the + * Value interface in Value-like classes. Wrapper must be a class inheriting + * MutableWrappedPtrOperations<Wrapper> with visible get() methods returning + * const and non-const references to the Value abstracted by Wrapper. + */ +template <class Wrapper> +class MutableWrappedPtrOperations<JS::Value, Wrapper> + : public WrappedPtrOperations<JS::Value, Wrapper> { + protected: + void set(const JS::Value& v) { + // Call Wrapper::set to trigger any barriers. + static_cast<Wrapper*>(this)->set(v); + } + + public: + void setNull() { set(JS::NullValue()); } + void setUndefined() { set(JS::UndefinedValue()); } + void setInt32(int32_t i) { set(JS::Int32Value(i)); } + void setDouble(double d) { set(JS::DoubleValue(d)); } + void setNaN() { set(JS::NaNValue()); } + void setInfinity() { set(JS::InfinityValue()); } + void setBoolean(bool b) { set(JS::BooleanValue(b)); } + void setMagic(JSWhyMagic why) { set(JS::MagicValue(why)); } + template <typename T> + void setNumber(T t) { + set(JS::NumberValue(t)); + } + void setString(JSString* str) { set(JS::StringValue(str)); } + void setSymbol(JS::Symbol* sym) { set(JS::SymbolValue(sym)); } + void setBigInt(JS::BigInt* bi) { set(JS::BigIntValue(bi)); } + void setObject(JSObject& obj) { set(JS::ObjectValue(obj)); } + void setObjectOrNull(JSObject* arg) { set(JS::ObjectOrNullValue(arg)); } +#ifdef ENABLE_RECORD_TUPLE + void setExtendedPrimitive(JSObject& obj) { + return set(JS::ExtendedPrimitiveValue(obj)); + } +#endif + void setPrivate(void* ptr) { set(JS::PrivateValue(ptr)); } + void setPrivateUint32(uint32_t ui) { set(JS::PrivateUint32Value(ui)); } + void setPrivateGCThing(js::gc::Cell* cell) { + set(JS::PrivateGCThingValue(cell)); + } +}; + +/* + * Augment the generic Heap<T> interface when T = Value with + * type-querying, value-extracting, and mutating operations. + */ +template <typename Wrapper> +class HeapOperations<JS::Value, Wrapper> + : public MutableWrappedPtrOperations<JS::Value, Wrapper> {}; + +MOZ_HAVE_NORETURN MOZ_COLD MOZ_NEVER_INLINE void ReportBadValueTypeAndCrash( + const JS::Value& val); + +// If the Value is a GC pointer type, call |f| with the pointer cast to that +// type and return the result wrapped in a Maybe, otherwise return None(). +template <typename F> +auto MapGCThingTyped(const JS::Value& val, F&& f) { + switch (val.type()) { + case JS::ValueType::String: { + JSString* str = val.toString(); + MOZ_ASSERT(gc::IsCellPointerValid(str)); + return mozilla::Some(f(str)); + } +#ifdef ENABLE_RECORD_TUPLE + case JS::ValueType::ExtendedPrimitive: +#endif + case JS::ValueType::Object: { + JSObject* obj = &val.getObjectPayload(); + MOZ_ASSERT(gc::IsCellPointerValid(obj)); + return mozilla::Some(f(obj)); + } + case JS::ValueType::Symbol: { + JS::Symbol* sym = val.toSymbol(); + MOZ_ASSERT(gc::IsCellPointerValid(sym)); + return mozilla::Some(f(sym)); + } + case JS::ValueType::BigInt: { + JS::BigInt* bi = val.toBigInt(); + MOZ_ASSERT(gc::IsCellPointerValid(bi)); + return mozilla::Some(f(bi)); + } + case JS::ValueType::PrivateGCThing: { + MOZ_ASSERT(gc::IsCellPointerValid(val.toGCThing())); + return mozilla::Some(MapGCThingTyped(val.toGCCellPtr(), std::move(f))); + } + case JS::ValueType::Double: + case JS::ValueType::Int32: + case JS::ValueType::Boolean: + case JS::ValueType::Undefined: + case JS::ValueType::Null: + case JS::ValueType::Magic: { + MOZ_ASSERT(!val.isGCThing()); + using ReturnType = decltype(f(static_cast<JSObject*>(nullptr))); + return mozilla::Maybe<ReturnType>(); + } + } + + ReportBadValueTypeAndCrash(val); +} + +// If the Value is a GC pointer type, call |f| with the pointer cast to that +// type. Return whether this happened. +template <typename F> +bool ApplyGCThingTyped(const JS::Value& val, F&& f) { + return MapGCThingTyped(val, + [&f](auto t) { + f(t); + return true; + }) + .isSome(); +} + +static inline JS::Value PoisonedObjectValue(uintptr_t poison) { + JS::Value v; + v.setObjectNoCheck(reinterpret_cast<JSObject*>(poison)); + return v; +} + +} // namespace js + +#ifdef DEBUG +namespace JS { + +MOZ_ALWAYS_INLINE void AssertValueIsNotGray(const Value& value) { + if (value.isGCThing()) { + AssertCellIsNotGray(value.toGCThing()); + } +} + +MOZ_ALWAYS_INLINE void AssertValueIsNotGray(const Heap<Value>& value) { + AssertValueIsNotGray(value.unbarrieredGet()); +} + +} // namespace JS +#endif + +/************************************************************************/ + +namespace JS { + +extern JS_PUBLIC_DATA const HandleValue NullHandleValue; +extern JS_PUBLIC_DATA const HandleValue UndefinedHandleValue; +extern JS_PUBLIC_DATA const HandleValue TrueHandleValue; +extern JS_PUBLIC_DATA const HandleValue FalseHandleValue; +extern JS_PUBLIC_DATA const Handle<mozilla::Maybe<Value>> NothingHandleValue; + +} // namespace JS + +#endif /* js_Value_h */ diff --git a/js/public/ValueArray.h b/js/public/ValueArray.h new file mode 100644 index 0000000000..0d520fb9b6 --- /dev/null +++ b/js/public/ValueArray.h @@ -0,0 +1,130 @@ +/* -*- 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/. */ + +/** GC-safe representations of consecutive JS::Value in memory. */ + +#ifndef js_ValueArray_h +#define js_ValueArray_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_IMPLICIT, MOZ_RAII + +#include <stdint.h> // size_t + +#include "js/CallArgs.h" // JS::CallArgs +#include "js/GCVector.h" // JS::RootedVector +#include "js/RootingAPI.h" // JS::AutoGCRooter, JS::{,Mutable}Handle +#include "js/Value.h" // JS::Value + +namespace js { +JS_PUBLIC_API void TraceValueArray(JSTracer* trc, size_t length, + JS::Value* elements); +} // namespace js + +namespace JS { + +/* A fixed-size array of values, for use inside Rooted<>. */ +template <size_t N> +struct ValueArray { + Value elements[N]; + void trace(JSTracer* trc) { js::TraceValueArray(trc, N, elements); } +}; + +/** RootedValueArray roots an internal fixed-size array of Values. */ +template <size_t N> +using RootedValueArray = Rooted<ValueArray<N>>; + +/** + * A generic handle to an array of rooted values. + * + * The rooted array refernced can take several forms, therfore this is not the + * same as Handle<js::ValueArray>. + */ +class HandleValueArray { + const size_t length_; + const Value* const elements_; + + HandleValueArray(size_t len, const Value* elements) + : length_(len), elements_(elements) {} + + public: + explicit HandleValueArray(Handle<Value> value) + : length_(1), elements_(value.address()) {} + + MOZ_IMPLICIT HandleValueArray(const RootedVector<Value>& values) + : length_(values.length()), elements_(values.begin()) {} + + template <size_t N> + MOZ_IMPLICIT HandleValueArray(const RootedValueArray<N>& values) + : length_(N), elements_(values.begin()) {} + + /** CallArgs must already be rooted somewhere up the stack. */ + MOZ_IMPLICIT HandleValueArray(const JS::CallArgs& args) + : length_(args.length()), elements_(args.array()) {} + + /** Use with care! Only call this if the data is guaranteed to be marked. */ + static HandleValueArray fromMarkedLocation(size_t len, + const Value* elements) { + return HandleValueArray(len, elements); + } + + static HandleValueArray subarray(const HandleValueArray& values, + size_t startIndex, size_t len) { + MOZ_ASSERT(startIndex + len <= values.length()); + return HandleValueArray(len, values.begin() + startIndex); + } + + static HandleValueArray empty() { return HandleValueArray(0, nullptr); } + + size_t length() const { return length_; } + const Value* begin() const { return elements_; } + + Handle<Value> operator[](size_t i) const { + MOZ_ASSERT(i < length_); + return Handle<Value>::fromMarkedLocation(&elements_[i]); + } +}; + +} // namespace JS + +namespace js { + +template <size_t N, typename Container> +class WrappedPtrOperations<JS::ValueArray<N>, Container> { + const JS::ValueArray<N>& array() const { + return static_cast<const Container*>(this)->get(); + } + + public: + size_t length() const { return N; } + const JS::Value* begin() const { return array().elements; } + + JS::HandleValue operator[](size_t i) const { + MOZ_ASSERT(i < N); + return JS::HandleValue::fromMarkedLocation(&array().elements[i]); + } +}; + +template <size_t N, typename Container> +class MutableWrappedPtrOperations<JS::ValueArray<N>, Container> + : public WrappedPtrOperations<JS::ValueArray<N>, Container> { + using Base = WrappedPtrOperations<JS::ValueArray<N>, Container>; + JS::ValueArray<N>& array() { return static_cast<Container*>(this)->get(); } + + public: + using Base::begin; + JS::Value* begin() { return array().elements; } + + using Base::operator[]; + JS::MutableHandleValue operator[](size_t i) { + MOZ_ASSERT(i < N); + return JS::MutableHandleValue::fromMarkedLocation(&array().elements[i]); + } +}; + +} // namespace js + +#endif // js_ValueArray_h diff --git a/js/public/Vector.h b/js/public/Vector.h new file mode 100644 index 0000000000..afaa55ca01 --- /dev/null +++ b/js/public/Vector.h @@ -0,0 +1,38 @@ +/* -*- 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 js_Vector_h +#define js_Vector_h + +#include "mozilla/Vector.h" + +#include <type_traits> + +#include "js/TypeDecls.h" + +namespace js { + +class JS_PUBLIC_API TempAllocPolicy; + +namespace detail { + +template <typename T> +struct TypeIsGCThing : std::false_type {}; + +template <> +struct TypeIsGCThing<JS::Value> : std::true_type {}; + +} // namespace detail + +template <typename T, size_t MinInlineCapacity = 0, + class AllocPolicy = TempAllocPolicy, + // Don't use this with JS::Value! Use JS::RootedValueVector instead. + typename = std::enable_if_t<!detail::TypeIsGCThing<T>::value>> +using Vector = mozilla::Vector<T, MinInlineCapacity, AllocPolicy>; + +} // namespace js + +#endif /* js_Vector_h */ diff --git a/js/public/WaitCallbacks.h b/js/public/WaitCallbacks.h new file mode 100644 index 0000000000..b79172e6c8 --- /dev/null +++ b/js/public/WaitCallbacks.h @@ -0,0 +1,54 @@ +/* -*- 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 js_WaitCallbacks_h +#define js_WaitCallbacks_h + +#include <stddef.h> +#include <stdint.h> + +#include "jstypes.h" + +struct JS_PUBLIC_API JSRuntime; + +namespace JS { + +/** + * When the JSRuntime is about to block in an Atomics.wait() JS call or in a + * `wait` instruction in WebAssembly, it can notify the host by means of a call + * to BeforeWaitCallback. After the wait, it can notify the host by means of a + * call to AfterWaitCallback. Both callbacks must be null, or neither. + * + * (If you change the callbacks from null to not-null or vice versa while some + * thread on the runtime is in a wait, you will be sorry.) + * + * The argument to the BeforeWaitCallback is a pointer to uninitialized + * stack-allocated working memory of size WAIT_CALLBACK_CLIENT_MAXMEM bytes. + * The caller of SetWaitCallback() must pass the amount of memory it will need, + * and this amount will be checked against that limit and the process will crash + * reliably if the check fails. + * + * The value returned by the BeforeWaitCallback will be passed to the + * AfterWaitCallback. + * + * The AfterWaitCallback will be called even if the wakeup is spurious and the + * thread goes right back to waiting again. Of course the thread will call the + * BeforeWaitCallback once more before it goes to sleep in this situation. + */ + +static constexpr size_t WAIT_CALLBACK_CLIENT_MAXMEM = 32; + +using BeforeWaitCallback = void* (*)(uint8_t* memory); +using AfterWaitCallback = void (*)(void* cookie); + +extern JS_PUBLIC_API void SetWaitCallback(JSRuntime* rt, + BeforeWaitCallback beforeWait, + AfterWaitCallback afterWait, + size_t requiredMemory); + +} // namespace JS + +#endif // js_WaitCallbacks_h diff --git a/js/public/Warnings.h b/js/public/Warnings.h new file mode 100644 index 0000000000..c00cd099b6 --- /dev/null +++ b/js/public/Warnings.h @@ -0,0 +1,98 @@ +/* -*- 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/. */ + +/* + * Functionality for issuing and handling warnings. + * + * Warnings are situations that aren't inherently full-blown errors (and perhaps + * for spec compliance *can't* be), but that may represent dubious programming + * practice that embeddings may wish to know about. + * + * SpiderMonkey recognizes an unspecified set of syntactic patterns and runtime + * behaviors as triggering a warning. Embeddings may also recognize and report + * additional warnings. + */ + +#ifndef js_Warnings_h +#define js_Warnings_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_FORMAT_PRINTF, MOZ_RAII + +#include "jstypes.h" // JS_PUBLIC_API + +struct JS_PUBLIC_API JSContext; +class JSErrorReport; + +namespace JS { + +/** + * Report a warning represented by the sprintf-like conversion of ASCII format + * filled from trailing ASCII arguments. + * + * Return true iff the warning was successfully reported without reporting an + * error (or being upgraded into one). + */ +extern JS_PUBLIC_API bool WarnASCII(JSContext* cx, const char* format, ...) + MOZ_FORMAT_PRINTF(2, 3); + +/** + * Report a warning represented by the sprintf-like conversion of Latin-1 format + * filled from trailing Latin-1 arguments. + * + * Return true iff the warning was successfully reported without reporting an + * error (or being upgraded into one). + */ +extern JS_PUBLIC_API bool WarnLatin1(JSContext* cx, const char* format, ...) + MOZ_FORMAT_PRINTF(2, 3); + +/** + * Report a warning represented by the sprintf-like conversion of UTF-8 format + * filled from trailing UTF-8 arguments. + * + * Return true iff the warning was successfully reported without reporting an + * error (or being upgraded into one). + */ +extern JS_PUBLIC_API bool WarnUTF8(JSContext* cx, const char* format, ...) + MOZ_FORMAT_PRINTF(2, 3); + +using WarningReporter = void (*)(JSContext* cx, JSErrorReport* report); + +extern JS_PUBLIC_API WarningReporter GetWarningReporter(JSContext* cx); + +extern JS_PUBLIC_API WarningReporter +SetWarningReporter(JSContext* cx, WarningReporter reporter); + +/** + * A simple RAII class that clears the registered warning reporter on + * construction and restores it on destruction. + * + * A fresh warning reporter *may* be set while an instance of this class is + * live, but it must be unset in LIFO fashion by the time that instance is + * destroyed. + */ +class MOZ_RAII JS_PUBLIC_API AutoSuppressWarningReporter { + JSContext* context_; + WarningReporter prevReporter_; + + public: + explicit AutoSuppressWarningReporter(JSContext* cx) : context_(cx) { + prevReporter_ = SetWarningReporter(context_, nullptr); + } + + ~AutoSuppressWarningReporter() { +#ifdef DEBUG + WarningReporter reporter = +#endif + SetWarningReporter(context_, prevReporter_); + MOZ_ASSERT(reporter == nullptr, "Unexpected WarningReporter active"); + SetWarningReporter(context_, prevReporter_); + } +}; + +} // namespace JS + +#endif // js_Warnings_h diff --git a/js/public/WasmFeatures.h b/js/public/WasmFeatures.h new file mode 100644 index 0000000000..4abd99cd1d --- /dev/null +++ b/js/public/WasmFeatures.h @@ -0,0 +1,172 @@ +/* -*- 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 js_WasmFeatures_h +#define js_WasmFeatures_h + +// [SMDOC] WebAssembly feature gating +// +// Declarative listing of WebAssembly optional features. This macro is used to +// generate most of the feature gating code in a centralized manner. See +// 'Adding a feature' below for the exact steps needed to add a new feature. +// +// Each feature is either `DEFAULT`, `TENTATIVE`, or `EXPERIMENTAL`: +// +// Default features are enabled by default in ContextOptions and in the +// JS-shell, and are given a `--no-wasm-FEATURE` shell flag to disable. The +// `--wasm-FEATURE` flag is rejected. +// +// Tentative features are like Default features, but the `--wasm-FEATURE` flag +// is silently ignored. +// +// Experimental features are disabled by default in ContextOptions and in the +// JS-shell, and are given a `--wasm-FEATURE` shell flag to enable. The +// `--no-wasm-FEATURE` flag is silently ignored. +// +// The browser pref is `javascript.options.wasm-FEATURE` for default, tentative, +// and experimental features alike. +// +// # Adding a feature +// +// 1. Add a configure switch for the feature in js/moz.configure +// 2. Add a WASM_FEATURE_ENABLED #define below +// 3. Add the feature to JS_FOR_WASM_FEATURES +// a. capitalized name: Used for naming of feature functions, including +// wasmFeatureEnabled shell function. +// b. lower case name: Used for naming of feature flag variables, including +// in wasm::FeatureArgs. +// c. compile predicate: Set to WASM_FEATURE_ENABLED +// d. compiler predicate: Expression of compilers that this feature depends +// on. +// e. flag predicate: Expression used to predicate enablement of feature +// flag. Useful for disabling a feature when dependent feature is not +// enabled or if we are fuzzing. +// f. shell flag: The stem of the JS-shell flag. Will be expanded to +// --no-wasm-FEATURE or --wasm-FEATURE as explained above. +// g. preference name: The stem of the browser preference. Will be expanded +// to `javascript.options.wasm-FEATURE`. +// 4. Add the preference to module/libpref/init/StaticPrefList.yaml +// a. Use conditionally compiled flag +// b. Set value to 'true' for default features, @IS_NIGHTLY_BUILD@ for +// tentative features, and 'false' for experimental features. +// 5. [fuzzing] Add the feature to gluesmith/src/lib.rs, if wasm-smith has +// support for it. + +#ifdef ENABLE_WASM_SIMD +# define WASM_SIMD_ENABLED 1 +#else +# define WASM_SIMD_ENABLED 0 +#endif +#ifdef ENABLE_WASM_RELAXED_SIMD +# define WASM_RELAXED_SIMD_ENABLED 1 +#else +# define WASM_RELAXED_SIMD_ENABLED 0 +#endif +#ifdef ENABLE_WASM_EXTENDED_CONST +# define WASM_EXTENDED_CONST_ENABLED 1 +#else +# define WASM_EXTENDED_CONST_ENABLED 0 +#endif +#ifdef ENABLE_WASM_FUNCTION_REFERENCES +# define WASM_FUNCTION_REFERENCES_ENABLED 1 +#else +# define WASM_FUNCTION_REFERENCES_ENABLED 0 +#endif +#ifdef ENABLE_WASM_GC +# define WASM_GC_ENABLED 1 +#else +# define WASM_GC_ENABLED 0 +#endif +#ifdef ENABLE_WASM_MEMORY64 +# define WASM_MEMORY64_ENABLED 1 +#else +# define WASM_MEMORY64_ENABLED 0 +#endif +#ifdef ENABLE_WASM_MEMORY_CONTROL +# define WASM_MEMORY_CONTROL_ENABLED 1 +#else +# define WASM_MEMORY_CONTROL_ENABLED 0 +#endif +#ifdef ENABLE_WASM_MOZ_INTGEMM +# define WASM_MOZ_INTGEMM_ENABLED 1 +#else +# define WASM_MOZ_INTGEMM_ENABLED 0 +#endif + +// clang-format off +#define JS_FOR_WASM_FEATURES(DEFAULT, TENTATIVE, EXPERIMENTAL) \ + TENTATIVE(/* capitalized name */ ExtendedConst, \ + /* lower case name */ extendedConst, \ + /* compile predicate */ WASM_EXTENDED_CONST_ENABLED, \ + /* compiler predicate */ true, \ + /* flag predicate */ true, \ + /* shell flag */ "extended-const", \ + /* preference name */ "extended_const") \ + TENTATIVE( \ + /* capitalized name */ Exceptions, \ + /* lower case name */ exceptions, \ + /* compile predicate */ true, \ + /* compiler predicate */ BaselineAvailable(cx) || IonAvailable(cx), \ + /* flag predicate */ true, \ + /* shell flag */ "exceptions", \ + /* preference name */ "exceptions") \ + EXPERIMENTAL(/* capitalized name */ FunctionReferences, \ + /* lower case name */ functionReferences, \ + /* compile predicate */ WASM_FUNCTION_REFERENCES_ENABLED, \ + /* compiler predicate */ BaselineAvailable(cx) || \ + IonAvailable(cx), \ + /* flag predicate */ true, \ + /* shell flag */ "function-references", \ + /* preference name */ "function_references") \ + EXPERIMENTAL(/* capitalized name */ Gc, \ + /* lower case name */ gc, \ + /* compile predicate */ WASM_GC_ENABLED, \ + /* compiler predicate */ AnyCompilerAvailable(cx), \ + /* flag predicate */ WasmFunctionReferencesFlag(cx), \ + /* shell flag */ "gc", \ + /* preference name */ "gc") \ + TENTATIVE(/* capitalized name */ RelaxedSimd, \ + /* lower case name */ v128Relaxed, \ + /* compile predicate */ WASM_RELAXED_SIMD_ENABLED, \ + /* compiler predicate */ AnyCompilerAvailable(cx), \ + /* flag predicate */ js::jit::JitSupportsWasmSimd(), \ + /* shell flag */ "relaxed-simd", \ + /* preference name */ "relaxed_simd") \ + TENTATIVE( \ + /* capitalized name */ Memory64, \ + /* lower case name */ memory64, \ + /* compile predicate */ WASM_MEMORY64_ENABLED, \ + /* compiler predicate */ BaselineAvailable(cx) || IonAvailable(cx), \ + /* flag predicate */ true, \ + /* shell flag */ "memory64", \ + /* preference name */ "memory64") \ + EXPERIMENTAL( \ + /* capitalized name */ MemoryControl, \ + /* lower case name */ memoryControl, \ + /* compile predicate */ WASM_MEMORY_CONTROL_ENABLED, \ + /* compiler predicate */ BaselineAvailable(cx) || IonAvailable(cx), \ + /* flag predicate */ true, \ + /* shell flag */ "memory-control", \ + /* preference name */ "memory_control") \ + EXPERIMENTAL(/* capitalized name */ MozIntGemm, \ + /* lower case name */ mozIntGemm, \ + /* compile predicate */ WASM_MOZ_INTGEMM_ENABLED, \ + /* compiler predicate */ BaselineAvailable(cx) || \ + IonAvailable(cx), \ + /* flag predicate */ IsSimdPrivilegedContext(cx), \ + /* shell flag */ "moz-intgemm", \ + /* preference name */ "moz_intgemm") \ + EXPERIMENTAL(/* capitalized name */ TestSerialization, \ + /* lower case name */ testSerialization, \ + /* compile predicate */ 1, \ + /* compiler predicate */ IonAvailable(cx), \ + /* flag predicate */ true, \ + /* shell flag */ "test-serialization", \ + /* preference name */ "test-serialization") + +// clang-format on + +#endif // js_WasmFeatures_h diff --git a/js/public/WasmModule.h b/js/public/WasmModule.h new file mode 100644 index 0000000000..a4d386ce61 --- /dev/null +++ b/js/public/WasmModule.h @@ -0,0 +1,46 @@ +/* -*- 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 js_WasmModule_h +#define js_WasmModule_h + +#include "mozilla/RefPtr.h" // RefPtr + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/RefCounted.h" // AtomicRefCounted +#include "js/TypeDecls.h" // HandleObject + +namespace JS { + +/** + * The WasmModule interface allows the embedding to hold a reference to the + * underying C++ implementation of a JS WebAssembly.Module object for purposes + * of efficient postMessage() and (de)serialization from a random thread. + * + * In particular, this allows postMessage() of a WebAssembly.Module: + * GetWasmModule() is called when making a structured clone of a payload + * containing a WebAssembly.Module object. The structured clone buffer holds a + * refcount of the JS::WasmModule until createObject() is called in the target + * agent's JSContext. The new WebAssembly.Module object continues to hold the + * JS::WasmModule and thus the final reference of a JS::WasmModule may be + * dropped from any thread and so the virtual destructor (and all internal + * methods of the C++ module) must be thread-safe. + */ + +struct WasmModule : js::AtomicRefCounted<WasmModule> { + virtual ~WasmModule() = default; + virtual JSObject* createObject(JSContext* cx) const = 0; + virtual JSObject* createObjectForAsmJS(JSContext* cx) const = 0; +}; + +extern JS_PUBLIC_API bool IsWasmModuleObject(HandleObject obj); + +extern JS_PUBLIC_API RefPtr<WasmModule> GetWasmModule(HandleObject obj); + +} // namespace JS + +#endif /* js_WasmModule_h */ diff --git a/js/public/WeakMap.h b/js/public/WeakMap.h new file mode 100644 index 0000000000..16b81b7e3e --- /dev/null +++ b/js/public/WeakMap.h @@ -0,0 +1,34 @@ +/* -*- 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/. */ + +/* + * Weak Maps. + */ + +#ifndef js_WeakMap_h +#define js_WeakMap_h + +#include "jspubtd.h" + +namespace JS { + +extern JS_PUBLIC_API JSObject* NewWeakMapObject(JSContext* cx); + +extern JS_PUBLIC_API bool IsWeakMapObject(JSObject* obj); + +extern JS_PUBLIC_API bool GetWeakMapEntry(JSContext* cx, + JS::HandleObject mapObj, + JS::HandleObject key, + JS::MutableHandleValue val); + +extern JS_PUBLIC_API bool SetWeakMapEntry(JSContext* cx, + JS::HandleObject mapObj, + JS::HandleObject key, + JS::HandleValue val); + +} // namespace JS + +#endif // js_WeakMap_h diff --git a/js/public/WeakMapPtr.h b/js/public/WeakMapPtr.h new file mode 100644 index 0000000000..b175bb3948 --- /dev/null +++ b/js/public/WeakMapPtr.h @@ -0,0 +1,48 @@ +/* -*- 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 js_WeakMapPtr_h +#define js_WeakMapPtr_h + +#include "jstypes.h" + +#include "js/TypeDecls.h" + +class JS_PUBLIC_API JSTracer; + +namespace JS { + +// A wrapper around the internal C++ representation of SpiderMonkey WeakMaps, +// usable outside the engine. +// +// The supported template specializations are enumerated in gc/WeakMapPtr.cpp. +// If you want to use this class for a different key/value combination, add it +// to the list and the compiler will generate the relevant machinery. +template <typename K, typename V> +class JS_PUBLIC_API WeakMapPtr { + public: + WeakMapPtr() : ptr(nullptr) {} + bool init(JSContext* cx); + bool initialized() { return ptr != nullptr; } + void destroy(); + virtual ~WeakMapPtr() { MOZ_ASSERT(!initialized()); } + void trace(JSTracer* tracer); + + V lookup(const K& key); + bool put(JSContext* cx, const K& key, const V& value); + V removeValue(const K& key); + + private: + void* ptr; + + // WeakMapPtr is neither copyable nor assignable. + WeakMapPtr(const WeakMapPtr& wmp) = delete; + WeakMapPtr& operator=(const WeakMapPtr& wmp) = delete; +}; + +} /* namespace JS */ + +#endif /* js_WeakMapPtr_h */ diff --git a/js/public/Wrapper.h b/js/public/Wrapper.h new file mode 100644 index 0000000000..197d76f11f --- /dev/null +++ b/js/public/Wrapper.h @@ -0,0 +1,505 @@ +/* -*- 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 js_Wrapper_h +#define js_Wrapper_h + +#include "mozilla/Attributes.h" + +#include "js/Proxy.h" + +namespace js { +struct CompartmentFilter; + +/* + * Helper for Wrapper::New default options. + * + * Callers of Wrapper::New() who wish to specify a prototype for the created + * Wrapper, *MUST* construct a WrapperOptions with a JSContext. + */ +class MOZ_STACK_CLASS WrapperOptions : public ProxyOptions { + public: + WrapperOptions() : ProxyOptions(false), proto_() {} + + explicit WrapperOptions(JSContext* cx) : ProxyOptions(false), proto_() { + proto_.emplace(cx); + } + + inline JSObject* proto() const; + WrapperOptions& setProto(JSObject* protoArg) { + MOZ_ASSERT(proto_); + *proto_ = protoArg; + return *this; + } + + private: + mozilla::Maybe<JS::RootedObject> proto_; +}; + +// Base class for proxy handlers that want to forward all operations to an +// object stored in the proxy's private slot. +class JS_PUBLIC_API ForwardingProxyHandler : public BaseProxyHandler { + public: + using BaseProxyHandler::BaseProxyHandler; + + /* Standard internal methods. */ + virtual bool getOwnPropertyDescriptor( + JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) + const override; + virtual bool defineProperty(JSContext* cx, JS::HandleObject proxy, + JS::HandleId id, + JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult& result) const override; + virtual bool ownPropertyKeys(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props) const override; + virtual bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::ObjectOpResult& result) const override; + virtual bool enumerate(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props) const override; + virtual bool getPrototype(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleObject protop) const override; + virtual bool setPrototype(JSContext* cx, JS::HandleObject proxy, + JS::HandleObject proto, + JS::ObjectOpResult& result) const override; + virtual bool getPrototypeIfOrdinary( + JSContext* cx, JS::HandleObject proxy, bool* isOrdinary, + JS::MutableHandleObject protop) const override; + virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject proxy, + bool* succeeded) const override; + virtual bool preventExtensions(JSContext* cx, JS::HandleObject proxy, + JS::ObjectOpResult& result) const override; + virtual bool isExtensible(JSContext* cx, JS::HandleObject proxy, + bool* extensible) const override; + virtual bool has(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + bool* bp) const override; + virtual bool get(JSContext* cx, JS::HandleObject proxy, + JS::HandleValue receiver, JS::HandleId id, + JS::MutableHandleValue vp) const override; + virtual bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::HandleValue v, JS::HandleValue receiver, + JS::ObjectOpResult& result) const override; + virtual bool call(JSContext* cx, JS::HandleObject proxy, + const JS::CallArgs& args) const override; + virtual bool construct(JSContext* cx, JS::HandleObject proxy, + const JS::CallArgs& args) const override; + + /* SpiderMonkey extensions. */ + virtual bool hasOwn(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + bool* bp) const override; + virtual bool getOwnEnumerablePropertyKeys( + JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props) const override; + virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test, + JS::NativeImpl impl, + const JS::CallArgs& args) const override; + virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject proxy, + ESClass* cls) const override; + virtual bool isArray(JSContext* cx, JS::HandleObject proxy, + JS::IsArrayAnswer* answer) const override; + virtual const char* className(JSContext* cx, + JS::HandleObject proxy) const override; + virtual JSString* fun_toString(JSContext* cx, JS::HandleObject proxy, + bool isToSource) const override; + virtual RegExpShared* regexp_toShared(JSContext* cx, + JS::HandleObject proxy) const override; + virtual bool boxedValue_unbox(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleValue vp) const override; + virtual bool isCallable(JSObject* obj) const override; + virtual bool isConstructor(JSObject* obj) const override; + + // Use the target object for private fields. + virtual bool useProxyExpandoObjectForPrivateFields() const override { + return false; + } +}; + +/* + * A wrapper is a proxy with a target object to which it generally forwards + * operations, but may restrict access to certain operations or augment those + * operations in various ways. + * + * A wrapper can be "unwrapped" in C++, exposing the underlying object. + * Callers should be careful to avoid unwrapping security wrappers in the wrong + * context. + * + * Important: If you add a method implementation here, you probably also need + * to add an override in CrossCompartmentWrapper. If you don't, you risk + * compartment mismatches. See bug 945826 comment 0. + */ +class JS_PUBLIC_API Wrapper : public ForwardingProxyHandler { + unsigned mFlags; + + public: + explicit constexpr Wrapper(unsigned aFlags, bool aHasPrototype = false, + bool aHasSecurityPolicy = false) + : ForwardingProxyHandler(&family, aHasPrototype, aHasSecurityPolicy), + mFlags(aFlags) {} + + virtual bool finalizeInBackground(const JS::Value& priv) const override; + + /** + * A hook subclasses can override to implement CheckedUnwrapDynamic + * behavior. The JSContext represents the "who is trying to unwrap?" Realm. + * The JSObject is the wrapper that the caller is trying to unwrap. + */ + virtual bool dynamicCheckedUnwrapAllowed(JS::HandleObject obj, + JSContext* cx) const { + MOZ_ASSERT(hasSecurityPolicy(), "Why are you asking?"); + return false; + } + + using BaseProxyHandler::Action; + + enum Flags { CROSS_COMPARTMENT = 1 << 0, LAST_USED_FLAG = CROSS_COMPARTMENT }; + + static JSObject* New(JSContext* cx, JSObject* obj, const Wrapper* handler, + const WrapperOptions& options = WrapperOptions()); + + static JSObject* Renew(JSObject* existing, JSObject* obj, + const Wrapper* handler); + + static inline const Wrapper* wrapperHandler(const JSObject* wrapper); + + static JSObject* wrappedObject(JSObject* wrapper); + + unsigned flags() const { return mFlags; } + + bool isCrossCompartmentWrapper() const { + return !!(mFlags & CROSS_COMPARTMENT); + } + + static const char family; + static const Wrapper singleton; + static const Wrapper singletonWithPrototype; + + static JSObject* const defaultProto; +}; + +inline JSObject* WrapperOptions::proto() const { + return proto_ ? *proto_ : Wrapper::defaultProto; +} + +/* Base class for all cross compartment wrapper handlers. */ +class JS_PUBLIC_API CrossCompartmentWrapper : public Wrapper { + public: + explicit constexpr CrossCompartmentWrapper(unsigned aFlags, + bool aHasPrototype = false, + bool aHasSecurityPolicy = false) + : Wrapper(CROSS_COMPARTMENT | aFlags, aHasPrototype, aHasSecurityPolicy) { + } + + /* Standard internal methods. */ + virtual bool getOwnPropertyDescriptor( + JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) + const override; + virtual bool defineProperty(JSContext* cx, JS::HandleObject wrapper, + JS::HandleId id, + JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult& result) const override; + virtual bool ownPropertyKeys(JSContext* cx, JS::HandleObject wrapper, + JS::MutableHandleIdVector props) const override; + virtual bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + JS::ObjectOpResult& result) const override; + virtual bool enumerate(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props) const override; + virtual bool getPrototype(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleObject protop) const override; + virtual bool setPrototype(JSContext* cx, JS::HandleObject proxy, + JS::HandleObject proto, + JS::ObjectOpResult& result) const override; + + virtual bool getPrototypeIfOrdinary( + JSContext* cx, JS::HandleObject proxy, bool* isOrdinary, + JS::MutableHandleObject protop) const override; + virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject proxy, + bool* succeeded) const override; + virtual bool preventExtensions(JSContext* cx, JS::HandleObject wrapper, + JS::ObjectOpResult& result) const override; + virtual bool isExtensible(JSContext* cx, JS::HandleObject wrapper, + bool* extensible) const override; + virtual bool has(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + bool* bp) const override; + virtual bool get(JSContext* cx, JS::HandleObject wrapper, + JS::HandleValue receiver, JS::HandleId id, + JS::MutableHandleValue vp) const override; + virtual bool set(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + JS::HandleValue v, JS::HandleValue receiver, + JS::ObjectOpResult& result) const override; + virtual bool call(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args) const override; + virtual bool construct(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args) const override; + + /* SpiderMonkey extensions. */ + virtual bool hasOwn(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + bool* bp) const override; + virtual bool getOwnEnumerablePropertyKeys( + JSContext* cx, JS::HandleObject wrapper, + JS::MutableHandleIdVector props) const override; + virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test, + JS::NativeImpl impl, + const JS::CallArgs& args) const override; + virtual const char* className(JSContext* cx, + JS::HandleObject proxy) const override; + virtual JSString* fun_toString(JSContext* cx, JS::HandleObject wrapper, + bool isToSource) const override; + virtual RegExpShared* regexp_toShared(JSContext* cx, + JS::HandleObject proxy) const override; + virtual bool boxedValue_unbox(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleValue vp) const override; + + // Allocate CrossCompartmentWrappers in the nursery. + virtual bool canNurseryAllocate() const override { return true; } + + static const CrossCompartmentWrapper singleton; + static const CrossCompartmentWrapper singletonWithPrototype; +}; + +class JS_PUBLIC_API OpaqueCrossCompartmentWrapper + : public CrossCompartmentWrapper { + public: + explicit constexpr OpaqueCrossCompartmentWrapper() + : CrossCompartmentWrapper(0) {} + + /* Standard internal methods. */ + virtual bool getOwnPropertyDescriptor( + JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) + const override; + virtual bool defineProperty(JSContext* cx, JS::HandleObject wrapper, + JS::HandleId id, + JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult& result) const override; + virtual bool ownPropertyKeys(JSContext* cx, JS::HandleObject wrapper, + JS::MutableHandleIdVector props) const override; + virtual bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + JS::ObjectOpResult& result) const override; + virtual bool enumerate(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props) const override; + virtual bool getPrototype(JSContext* cx, JS::HandleObject wrapper, + JS::MutableHandleObject protop) const override; + virtual bool setPrototype(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject proto, + JS::ObjectOpResult& result) const override; + virtual bool getPrototypeIfOrdinary( + JSContext* cx, JS::HandleObject wrapper, bool* isOrdinary, + JS::MutableHandleObject protop) const override; + virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject wrapper, + bool* succeeded) const override; + virtual bool preventExtensions(JSContext* cx, JS::HandleObject wrapper, + JS::ObjectOpResult& result) const override; + virtual bool isExtensible(JSContext* cx, JS::HandleObject wrapper, + bool* extensible) const override; + virtual bool has(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + bool* bp) const override; + virtual bool get(JSContext* cx, JS::HandleObject wrapper, + JS::HandleValue receiver, JS::HandleId id, + JS::MutableHandleValue vp) const override; + virtual bool set(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + JS::HandleValue v, JS::HandleValue receiver, + JS::ObjectOpResult& result) const override; + virtual bool call(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args) const override; + virtual bool construct(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args) const override; + + /* SpiderMonkey extensions. */ + virtual bool hasOwn(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + bool* bp) const override; + virtual bool getOwnEnumerablePropertyKeys( + JSContext* cx, JS::HandleObject wrapper, + JS::MutableHandleIdVector props) const override; + virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, + ESClass* cls) const override; + virtual bool isArray(JSContext* cx, JS::HandleObject obj, + JS::IsArrayAnswer* answer) const override; + virtual const char* className(JSContext* cx, + JS::HandleObject wrapper) const override; + virtual JSString* fun_toString(JSContext* cx, JS::HandleObject proxy, + bool isToSource) const override; + + static const OpaqueCrossCompartmentWrapper singleton; +}; + +/* + * Base class for security wrappers. A security wrapper is potentially hiding + * all or part of some wrapped object thus SecurityWrapper defaults to denying + * access to the wrappee. This is the opposite of Wrapper which tries to be + * completely transparent. + * + * NB: Currently, only a few ProxyHandler operations are overridden to deny + * access, relying on derived SecurityWrapper to block access when necessary. + */ +template <class Base> +class JS_PUBLIC_API SecurityWrapper : public Base { + public: + explicit constexpr SecurityWrapper(unsigned flags, bool hasPrototype = false) + : Base(flags, hasPrototype, /* hasSecurityPolicy = */ true) {} + + virtual bool enter(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + Wrapper::Action act, bool mayThrow, + bool* bp) const override; + + virtual bool defineProperty(JSContext* cx, JS::HandleObject wrapper, + JS::HandleId id, + JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult& result) const override; + virtual bool isExtensible(JSContext* cx, JS::HandleObject wrapper, + bool* extensible) const override; + virtual bool preventExtensions(JSContext* cx, JS::HandleObject wrapper, + JS::ObjectOpResult& result) const override; + virtual bool setPrototype(JSContext* cx, JS::HandleObject proxy, + JS::HandleObject proto, + JS::ObjectOpResult& result) const override; + virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject proxy, + bool* succeeded) const override; + + virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test, + JS::NativeImpl impl, + const JS::CallArgs& args) const override; + virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, + ESClass* cls) const override; + virtual bool isArray(JSContext* cx, JS::HandleObject wrapper, + JS::IsArrayAnswer* answer) const override; + virtual RegExpShared* regexp_toShared(JSContext* cx, + JS::HandleObject proxy) const override; + virtual bool boxedValue_unbox(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleValue vp) const override; + + // Allow isCallable and isConstructor. They used to be class-level, and so + // could not be guarded against. + + /* + * Allow our subclasses to select the superclass behavior they want without + * needing to specify an exact superclass. + */ + typedef Base Permissive; + typedef SecurityWrapper<Base> Restrictive; +}; + +typedef SecurityWrapper<CrossCompartmentWrapper> + CrossCompartmentSecurityWrapper; + +extern JSObject* TransparentObjectWrapper(JSContext* cx, + JS::HandleObject existing, + JS::HandleObject obj); + +inline bool IsWrapper(const JSObject* obj) { + return IsProxy(obj) && GetProxyHandler(obj)->family() == &Wrapper::family; +} + +inline bool IsCrossCompartmentWrapper(const JSObject* obj) { + return IsWrapper(obj) && + (Wrapper::wrapperHandler(obj)->flags() & Wrapper::CROSS_COMPARTMENT); +} + +/* static */ inline const Wrapper* Wrapper::wrapperHandler( + const JSObject* wrapper) { + MOZ_ASSERT(IsWrapper(wrapper)); + return static_cast<const Wrapper*>(GetProxyHandler(wrapper)); +} + +// Given a JSObject, returns that object stripped of wrappers. If +// stopAtWindowProxy is true, then this returns the WindowProxy if it was +// previously wrapped. Otherwise, this returns the first object for which +// JSObject::isWrapper returns false. +// +// ExposeToActiveJS is called on wrapper targets to allow gray marking +// assertions to work while an incremental GC is in progress, but this means +// that this cannot be called from the GC or off the main thread. +JS_PUBLIC_API JSObject* UncheckedUnwrap(JSObject* obj, + bool stopAtWindowProxy = true, + unsigned* flagsp = nullptr); + +// Given a JSObject, returns that object stripped of wrappers, except +// WindowProxy wrappers. At each stage, the wrapper has the opportunity to veto +// the unwrap. Null is returned if there are security wrappers that can't be +// unwrapped. +// +// This does a static-only unwrap check: it basically checks whether _all_ +// globals in the wrapper's source compartment should be able to access the +// wrapper target. This won't necessarily return the right thing for the HTML +// spec's cross-origin objects (WindowProxy and Location), but is fine to use +// when failure to unwrap one of those objects wouldn't be a problem. For +// example, if you want to test whether your target object is a specific class +// that's not WindowProxy or Location, you can use this. +// +// ExposeToActiveJS is called on wrapper targets to allow gray marking +// assertions to work while an incremental GC is in progress, but this means +// that this cannot be called from the GC or off the main thread. +JS_PUBLIC_API JSObject* CheckedUnwrapStatic(JSObject* obj); + +// Unwrap only the outermost security wrapper, with the same semantics as +// above. This is the checked version of Wrapper::wrappedObject. +JS_PUBLIC_API JSObject* UnwrapOneCheckedStatic(JSObject* obj); + +// Given a JSObject, returns that object stripped of wrappers. At each stage, +// the security wrapper has the opportunity to veto the unwrap. If +// stopAtWindowProxy is true, then this returns the WindowProxy if it was +// previously wrapped. Null is returned if there are security wrappers that +// can't be unwrapped. +// +// ExposeToActiveJS is called on wrapper targets to allow gray marking +// assertions to work while an incremental GC is in progress, but this means +// that this cannot be called from the GC or off the main thread. +// +// The JSContext argument will be used for dynamic checks (needed by WindowProxy +// and Location) and should represent the Realm doing the unwrapping. It is not +// used to throw exceptions; this function never throws. +// +// This function may be able to GC (and the static analysis definitely thinks it +// can), but it still takes a JSObject* argument, because some of its callers +// would actually have a bit of a hard time producing a Rooted. And it ends up +// having to root internally anyway, because it wants to use the value in a loop +// and you can't assign to a HandleObject. What this means is that callers who +// plan to use the argument object after they have called this function will +// need to root it to avoid hazard failures, even though this function doesn't +// require a Handle. +JS_PUBLIC_API JSObject* CheckedUnwrapDynamic(JSObject* obj, JSContext* cx, + bool stopAtWindowProxy = true); + +// Unwrap only the outermost security wrapper, with the same semantics as +// above. This is the checked version of Wrapper::wrappedObject. +JS_PUBLIC_API JSObject* UnwrapOneCheckedDynamic(JS::HandleObject obj, + JSContext* cx, + bool stopAtWindowProxy = true); + +// Given a JSObject, returns that object stripped of wrappers. This returns the +// WindowProxy if it was previously wrapped. +// +// ExposeToActiveJS is not called on wrapper targets so this can be called from +// the GC or off the main thread. +JS_PUBLIC_API JSObject* UncheckedUnwrapWithoutExpose(JSObject* obj); + +void ReportAccessDenied(JSContext* cx); + +JS_PUBLIC_API void NukeCrossCompartmentWrapper(JSContext* cx, + JSObject* wrapper); + +// If a cross-compartment wrapper source => target exists, nuke it. +JS_PUBLIC_API void NukeCrossCompartmentWrapperIfExists(JSContext* cx, + JS::Compartment* source, + JSObject* target); + +void RemapWrapper(JSContext* cx, JSObject* wobj, JSObject* newTarget); +void RemapDeadWrapper(JSContext* cx, JS::HandleObject wobj, + JS::HandleObject newTarget); + +JS_PUBLIC_API bool RemapAllWrappersForObject(JSContext* cx, + JS::HandleObject oldTarget, + JS::HandleObject newTarget); + +// API to recompute all cross-compartment wrappers whose source and target +// match the given filters. +JS_PUBLIC_API bool RecomputeWrappers(JSContext* cx, + const CompartmentFilter& sourceFilter, + const CompartmentFilter& targetFilter); + +} /* namespace js */ + +#endif /* js_Wrapper_h */ diff --git a/js/public/WrapperCallbacks.h b/js/public/WrapperCallbacks.h new file mode 100644 index 0000000000..24f5aa7272 --- /dev/null +++ b/js/public/WrapperCallbacks.h @@ -0,0 +1,40 @@ +/* -*- 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 js_WrapperCallbacks_h +#define js_WrapperCallbacks_h + +#include "js/TypeDecls.h" + +/** + * Callback used to ask the embedding for the cross compartment wrapper handler + * that implements the desired prolicy for this kind of object in the + * destination compartment. |obj| is the object to be wrapped. If |existing| is + * non-nullptr, it will point to an existing wrapper object that should be + * re-used if possible. |existing| is guaranteed to be a cross-compartment + * wrapper with a lazily-defined prototype and the correct global. It is + * guaranteed not to wrap a function. + */ +using JSWrapObjectCallback = JSObject* (*)(JSContext*, JS::HandleObject, + JS::HandleObject); + +/** + * Callback used by the wrap hook to ask the embedding to prepare an object + * for wrapping in a context. This might include unwrapping other wrappers + * or even finding a more suitable object for the new compartment. If |origObj| + * is non-null, then it is the original object we are going to swap into during + * a transplant. + */ +using JSPreWrapCallback = void (*)(JSContext*, JS::HandleObject, + JS::HandleObject, JS::HandleObject, + JS::HandleObject, JS::MutableHandleObject); + +struct JSWrapObjectCallbacks { + JSWrapObjectCallback wrap; + JSPreWrapCallback preWrap; +}; + +#endif // js_WrapperCallbacks_h diff --git a/js/public/Zone.h b/js/public/Zone.h new file mode 100644 index 0000000000..452d7dec5e --- /dev/null +++ b/js/public/Zone.h @@ -0,0 +1,86 @@ +/* -*- 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/. */ + +/* JavaScript API. */ + +#ifndef js_Zone_h +#define js_Zone_h + +#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API +#include "js/RootingAPI.h" // JS::Handle +#include "js/TypeDecls.h" // JSContext, JSObject, jsid, JS::Compartment, JS::GCContext, JS::Value, JS::Zone + +// [SMDOC] Nested GC Data Structures (Compartments and Zones) +// +// The GC has two nested data structures, Zones and Compartents. Each has +// distint responsibilities. Zones contain compartments, along with other GC +// resources used by a tab. Compartments contain realms, which are specification +// defined. +// +// See also the SMDoc on Realms. +// +// Compartment +// ----------- +// Security membrane; when an object from compartment A is used in compartment +// B, a cross-compartment wrapper (a kind of proxy) is used. In the browser, +// same-origin realms can share a compartment. +// +// Zone +// ---- +// A Zone is a group of compartments that share GC resources (arenas, strings, +// etc) for memory usage and performance reasons. Zone is the GC unit: the GC +// can operate on one or more zones at a time. The browser uses roughly one zone +// per tab. + +using JSDestroyZoneCallback = void (*)(JS::GCContext*, JS::Zone*); + +using JSDestroyCompartmentCallback = void (*)(JS::GCContext*, JS::Compartment*); + +using JSSizeOfIncludingThisCompartmentCallback = + size_t (*)(mozilla::MallocSizeOf, JS::Compartment*); + +extern JS_PUBLIC_API void JS_SetDestroyZoneCallback( + JSContext* cx, JSDestroyZoneCallback callback); + +extern JS_PUBLIC_API void JS_SetDestroyCompartmentCallback( + JSContext* cx, JSDestroyCompartmentCallback callback); + +extern JS_PUBLIC_API void JS_SetSizeOfIncludingThisCompartmentCallback( + JSContext* cx, JSSizeOfIncludingThisCompartmentCallback callback); + +extern JS_PUBLIC_API void JS_SetCompartmentPrivate(JS::Compartment* compartment, + void* data); + +extern JS_PUBLIC_API void* JS_GetCompartmentPrivate( + JS::Compartment* compartment); + +extern JS_PUBLIC_API void JS_SetZoneUserData(JS::Zone* zone, void* data); + +extern JS_PUBLIC_API void* JS_GetZoneUserData(JS::Zone* zone); + +extern JS_PUBLIC_API bool JS_RefreshCrossCompartmentWrappers( + JSContext* cx, JS::Handle<JSObject*> obj); + +/** + * Mark a jsid after entering a new compartment. Different zones separately + * mark the ids in a runtime, and this must be used any time an id is obtained + * from one compartment and then used in another compartment, unless the two + * compartments are guaranteed to be in the same zone. + */ +extern JS_PUBLIC_API void JS_MarkCrossZoneId(JSContext* cx, jsid id); + +/** + * If value stores a jsid (an atomized string or symbol), mark that id as for + * JS_MarkCrossZoneId. + */ +extern JS_PUBLIC_API void JS_MarkCrossZoneIdValue(JSContext* cx, + const JS::Value& value); + +#endif // js_Zone_h diff --git a/js/public/experimental/CTypes.h b/js/public/experimental/CTypes.h new file mode 100644 index 0000000000..bcd3b28775 --- /dev/null +++ b/js/public/experimental/CTypes.h @@ -0,0 +1,105 @@ +/* -*- 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 js_experimental_CTypes_h +#define js_experimental_CTypes_h + +#include "mozilla/Attributes.h" // MOZ_RAII + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/TypeDecls.h" + +namespace JS { + +#ifdef JS_HAS_CTYPES + +/** + * Initialize the 'ctypes' object on a global variable 'obj'. The 'ctypes' + * object will be sealed. + */ +extern JS_PUBLIC_API bool InitCTypesClass(JSContext* cx, + Handle<JSObject*> global); + +#endif // JS_HAS_CTYPES + +/** + * The type of ctypes activity that is occurring. + */ +enum class CTypesActivityType { + BeginCall, + EndCall, + BeginCallback, + EndCallback, +}; + +/** + * The signature of a function invoked at the leading or trailing edge of ctypes + * activity. + */ +using CTypesActivityCallback = void (*)(JSContext*, CTypesActivityType); + +/** + * Sets a callback that is run whenever js-ctypes is about to be used when + * calling into C. + */ +extern JS_PUBLIC_API void SetCTypesActivityCallback(JSContext* cx, + CTypesActivityCallback cb); + +class MOZ_RAII JS_PUBLIC_API AutoCTypesActivityCallback { + private: + JSContext* cx; + CTypesActivityCallback callback; + CTypesActivityType endType; + + public: + AutoCTypesActivityCallback(JSContext* cx, CTypesActivityType beginType, + CTypesActivityType endType); + + ~AutoCTypesActivityCallback() { DoEndCallback(); } + + void DoEndCallback() { + if (callback) { + callback(cx, endType); + callback = nullptr; + } + } +}; + +#ifdef JS_HAS_CTYPES + +/** + * Convert a unicode string 'source' of length 'slen' to the platform native + * charset, returning a null-terminated string allocated with JS_malloc. On + * failure, this function should report an error. + */ +using CTypesUnicodeToNativeFun = char* (*)(JSContext*, const char16_t*, size_t); + +/** + * Set of function pointers that ctypes can use for various internal functions. + * See JS::SetCTypesCallbacks below. Providing nullptr for a function is safe + * and will result in the applicable ctypes functionality not being available. + */ +struct CTypesCallbacks { + CTypesUnicodeToNativeFun unicodeToNative; +}; + +/** + * Set the callbacks on the provided 'ctypesObj' object. 'callbacks' should be a + * pointer to static data that exists for the lifetime of 'ctypesObj', but it + * may safely be altered after calling this function and without having + * to call this function again. + */ +extern JS_PUBLIC_API void SetCTypesCallbacks(JSObject* ctypesObj, + const CTypesCallbacks* callbacks); + +#endif // JS_HAS_CTYPES + +} // namespace JS + +#endif // js_experimental_CTypes_h diff --git a/js/public/experimental/CodeCoverage.h b/js/public/experimental/CodeCoverage.h new file mode 100644 index 0000000000..32729c792d --- /dev/null +++ b/js/public/experimental/CodeCoverage.h @@ -0,0 +1,40 @@ +/* -*- 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 js_experimental_CodeCoverage_h +#define js_experimental_CodeCoverage_h + +#include "jstypes.h" // JS_PUBLIC_API +#include "js/Utility.h" // JS::UniqueChars + +struct JS_PUBLIC_API JSContext; + +namespace js { + +/** + * Enable the collection of lcov code coverage metrics. + * Must be called before a runtime is created and before any calls to + * GetCodeCoverageSummary. + */ +extern JS_PUBLIC_API void EnableCodeCoverage(); + +/** + * Generate lcov trace file content for the current realm, and allocate a new + * buffer and return the content in it, the size of the newly allocated content + * within the buffer would be set to the length out-param. The 'All' variant + * will collect data for all realms in the runtime. + * + * In case of out-of-memory, this function returns nullptr. The length + * out-param is undefined on failure. + */ +extern JS_PUBLIC_API JS::UniqueChars GetCodeCoverageSummary(JSContext* cx, + size_t* length); +extern JS_PUBLIC_API JS::UniqueChars GetCodeCoverageSummaryAll(JSContext* cx, + size_t* length); + +} // namespace js + +#endif // js_experimental_CodeCoverage_h diff --git a/js/public/experimental/CompileScript.h b/js/public/experimental/CompileScript.h new file mode 100644 index 0000000000..723ce6a05d --- /dev/null +++ b/js/public/experimental/CompileScript.h @@ -0,0 +1,120 @@ +/* -*- 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/. */ + +/* JavaScript API for compiling scripts to stencil without depending on + * JSContext. */ + +#ifndef js_experimental_CompileScript_h +#define js_experimental_CompileScript_h + +#include "jspubtd.h" +#include "js/experimental/JSStencil.h" +#include "js/GCAnnotations.h" +#include "js/Modules.h" +#include "js/Stack.h" +#include "js/UniquePtr.h" + +namespace js { +class FrontendContext; +namespace frontend { +struct CompilationInput; +} // namespace frontend +} // namespace js + +namespace JS { +using FrontendContext = js::FrontendContext; + +// Create a new front-end context. +JS_PUBLIC_API JS::FrontendContext* NewFrontendContext(); + +// Destroy a front-end context allocated with NewFrontendContext. +JS_PUBLIC_API void DestroyFrontendContext(JS::FrontendContext* fc); + +JS_PUBLIC_API void SetNativeStackQuota(JS::FrontendContext* fc, + JS::NativeStackSize stackSize); + +/* + * Set supported import assertions on a FrontendContext to be used with + * CompileModuleScriptToStencil. May only be set once for each FrontendContext. + * The default list of supported import assertions is empty. + */ +JS_PUBLIC_API bool SetSupportedImportAssertions( + JS::FrontendContext* fc, + const JS::ImportAssertionVector& supportedImportAssertions); + +// Temporary storage used during compiling and preparing to instantiate a +// Stencil. +// +// Off-thread consumers can allocate this instance off main thread, and pass it +// back to the main thread, in order to reduce the main thread allocation. +struct CompilationStorage { + private: + // Owned CompilationInput. + // + // This uses raw pointer instead of UniquePtr because CompilationInput + // is opaque. + JS_HAZ_NON_GC_POINTER js::frontend::CompilationInput* input_ = nullptr; + bool isBorrowed_ = false; + + public: + CompilationStorage() = default; + explicit CompilationStorage(js::frontend::CompilationInput* input) + : input_(input), isBorrowed_(true) {} + CompilationStorage(CompilationStorage&& other) + : input_(other.input_), isBorrowed_(other.isBorrowed_) { + other.input_ = nullptr; + } + + ~CompilationStorage(); + + private: + CompilationStorage(const CompilationStorage& other) = delete; + void operator=(const CompilationStorage& aOther) = delete; + + public: + bool hasInput() { return !!input_; } + + // Internal function that initializes the CompilationInput. It should only be + // called once. + bool allocateInput(FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options); + + js::frontend::CompilationInput& getInput() { + MOZ_ASSERT(hasInput()); + return *input_; + } + + // Size of dynamic data. Note that GC data is counted by GC and not here. + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + void trace(JSTracer* trc); +}; + +extern JS_PUBLIC_API already_AddRefed<JS::Stencil> CompileGlobalScriptToStencil( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<mozilla::Utf8Unit>& srcBuf, + JS::CompilationStorage& compileStorage); + +extern JS_PUBLIC_API already_AddRefed<JS::Stencil> CompileGlobalScriptToStencil( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, JS::CompilationStorage& compileStorage); + +extern JS_PUBLIC_API already_AddRefed<JS::Stencil> CompileModuleScriptToStencil( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<mozilla::Utf8Unit>& srcBuf, + JS::CompilationStorage& compileStorage); + +extern JS_PUBLIC_API already_AddRefed<JS::Stencil> CompileModuleScriptToStencil( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, JS::CompilationStorage& compileStorage); + +extern JS_PUBLIC_API bool PrepareForInstantiate( + JS::FrontendContext* fc, JS::CompilationStorage& compileStorage, + JS::Stencil& stencil, JS::InstantiationStorage& storage); + +} // namespace JS + +#endif // js_experimental_CompileScript_h diff --git a/js/public/experimental/Intl.h b/js/public/experimental/Intl.h new file mode 100644 index 0000000000..a3d1c5c171 --- /dev/null +++ b/js/public/experimental/Intl.h @@ -0,0 +1,50 @@ +/* -*- 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 js_experimental_Intl_h +#define js_experimental_Intl_h + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/TypeDecls.h" + +namespace JS { + +/** + * Create and add the Intl.MozDateTimeFormat constructor function to the + * provided object. + * + * This custom date/time formatter constructor gives users the ability to + * specify a custom format pattern. This pattern is passed *directly* to ICU + * with NO SYNTAX PARSING OR VALIDATION WHATSOEVER. ICU appears to have a + * modicum of testing of this, and it won't fall over completely if passed bad + * input. But the current behavior is entirely under-specified and emphatically + * not shippable on the web, and it *must* be fixed before this functionality + * can be exposed in the real world. (There are also some questions about + * whether the format exposed here is the *right* one to standardize, that will + * also need to be resolved to ship this.) + * + * If JS was built without JS_HAS_INTL_API, this function will throw an + * exception. + */ +extern JS_PUBLIC_API bool AddMozDateTimeFormatConstructor( + JSContext* cx, Handle<JSObject*> intl); + +/** + * Create and add the Intl.MozDisplayNames constructor function to the + * provided object. This constructor acts like the standard |Intl.DisplayNames| + * but accepts certain additional syntax that isn't standardized to the point of + * being shippable. + * + * If JS was built without JS_HAS_INTL_API, this function will throw an + * exception. + */ +extern JS_PUBLIC_API bool AddMozDisplayNamesConstructor(JSContext* cx, + Handle<JSObject*> intl); + +} // namespace JS + +#endif // js_experimental_Intl_h diff --git a/js/public/experimental/JSStencil.h b/js/public/experimental/JSStencil.h new file mode 100644 index 0000000000..4d3803670e --- /dev/null +++ b/js/public/experimental/JSStencil.h @@ -0,0 +1,302 @@ +/* -*- 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 js_experimental_JSStencil_h +#define js_experimental_JSStencil_h + +/* The `JS::Stencil` type holds the output of the JS Parser before it is + * allocated on the GC heap as a `JSScript`. This form may be serialized as + * part of building a bytecode cache. This `Stencil` is not associated with any + * particular Realm and may be generated off-main-thread, making it useful for + * building script loaders. + */ + +#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf +#include "mozilla/RefPtr.h" // RefPtr, already_AddRefed +#include "mozilla/Utf8.h" // mozilla::Utf8Unit +#include "mozilla/Vector.h" // mozilla::Vector + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions, JS::InstantiateOptions, JS::DecodeOptions +#include "js/OffThreadScriptCompilation.h" // JS::OffThreadCompileCallback +#include "js/SourceText.h" // JS::SourceText +#include "js/Transcoding.h" // JS::TranscodeSources, JS::TranscodeBuffer, JS::TranscodeRange + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSTracer; + +// Underlying opaque type. +namespace js { +struct ParseTask; +class FrontendContext; +namespace frontend { +struct CompilationStencil; +struct CompilationGCOutput; +struct CompilationInput; +} // namespace frontend +} // namespace js + +// ************************************************************************ +// Types +// ************************************************************************ + +namespace JS { + +struct CompilationStorage; + +using Stencil = js::frontend::CompilationStencil; +using FrontendContext = js::FrontendContext; + +// Temporary storage used during instantiating Stencil. +// +// Off-thread APIs can allocate this instance off main thread, and pass it back +// to the main thread, in order to reduce the main thread allocation. +struct InstantiationStorage { + private: + // Owned CompilationGCOutput. + // + // This uses raw pointer instead of UniquePtr because CompilationGCOutput + // is opaque. + js::frontend::CompilationGCOutput* gcOutput_ = nullptr; + + friend JS_PUBLIC_API JSScript* InstantiateGlobalStencil( + JSContext* cx, const InstantiateOptions& options, Stencil* stencil, + InstantiationStorage* storage); + + friend JS_PUBLIC_API JSObject* InstantiateModuleStencil( + JSContext* cx, const InstantiateOptions& options, Stencil* stencil, + InstantiationStorage* storage); + + friend JS_PUBLIC_API bool PrepareForInstantiate( + JS::FrontendContext* fc, JS::CompilationStorage& compileStorage, + JS::Stencil& stencil, JS::InstantiationStorage& storage); + + friend struct js::ParseTask; + + public: + InstantiationStorage() = default; + InstantiationStorage(InstantiationStorage&& other) + : gcOutput_(other.gcOutput_) { + other.gcOutput_ = nullptr; + } + + ~InstantiationStorage(); + + private: + InstantiationStorage(const InstantiationStorage& other) = delete; + void operator=(const InstantiationStorage& aOther) = delete; + + public: + bool isValid() const { return !!gcOutput_; } + + void trace(JSTracer* trc); +}; + +} // namespace JS + +// ************************************************************************ +// Reference Count +// ************************************************************************ + +namespace JS { + +// These non-member functions let us manipulate the ref counts of the opaque +// Stencil type. The RefPtrTraits below calls these for use when using the +// RefPtr type. +JS_PUBLIC_API void StencilAddRef(Stencil* stencil); +JS_PUBLIC_API void StencilRelease(Stencil* stencil); + +} // namespace JS + +namespace mozilla { +template <> +struct RefPtrTraits<JS::Stencil> { + static void AddRef(JS::Stencil* stencil) { JS::StencilAddRef(stencil); } + static void Release(JS::Stencil* stencil) { JS::StencilRelease(stencil); } +}; +} // namespace mozilla + +// ************************************************************************ +// Properties +// ************************************************************************ + +namespace JS { + +// Return true if the stencil relies on external data as a result of XDR +// decoding. +extern JS_PUBLIC_API bool StencilIsBorrowed(Stencil* stencil); + +extern JS_PUBLIC_API size_t SizeOfStencil(Stencil* stencil, + mozilla::MallocSizeOf mallocSizeOf); + +} // namespace JS + +// ************************************************************************ +// Compilation +// ************************************************************************ + +namespace JS { + +// Compile the source text into a JS::Stencil using the provided options. The +// resulting stencil may be instantiated into any Realm on the current runtime +// and may be used multiple times. +// +// NOTE: On error, a null will be returned and an exception will be set on the +// JSContext. +extern JS_PUBLIC_API already_AddRefed<Stencil> CompileGlobalScriptToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<mozilla::Utf8Unit>& srcBuf); +extern JS_PUBLIC_API already_AddRefed<Stencil> CompileGlobalScriptToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf); + +// Compile the source text into a JS::Stencil using "module" parse goal. The +// ECMAScript spec defines special semantics so we use a seperate entry point +// here for clarity. The result is still a JS::Stencil, but should use the +// appropriate instantiate API below. +extern JS_PUBLIC_API already_AddRefed<Stencil> CompileModuleScriptToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<mozilla::Utf8Unit>& srcBuf); +extern JS_PUBLIC_API already_AddRefed<Stencil> CompileModuleScriptToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf); + +} // namespace JS + +// ************************************************************************ +// Instantiation +// ************************************************************************ + +namespace JS { + +// Instantiate the Stencil into current Realm and return the JSScript. +extern JS_PUBLIC_API JSScript* InstantiateGlobalStencil( + JSContext* cx, const InstantiateOptions& options, Stencil* stencil, + InstantiationStorage* storage = nullptr); + +// Instantiate a module Stencil and return the associated object. Inside the +// engine this is a js::ModuleObject. +extern JS_PUBLIC_API JSObject* InstantiateModuleStencil( + JSContext* cx, const InstantiateOptions& options, Stencil* stencil, + InstantiationStorage* storage = nullptr); + +} // namespace JS + +// ************************************************************************ +// Transcoding +// ************************************************************************ + +namespace JS { + +class OffThreadToken; + +// Serialize the Stencil into the transcode buffer. +extern JS_PUBLIC_API TranscodeResult EncodeStencil(JSContext* cx, + Stencil* stencil, + TranscodeBuffer& buffer); + +// Deserialize data and create a new Stencil. +extern JS_PUBLIC_API TranscodeResult DecodeStencil(JSContext* cx, + const DecodeOptions& options, + const TranscodeRange& range, + Stencil** stencilOut); +extern JS_PUBLIC_API TranscodeResult DecodeStencil(JS::FrontendContext* fc, + const DecodeOptions& options, + const TranscodeRange& range, + Stencil** stencilOut); + +// Register an encoder on its script source, such that all functions can be +// encoded as they are delazified. +extern JS_PUBLIC_API bool StartIncrementalEncoding(JSContext* cx, + RefPtr<Stencil>&& stencil); + +} // namespace JS + +// ************************************************************************ +// Off-thread compilation/transcoding +// ************************************************************************ + +namespace JS { + +// Start an off-thread task to compile the source text into a JS::Stencil, +// using the provided options. +extern JS_PUBLIC_API OffThreadToken* CompileToStencilOffThread( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf, OffThreadCompileCallback callback, + void* callbackData); + +extern JS_PUBLIC_API OffThreadToken* CompileToStencilOffThread( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<mozilla::Utf8Unit>& srcBuf, OffThreadCompileCallback callback, + void* callbackData); + +// Start an off-thread task to compile the module source text into a +// JS::Stencil, using the provided options. +extern JS_PUBLIC_API OffThreadToken* CompileModuleToStencilOffThread( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf, OffThreadCompileCallback callback, + void* callbackData); + +extern JS_PUBLIC_API OffThreadToken* CompileModuleToStencilOffThread( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<mozilla::Utf8Unit>& srcBuf, OffThreadCompileCallback callback, + void* callbackData); + +// Start an off-thread task to decode stencil. +// +// The start of `buffer` and `cursor` should meet +// IsTranscodingBytecodeAligned and IsTranscodingBytecodeOffsetAligned. +// (This should be handled while encoding). +// +// `buffer` should be alive until the end of `FinishDecodeStencilOffThread`. +extern JS_PUBLIC_API OffThreadToken* DecodeStencilOffThread( + JSContext* cx, const DecodeOptions& options, const TranscodeBuffer& buffer, + size_t cursor, OffThreadCompileCallback callback, void* callbackData); + +// The start of `range` should meet IsTranscodingBytecodeAligned and +// AlignTranscodingBytecodeOffset. +// (This should be handled while encoding). +// +// `range` should be alive until the end of `FinishDecodeStencilOffThread`. +extern JS_PUBLIC_API OffThreadToken* DecodeStencilOffThread( + JSContext* cx, const DecodeOptions& options, const TranscodeRange& range, + OffThreadCompileCallback callback, void* callbackData); + +// Start an off-thread task to decode multiple stencils. +// +// The start of `TranscodeSource.range` in `sources` should meet +// IsTranscodingBytecodeAligned and AlignTranscodingBytecodeOffset +// +// `sources` should be alive until the end of +// `FinishDecodeMultiStencilsOffThread`. +extern JS_PUBLIC_API OffThreadToken* DecodeMultiStencilsOffThread( + JSContext* cx, const DecodeOptions& options, TranscodeSources& sources, + OffThreadCompileCallback callback, void* callbackData); + +// Finish the off-thread task to compile the source text into a JS::Stencil, +// started by JS::CompileToStencilOffThread, and return the result JS::Stencil. +// +// If `options.allocateInstantiationStorage` was true in +// JS::CompileToStencilOffThread, pre-allocated JS::InstantiationStorage +// is returned as `storage` out parameter. +extern JS_PUBLIC_API already_AddRefed<Stencil> FinishOffThreadStencil( + JSContext* cx, OffThreadToken* token, + InstantiationStorage* storage = nullptr); + +extern JS_PUBLIC_API bool FinishDecodeMultiStencilsOffThread( + JSContext* cx, OffThreadToken* token, + mozilla::Vector<RefPtr<Stencil>>* stencils); + +// Cancel the off-thread task to compile/decode. +extern JS_PUBLIC_API void CancelOffThreadToken(JSContext* cx, + OffThreadToken* token); + +} // namespace JS + +#endif // js_experimental_JSStencil_h diff --git a/js/public/experimental/JitInfo.h b/js/public/experimental/JitInfo.h new file mode 100644 index 0000000000..1b070021bd --- /dev/null +++ b/js/public/experimental/JitInfo.h @@ -0,0 +1,336 @@ +/* -*- 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 js_experimental_JitInfo_h +#define js_experimental_JitInfo_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include <stddef.h> // size_t +#include <stdint.h> // uint16_t, uint32_t + +#include "js/CallArgs.h" // JS::CallArgs, JS::detail::CallArgsBase, JSNative +#include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted +#include "js/Value.h" // JS::Value, JSValueType + +namespace js { + +namespace jit { + +enum class InlinableNative : uint16_t; + +} // namespace jit + +} // namespace js + +/** + * A class, expected to be passed by value, which represents the CallArgs for a + * JSJitGetterOp. + */ +class JSJitGetterCallArgs : protected JS::MutableHandle<JS::Value> { + public: + explicit JSJitGetterCallArgs(const JS::CallArgs& args) + : JS::MutableHandle<JS::Value>(args.rval()) {} + + explicit JSJitGetterCallArgs(JS::Rooted<JS::Value>* rooted) + : JS::MutableHandle<JS::Value>(rooted) {} + + explicit JSJitGetterCallArgs(JS::MutableHandle<JS::Value> handle) + : JS::MutableHandle<JS::Value>(handle) {} + + JS::MutableHandle<JS::Value> rval() { return *this; } +}; + +/** + * A class, expected to be passed by value, which represents the CallArgs for a + * JSJitSetterOp. + */ +class JSJitSetterCallArgs : protected JS::MutableHandle<JS::Value> { + public: + explicit JSJitSetterCallArgs(const JS::CallArgs& args) + : JS::MutableHandle<JS::Value>(args[0]) {} + + explicit JSJitSetterCallArgs(JS::Rooted<JS::Value>* rooted) + : JS::MutableHandle<JS::Value>(rooted) {} + + JS::MutableHandle<JS::Value> operator[](unsigned i) { + MOZ_ASSERT(i == 0); + return *this; + } + + unsigned length() const { return 1; } + + // Add get() or maybe hasDefined() as needed +}; + +struct JSJitMethodCallArgsTraits; + +/** + * A class, expected to be passed by reference, which represents the CallArgs + * for a JSJitMethodOp. + */ +class JSJitMethodCallArgs + : protected JS::detail::CallArgsBase<JS::detail::NoUsedRval> { + private: + using Base = JS::detail::CallArgsBase<JS::detail::NoUsedRval>; + friend struct JSJitMethodCallArgsTraits; + + public: + explicit JSJitMethodCallArgs(const JS::CallArgs& args) { + argv_ = args.array(); + argc_ = args.length(); + } + + JS::MutableHandle<JS::Value> rval() const { return Base::rval(); } + + unsigned length() const { return Base::length(); } + + JS::MutableHandle<JS::Value> operator[](unsigned i) const { + return Base::operator[](i); + } + + bool hasDefined(unsigned i) const { return Base::hasDefined(i); } + + JSObject& callee() const { + // We can't use Base::callee() because that will try to poke at + // this->usedRval_, which we don't have. + return argv_[-2].toObject(); + } + + JS::Handle<JS::Value> get(unsigned i) const { return Base::get(i); } + + bool requireAtLeast(JSContext* cx, const char* fnname, + unsigned required) const { + // Can just forward to Base, since it only needs the length and we + // forward that already. + return Base::requireAtLeast(cx, fnname, required); + } +}; + +struct JSJitMethodCallArgsTraits { + static constexpr size_t offsetOfArgv = offsetof(JSJitMethodCallArgs, argv_); + static constexpr size_t offsetOfArgc = offsetof(JSJitMethodCallArgs, argc_); +}; + +using JSJitGetterOp = bool (*)(JSContext*, JS::Handle<JSObject*>, void*, + JSJitGetterCallArgs); +using JSJitSetterOp = bool (*)(JSContext*, JS::Handle<JSObject*>, void*, + JSJitSetterCallArgs); +using JSJitMethodOp = bool (*)(JSContext*, JS::Handle<JSObject*>, void*, + const JSJitMethodCallArgs&); + +/** + * This struct contains metadata passed from the DOM to the JS Engine for JIT + * optimizations on DOM property accessors. + * + * Eventually, this should be made available to general JSAPI users as *not* + * experimental and *not* a friend API, but we're not ready to do so yet. + */ +class JSJitInfo { + public: + enum OpType { + Getter, + Setter, + Method, + StaticMethod, + InlinableNative, + IgnoresReturnValueNative, + // Must be last + OpTypeCount + }; + + enum ArgType { + // Basic types + String = (1 << 0), + Integer = (1 << 1), // Only 32-bit or less + Double = (1 << 2), // Maybe we want to add Float sometime too + Boolean = (1 << 3), + Object = (1 << 4), + Null = (1 << 5), + + // And derived types + Numeric = Integer | Double, + // Should "Primitive" use the WebIDL definition, which + // excludes string and null, or the typical JS one that includes them? + Primitive = Numeric | Boolean | Null | String, + ObjectOrNull = Object | Null, + Any = ObjectOrNull | Primitive, + + // Our sentinel value. + ArgTypeListEnd = (1 << 31) + }; + + static_assert(Any & String, "Any must include String"); + static_assert(Any & Integer, "Any must include Integer"); + static_assert(Any & Double, "Any must include Double"); + static_assert(Any & Boolean, "Any must include Boolean"); + static_assert(Any & Object, "Any must include Object"); + static_assert(Any & Null, "Any must include Null"); + + /** + * An enum that describes what this getter/setter/method aliases. This + * determines what things can be hoisted past this call, and if this + * call is movable what it can be hoisted past. + */ + enum AliasSet { + /** + * Alias nothing: a constant value, getting it can't affect any other + * values, nothing can affect it. + */ + AliasNone, + + /** + * Alias things that can modify the DOM but nothing else. Doing the + * call can't affect the behavior of any other function. + */ + AliasDOMSets, + + /** + * Alias the world. Calling this can change arbitrary values anywhere + * in the system. Most things fall in this bucket. + */ + AliasEverything, + + /** Must be last. */ + AliasSetCount + }; + + bool needsOuterizedThisObject() const { + return type() != Getter && type() != Setter; + } + + bool isTypedMethodJitInfo() const { return isTypedMethod; } + + OpType type() const { return OpType(type_); } + + AliasSet aliasSet() const { return AliasSet(aliasSet_); } + + JSValueType returnType() const { return JSValueType(returnType_); } + + union { + JSJitGetterOp getter; + JSJitSetterOp setter; + JSJitMethodOp method; + /** A DOM static method, used for Promise wrappers */ + JSNative staticMethod; + JSNative ignoresReturnValueMethod; + }; + + static unsigned offsetOfIgnoresReturnValueNative() { + return offsetof(JSJitInfo, ignoresReturnValueMethod); + } + + union { + uint16_t protoID; + js::jit::InlinableNative inlinableNative; + }; + + union { + uint16_t depth; + + // Additional opcode for some InlinableNative functions. + uint16_t nativeOp; + }; + + // These fields are carefully packed to take up 4 bytes. If you need more + // bits for whatever reason, please see if you can steal bits from existing + // fields before adding more members to this structure. + static constexpr size_t OpTypeBits = 4; + static constexpr size_t AliasSetBits = 4; + static constexpr size_t ReturnTypeBits = 8; + static constexpr size_t SlotIndexBits = 10; + + /** The OpType that says what sort of function we are. */ + uint32_t type_ : OpTypeBits; + + /** + * The alias set for this op. This is a _minimal_ alias set; in + * particular for a method it does not include whatever argument + * conversions might do. That's covered by argTypes and runtime + * analysis of the actual argument types being passed in. + */ + uint32_t aliasSet_ : AliasSetBits; + + /** The return type tag. Might be JSVAL_TYPE_UNKNOWN. */ + uint32_t returnType_ : ReturnTypeBits; + + static_assert(OpTypeCount <= (1 << OpTypeBits), + "Not enough space for OpType"); + static_assert(AliasSetCount <= (1 << AliasSetBits), + "Not enough space for AliasSet"); + static_assert((sizeof(JSValueType) * 8) <= ReturnTypeBits, + "Not enough space for JSValueType"); + + /** Is op fallible? False in setters. */ + uint32_t isInfallible : 1; + + /** + * Is op movable? To be movable the op must + * not AliasEverything, but even that might + * not be enough (e.g. in cases when it can + * throw or is explicitly not movable). + */ + uint32_t isMovable : 1; + + /** + * Can op be dead-code eliminated? Again, this + * depends on whether the op can throw, in + * addition to the alias set. + */ + uint32_t isEliminatable : 1; + + // XXXbz should we have a JSValueType for the type of the member? + /** + * True if this is a getter that can always + * get the value from a slot of the "this" object. + */ + uint32_t isAlwaysInSlot : 1; + + /** + * True if this is a getter that can sometimes (if the slot doesn't contain + * UndefinedValue()) get the value from a slot of the "this" object. + */ + uint32_t isLazilyCachedInSlot : 1; + + /** True if this is an instance of JSTypedMethodJitInfo. */ + uint32_t isTypedMethod : 1; + + /** + * If isAlwaysInSlot or isSometimesInSlot is true, + * the index of the slot to get the value from. + * Otherwise 0. + */ + uint32_t slotIndex : SlotIndexBits; + + static constexpr size_t maxSlotIndex = (1 << SlotIndexBits) - 1; +}; + +static_assert(sizeof(JSJitInfo) == (sizeof(void*) + 2 * sizeof(uint32_t)), + "There are several thousand instances of JSJitInfo stored in " + "a binary. Please don't increase its space requirements without " + "verifying that there is no other way forward (better packing, " + "smaller datatypes for fields, subclassing, etc.)."); + +struct JSTypedMethodJitInfo { + // We use C-style inheritance here, rather than C++ style inheritance + // because not all compilers support brace-initialization for non-aggregate + // classes. Using C++ style inheritance and constructors instead of + // brace-initialization would also force the creation of static + // constructors (on some compilers) when JSJitInfo and JSTypedMethodJitInfo + // structures are declared. Since there can be several thousand of these + // structures present and we want to have roughly equivalent performance + // across a range of compilers, we do things manually. + JSJitInfo base; + + const JSJitInfo::ArgType* const argTypes; /* For a method, a list of sets of + types that the function + expects. This can be used, + for example, to figure out + when argument coercions can + have side-effects. */ +}; + +#endif // js_experimental_JitInfo_h diff --git a/js/public/experimental/PCCountProfiling.h b/js/public/experimental/PCCountProfiling.h new file mode 100644 index 0000000000..132d48bcae --- /dev/null +++ b/js/public/experimental/PCCountProfiling.h @@ -0,0 +1,162 @@ +/* -*- 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/. */ + +/* + * Bytecode-level profiling support based on program counter offset within + * functions and overall scripts. + * + * SpiderMonkey compiles functions and scripts into bytecode representation. + * During execution, SpiderMonkey can keep a counter of how often each bytecode + * (specifically, bytecodes that are the targets of jumps) is reached, to track + * basic information about how many times any particular bytecode in a script + * or function is executed. These functions allow this counting to be started + * and stopped, and the results of such counting to be examined. + * + * Accumulated counting data is tracked per "script" (where "script" is either + * a JavaScript function from source text or a top-level script or module). + * Accumulated PCCount data thus prevents the particular scripts that were + * executed from being GC'd. Once PCCount profiling has been stopped, you + * should examine and then purge the accumulated data as soon as possible. + * + * Much of the data tracked here is pretty internal and may only have meaning if + * you're familiar with bytecode and Ion details. Hence why these APIs are + * "experimental". + */ + +#ifndef js_experimental_PCCountProfiling_h +#define js_experimental_PCCountProfiling_h + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSString; + +namespace JS { + +/** + * Start PCCount-based profiling if it hasn't already been started. + * + * This discards previously-accumulated PCCount profiling data from a prior + * PCCount profiling session. It also discards all active JIT code (as such + * code was compiled to *not* perform PCCount-based profiling), so a brief + * performance hit to code execution can be expected after calling this. + */ +extern JS_PUBLIC_API void StartPCCountProfiling(JSContext* cx); + +/** + * Stop PCCount-based profiling if it has been started. + * + * Internally, this accumulates PCCount profiling results into an array of + * (script, script count info) for future consumption. If accumulation fails, + * this array will just be empty. + * + * This also discard all active JIT code (as such code was compiled to perform + * PCCount-based profiling), so a brief performance hit to code execution can be + * expected after calling this. + */ +extern JS_PUBLIC_API void StopPCCountProfiling(JSContext* cx); + +/** + * Get a count of all scripts for which PCCount profiling data was accumulated, + * after PCCount profiling has been stopped. + * + * As noted above, if an internal error occurred when profiling was stopped, + * this function will return 0. + */ +extern JS_PUBLIC_API size_t GetPCCountScriptCount(JSContext* cx); + +/** + * Get a JSON string summarizing PCCount data for the given script, by index + * within the internal array computed when PCCount profiling was stopped, of + * length indicated by |JS::GetPCCountScriptCount|. + * + * The JSON string will encode an object of this form: + * + * { + * "file": filename for the script, + * "line": line number within the script, + * + * // OPTIONAL: only if this script is a function + * "name": function name + * + * "totals": + * { + * "interp": sum of execution counts at all bytecode locations, + * "ion": sum of Ion activity counts, + * } + * } + * + * There is no inherent ordering to the (script, script count info) pairs within + * the array. Callers generally should expect to call this function in a loop + * to get summaries for all executed scripts. + */ +extern JS_PUBLIC_API JSString* GetPCCountScriptSummary(JSContext* cx, + size_t script); + +/** + * Get a JSON string encoding detailed PCCount data for the given script, by + * index within the internal array computed when PCCount profiling was stopped, + * of length indicated by |JS::GetPCCountScriptCount|. + * + * The JSON string will encode an object of this form: + * + * { + * "text": a string containg (possibly decompiled) source of the script, + * "line": line number within the script, + * + * "opcodes": + * [ + * // array elements of this form, corresponding to the script's + * // bytecodes + * { + * "id": offset of bytecode within script + * "line": line number for bytecode, + * "name": the name of the bytecode in question, + * "text": text of the expression being evaluated, + * "counts": + * { + * // OPTIONAL: only if the count is nonzero + * "interp": count of times executed, + * }, + * ], + * + * // OPTIONAL: only if the script is executed under Ion + * "ion": + * [ + * // array of elements of this form, corresponding to Ion basic blocks + * { + * "id": Ion block id, + * "offset": Ion block offset, + * "successors": + * [ + * // list of Ion block ids from which execution may proceed after + * // this one + * ], + * "hits": count of times this block was hit during execution, + * "code": a string containing the code in this Ion basic block, + * } + * ], + * } + * + * There is no inherent ordering to the (script, script count info) pairs within + * the array. Callers generally should expect to call this function in a loop + * to get summaries for all executed scripts. + */ +extern JS_PUBLIC_API JSString* GetPCCountScriptContents(JSContext* cx, + size_t script); + +/** + * Purge all accumulated PCCount profiling data, particularly the internal array + * that |JS::GetPCCountScript{Count,Summary,Contents}| exposes -- and all that + * array's references to previously-executed scripts. + */ +extern JS_PUBLIC_API void PurgePCCounts(JSContext* cx); + +} // namespace JS + +#endif // js_experimental_PCCountProfiling_h diff --git a/js/public/experimental/SourceHook.h b/js/public/experimental/SourceHook.h new file mode 100644 index 0000000000..dc32fabbaf --- /dev/null +++ b/js/public/experimental/SourceHook.h @@ -0,0 +1,99 @@ +/* -*- 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/. */ + +/* + * The context-wide source hook allows the source of scripts/functions to be + * discarded, if that source is constant and readily-reloadable if it's needed + * in the future. + * + * Ordinarily, functions and scripts store a copy of their underlying source, to + * support |Function.prototype.toString| and debuggers. Some scripts, however, + * might be constant and retrievable on demand -- perhaps burned into the binary + * or in a readonly file provided by the embedding. Why not just ask the + * embedding for a copy of the source? + * + * The context-wide |SourceHook| gives embedders a way to respond to these + * requests. The source of scripts/functions compiled with the compile option + * |JS::CompileOptions::setSourceIsLazy(true)| is eligible to be discarded. + * (The exact conditions under which source is discarded are unspecified.) *If* + * source is discarded, performing an operation that requires source uses the + * source hook to load the source. + * + * The source hook must return the *exact* same source for every call. (This is + * why the source hook is unsuitable for use with scripts loaded from the web: + * in general, their contents can change over time.) If the source hook doesn't + * return the exact same source, Very Bad Things may happen. (For example, + * previously-valid indexes into the source will no longer be coherent: they + * might index out of bounds, into the middle of multi-unit code points, &c.) + * + * These APIs are experimental because they shouldn't provide a per-*context* + * mechanism, rather something that's per-compilation. + */ + +#ifndef js_experimental_SourceHook_h +#define js_experimental_SourceHook_h + +#include "mozilla/UniquePtr.h" // mozilla::UniquePtr + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +struct JS_PUBLIC_API JSContext; + +namespace js { + +/** + * A class of objects that return source code on demand. + * + * When code is compiled with setSourceIsLazy(true), SpiderMonkey doesn't + * retain the source code (and doesn't do lazy bytecode generation). If we ever + * need the source code, say, in response to a call to Function.prototype. + * toSource or Debugger.Source.prototype.text, then we call the 'load' member + * function of the instance of this class that has hopefully been registered + * with the runtime, passing the code's URL, and hope that it will be able to + * find the source. + */ +class SourceHook { + public: + virtual ~SourceHook() = default; + + /** + * Attempt to load the source for |filename|. + * + * On success, return true and store an owning pointer to the UTF-8 or UTF-16 + * contents of the file in whichever of |twoByteSource| or |utf8Source| is + * non-null. (Exactly one of these will be non-null.) If the stored pointer + * is non-null, source was loaded and must be |js_free|'d when it's no longer + * needed. If the stored pointer is null, the JS engine will simply act as if + * source was unavailable, and users like |Function.prototype.toString| will + * produce fallback results, e.g. "[native code]". + * + * On failure, return false. The contents of whichever of |twoByteSource| or + * |utf8Source| was initially non-null are unspecified and must not be + * |js_free|'d. + */ + virtual bool load(JSContext* cx, const char* filename, + char16_t** twoByteSource, char** utf8Source, + size_t* length) = 0; +}; + +/** + * Have |cx| use |hook| to retrieve lazily-retrieved source code. See the + * comments for SourceHook. The context takes ownership of the hook, and + * will delete it when the context itself is deleted, or when a new hook is + * set. + */ +extern JS_PUBLIC_API void SetSourceHook(JSContext* cx, + mozilla::UniquePtr<SourceHook> hook); + +/** Remove |cx|'s source hook, and return it. The caller now owns the hook. */ +extern JS_PUBLIC_API mozilla::UniquePtr<SourceHook> ForgetSourceHook( + JSContext* cx); + +} // namespace js + +#endif // js_experimental_SourceHook_h diff --git a/js/public/experimental/TypedData.h b/js/public/experimental/TypedData.h new file mode 100644 index 0000000000..9ba14c65bc --- /dev/null +++ b/js/public/experimental/TypedData.h @@ -0,0 +1,719 @@ +/* -*- 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/. */ + +/* + * Typed array, ArrayBuffer, and DataView creation, predicate, and accessor + * functions. + */ + +#ifndef js_experimental_TypedData_h +#define js_experimental_TypedData_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_CRASH +#include "mozilla/Casting.h" // mozilla::AssertedCast + +#include <stddef.h> // size_t +#include <stdint.h> // {,u}int8_t, {,u}int16_t, {,u}int32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Object.h" // JS::GetClass, JS::GetReservedSlot, JS::GetMaybePtrFromReservedSlot +#include "js/RootingAPI.h" // JS::Handle, JS_DECLARE_IS_HEAP_CONSTRUCTIBLE_TYPE +#include "js/ScalarType.h" // JS::Scalar::Type +#include "js/Wrapper.h" // js::CheckedUnwrapStatic + +struct JSClass; +class JS_PUBLIC_API JSObject; + +namespace JS { + +class JS_PUBLIC_API AutoRequireNoGC; + +} // namespace JS + +// JS_FOR_EACH_TYPED_ARRAY(MACRO) expands MACRO once for each specific +// typed array subtype (Int8Array, Float64Array, ...), passing arguments +// as MACRO(ExternalT, NativeT, Name) where +// +// ExternalT - externally-exposed element type (eg uint8_t) +// +// NativeT - element type used for the implementation (eg js::uint8_clamped_t) +// Note that this type is not exposed publicly. Internal files need to +// #include <vm/Uint8Clamped.h> to see it. +// +// Name - a name usable as both a JS::Scalar::Type value (eg +// JS::Scalar::Uint8Clamped) or the stem of a full typed array name (eg +// Uint8ClampedArray) +// +#define JS_FOR_EACH_TYPED_ARRAY(MACRO) \ + MACRO(int8_t, int8_t, Int8) \ + MACRO(uint8_t, uint8_t, Uint8) \ + MACRO(int16_t, int16_t, Int16) \ + MACRO(uint16_t, uint16_t, Uint16) \ + MACRO(int32_t, int32_t, Int32) \ + MACRO(uint32_t, uint32_t, Uint32) \ + MACRO(float, float, Float32) \ + MACRO(double, double, Float64) \ + MACRO(uint8_t, js::uint8_clamped, Uint8Clamped) \ + MACRO(int64_t, int64_t, BigInt64) \ + MACRO(uint64_t, uint64_t, BigUint64) + +/* + * JS_New(type)Array: + * + * Create a new typed array with nelements elements. + * + * These functions (except the WithBuffer variants) fill in the array with + * zeros. + * + * JS_New(type)ArrayFromArray: + * + * Create a new typed array and copy in values from the given object. The + * object is used as if it were an array; that is, the new array (if + * successfully created) will have length given by array.length, and its + * elements will be those specified by array[0], array[1], and so on, after + * conversion to the typed array element type. + * + * JS_New(type)ArrayWithBuffer: + * + * Create a new typed array using the given ArrayBuffer or + * SharedArrayBuffer for storage. The length value is optional; if -1 + * is passed, enough elements to use up the remainder of the byte + * array is used as the default value. + */ + +#define DECLARE_TYPED_ARRAY_CREATION_API(ExternalType, NativeType, Name) \ + extern JS_PUBLIC_API JSObject* JS_New##Name##Array(JSContext* cx, \ + size_t nelements); \ + extern JS_PUBLIC_API JSObject* JS_New##Name##ArrayFromArray( \ + JSContext* cx, JS::Handle<JSObject*> array); \ + extern JS_PUBLIC_API JSObject* JS_New##Name##ArrayWithBuffer( \ + JSContext* cx, JS::Handle<JSObject*> arrayBuffer, size_t byteOffset, \ + int64_t length); + +JS_FOR_EACH_TYPED_ARRAY(DECLARE_TYPED_ARRAY_CREATION_API) +#undef DECLARE_TYPED_ARRAY_CREATION_API + +/** + * Check whether obj supports JS_GetTypedArray* APIs. Note that this may return + * false if a security wrapper is encountered that denies the unwrapping. If + * this test or one of the JS_Is*Array tests succeeds, then it is safe to call + * the various accessor JSAPI calls defined below. + */ +extern JS_PUBLIC_API bool JS_IsTypedArrayObject(JSObject* obj); + +/** + * Check whether obj supports JS_GetArrayBufferView* APIs. Note that this may + * return false if a security wrapper is encountered that denies the + * unwrapping. If this test or one of the more specific tests succeeds, then it + * is safe to call the various ArrayBufferView accessor JSAPI calls defined + * below. + */ +extern JS_PUBLIC_API bool JS_IsArrayBufferViewObject(JSObject* obj); + +/** + * Return the isShared flag of a typed array, which denotes whether + * the underlying buffer is a SharedArrayBuffer. + * + * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow + * be known that it would pass such a test: it is a typed array or a wrapper of + * a typed array, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API bool JS_GetTypedArraySharedness(JSObject* obj); + +/* + * Test for specific typed array types (ArrayBufferView subtypes) and return + * the unwrapped object if so, else nullptr. Never throws. + */ + +namespace js { + +extern JS_PUBLIC_API JSObject* UnwrapArrayBufferView(JSObject* obj); + +extern JS_PUBLIC_API JSObject* UnwrapReadableStream(JSObject* obj); + +namespace detail { + +constexpr size_t TypedArrayLengthSlot = 1; +constexpr size_t TypedArrayDataSlot = 3; + +} // namespace detail + +// This one isn't inlined because it's rather tricky (by dint of having to deal +// with a dozen-plus classes and varying slot layouts. +extern JS_PUBLIC_API void GetArrayBufferViewLengthAndData(JSObject* obj, + size_t* length, + bool* isSharedMemory, + uint8_t** data); + +} // namespace js + +/* + * JS_GetObjectAs(type)Array(JSObject* maybeWrapped, size_t* length, bool* + * isSharedMemory, element_type** data) + * + * Unwrap Typed arrays all at once. Return nullptr without throwing if the + * object cannot be viewed as the correct typed array, or the typed array + * object on success, filling both outparameters. + */ +#define DECLARE_GET_OBJECT_AS(ExternalType, NativeType, Name) \ + extern JS_PUBLIC_API JSObject* JS_GetObjectAs##Name##Array( \ + JSObject* maybeWrapped, size_t* length, bool* isSharedMemory, \ + ExternalType** data); +JS_FOR_EACH_TYPED_ARRAY(DECLARE_GET_OBJECT_AS) +#undef DECLARE_GET_OBJECT_AS + +extern JS_PUBLIC_API JSObject* JS_GetObjectAsArrayBufferView( + JSObject* obj, size_t* length, bool* isSharedMemory, uint8_t** data); + +/* + * Get the type of elements in a typed array, or MaxTypedArrayViewType if a + * DataView. + * + * |obj| must have passed a JS_IsArrayBufferView/JS_Is*Array test, or somehow + * be known that it would pass such a test: it is an ArrayBufferView or a + * wrapper of an ArrayBufferView, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API JS::Scalar::Type JS_GetArrayBufferViewType(JSObject* obj); + +/** + * Return the number of elements in a typed array. + * + * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow + * be known that it would pass such a test: it is a typed array or a wrapper of + * a typed array, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API size_t JS_GetTypedArrayLength(JSObject* obj); + +/** + * Return the byte offset from the start of an ArrayBuffer to the start of a + * typed array view. + * + * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow + * be known that it would pass such a test: it is a typed array or a wrapper of + * a typed array, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API size_t JS_GetTypedArrayByteOffset(JSObject* obj); + +/** + * Return the byte length of a typed array. + * + * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow + * be known that it would pass such a test: it is a typed array or a wrapper of + * a typed array, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API size_t JS_GetTypedArrayByteLength(JSObject* obj); + +/** + * More generic name for JS_GetTypedArrayByteLength to cover DataViews as well + */ +extern JS_PUBLIC_API size_t JS_GetArrayBufferViewByteLength(JSObject* obj); + +/** + * More generic name for JS_GetTypedArrayByteOffset to cover DataViews as well + */ +extern JS_PUBLIC_API size_t JS_GetArrayBufferViewByteOffset(JSObject* obj); + +/** + * Same as above, but for any kind of ArrayBufferView. Prefer the type-specific + * versions when possible. + */ +extern JS_PUBLIC_API void* JS_GetArrayBufferViewData( + JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC&); + +/** + * Return a "fixed" pointer (one that will not move during a GC) to the + * ArrayBufferView's data. Note that this will not keep the object alive; the + * holding object should be rooted or traced. If the view is storing the data + * inline, this will copy the data to the provided buffer, returning nullptr if + * bufSize is inadequate. + * + * Avoid using this unless necessary. JS_GetArrayBufferViewData is simpler and + * more efficient because it requires the caller to ensure that a GC will not + * occur and thus does not need to handle movable data. + */ +extern JS_PUBLIC_API uint8_t* JS_GetArrayBufferViewFixedData(JSObject* obj, + uint8_t* buffer, + size_t bufSize); + +/** + * If the bufSize passed to JS_GetArrayBufferViewFixedData is at least this + * many bytes, then any copied data is guaranteed to fit into the provided + * buffer. + */ +extern JS_PUBLIC_API size_t JS_MaxMovableTypedArraySize(); + +/** + * Return the ArrayBuffer or SharedArrayBuffer underlying an ArrayBufferView. + * This may return a detached buffer. |obj| must be an object that would + * return true for JS_IsArrayBufferViewObject(). + */ +extern JS_PUBLIC_API JSObject* JS_GetArrayBufferViewBuffer( + JSContext* cx, JS::Handle<JSObject*> obj, bool* isSharedMemory); + +/** + * Create a new DataView using the given buffer for storage. The given buffer + * must be an ArrayBuffer or SharedArrayBuffer (or a cross-compartment wrapper + * of either type), and the offset and length must fit within the bounds of the + * buffer. Currently, nullptr will be returned and an exception will be thrown + * if these conditions do not hold, but do not depend on that behavior. + */ +JS_PUBLIC_API JSObject* JS_NewDataView(JSContext* cx, + JS::Handle<JSObject*> buffer, + size_t byteOffset, size_t byteLength); + +namespace JS { + +/* + * Returns whether the passed array buffer view is 'large': its byteLength >= 2 + * GB. + * + * |obj| must pass a JS_IsArrayBufferViewObject test. + */ +JS_PUBLIC_API bool IsLargeArrayBufferView(JSObject* obj); + +namespace detail { + +// Map from eg Uint8Clamped -> uint8_t, Uint8 -> uint8_t, or Float64 -> +// double. Used as the DataType within a JS::TypedArray specialization. +template <JS::Scalar::Type ArrayType> +struct ExternalTypeOf {}; + +#define DEFINE_ELEMENT_TYPES(ExternalT, NativeT, Name) \ + template <> \ + struct ExternalTypeOf<JS::Scalar::Name> { \ + using Type = ExternalT; \ + }; +JS_FOR_EACH_TYPED_ARRAY(DEFINE_ELEMENT_TYPES) +#undef DEFINE_ELEMENT_TYPES + +template <JS::Scalar::Type ArrayType> +using ExternalTypeOf_t = typename ExternalTypeOf<ArrayType>::Type; + +} // namespace detail + +// A class holding a JSObject referring to a buffer of data. Either an +// ArrayBufferObject or some sort of ArrayBufferViewObject (see below). +// Note that this will always hold an unwrapped object. +class JS_PUBLIC_API ArrayBufferOrView { + public: + // Typed Arrays will set this to their specific element type. + // Everything else just claims to expose things as uint8_t*. + using DataType = uint8_t; + + protected: + JSObject* obj; + + explicit ArrayBufferOrView(JSObject* unwrapped) : obj(unwrapped) {} + + public: + // ArrayBufferOrView subclasses will set `obj` to nullptr if wrapping an + // object of the wrong type. So this allows: + // + // auto view = JS::TypedArray<JS::Scalar::Int8>::fromObject(obj); + // if (!view) { ... } + // + explicit operator bool() const { return !!obj; } + + // `obj` must be an unwrapped ArrayBuffer or view, or nullptr. + static inline ArrayBufferOrView fromObject(JSObject* unwrapped); + + // Unwrap an ArrayBuffer or view. Returns ArrayBufferOrView(nullptr) if + // `maybeWrapped` is the wrong type or fails unwrapping. Never throw. + static ArrayBufferOrView unwrap(JSObject* maybeWrapped); + + // Allow use as Rooted<JS::ArrayBufferOrView>. + void trace(JSTracer* trc) { + if (obj) { + js::gc::TraceExternalEdge(trc, &obj, "ArrayBufferOrView object"); + } + } + + bool isDetached() const; + + void exposeToActiveJS() const { + if (obj) { + js::BarrierMethods<JSObject*>::exposeToJS(obj); + } + } + + JSObject* asObject() const { + exposeToActiveJS(); + return obj; + } + + JSObject* asObjectUnbarriered() const { return obj; } + + JSObject** addressOfObject() { return &obj; } + + bool operator==(const ArrayBufferOrView& other) const { + return obj == other.asObjectUnbarriered(); + } + bool operator!=(const ArrayBufferOrView& other) const { + return obj != other.asObjectUnbarriered(); + } +}; + +class JS_PUBLIC_API ArrayBuffer : public ArrayBufferOrView { + protected: + explicit ArrayBuffer(JSObject* unwrapped) : ArrayBufferOrView(unwrapped) {} + + public: + static const JSClass* const UnsharedClass; + static const JSClass* const SharedClass; + + static ArrayBuffer fromObject(JSObject* unwrapped) { + if (unwrapped) { + const JSClass* clasp = GetClass(unwrapped); + if (clasp == UnsharedClass || clasp == SharedClass) { + return ArrayBuffer(unwrapped); + } + } + return ArrayBuffer(nullptr); + } + static ArrayBuffer unwrap(JSObject* maybeWrapped); + + static ArrayBuffer create(JSContext* cx, size_t nbytes); + + bool isDetached() const; + bool isSharedMemory() const; + + uint8_t* getLengthAndData(size_t* length, bool* isSharedMemory, + const JS::AutoRequireNoGC&); + + uint8_t* getData(bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { + size_t length; + return getLengthAndData(&length, isSharedMemory, nogc); + } +}; + +// A view into an ArrayBuffer, either a DataViewObject or a Typed Array variant. +class JS_PUBLIC_API ArrayBufferView : public ArrayBufferOrView { + protected: + explicit ArrayBufferView(JSObject* unwrapped) + : ArrayBufferOrView(unwrapped) {} + + public: + static inline ArrayBufferView fromObject(JSObject* unwrapped); + static ArrayBufferView unwrap(JSObject* maybeWrapped) { + if (!maybeWrapped) { + return ArrayBufferView(nullptr); + } + ArrayBufferView view = fromObject(maybeWrapped); + if (view) { + return view; + } + return fromObject(js::CheckedUnwrapStatic(maybeWrapped)); + } + + bool isDetached() const; + bool isSharedMemory() const; + + uint8_t* getLengthAndData(size_t* length, bool* isSharedMemory, + const JS::AutoRequireNoGC&); + uint8_t* getData(bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { + size_t length; + return getLengthAndData(&length, isSharedMemory, nogc); + } + + // Must only be called if !isDetached(). + size_t getByteLength(const JS::AutoRequireNoGC&); +}; + +class JS_PUBLIC_API DataView : public ArrayBufferView { + protected: + explicit DataView(JSObject* unwrapped) : ArrayBufferView(unwrapped) {} + + public: + static const JSClass* const ClassPtr; + + static DataView fromObject(JSObject* unwrapped) { + if (unwrapped && GetClass(unwrapped) == ClassPtr) { + return DataView(unwrapped); + } + return DataView(nullptr); + } + + static DataView unwrap(JSObject* maybeWrapped) { + if (!maybeWrapped) { + return DataView(nullptr); + } + DataView view = fromObject(maybeWrapped); + if (view) { + return view; + } + return fromObject(js::CheckedUnwrapStatic(maybeWrapped)); + } +}; + +// Base type of all Typed Array variants. +class JS_PUBLIC_API TypedArray_base : public ArrayBufferView { + protected: + explicit TypedArray_base(JSObject* unwrapped) : ArrayBufferView(unwrapped) {} + + static const JSClass* const classes; + + public: + static TypedArray_base fromObject(JSObject* unwrapped); + + static TypedArray_base unwrap(JSObject* maybeWrapped) { + if (!maybeWrapped) { + return TypedArray_base(nullptr); + } + TypedArray_base view = fromObject(maybeWrapped); + if (view) { + return view; + } + return fromObject(js::CheckedUnwrapStatic(maybeWrapped)); + } +}; + +template <JS::Scalar::Type TypedArrayElementType> +class JS_PUBLIC_API TypedArray : public TypedArray_base { + protected: + explicit TypedArray(JSObject* unwrapped) : TypedArray_base(unwrapped) {} + + public: + using DataType = detail::ExternalTypeOf_t<TypedArrayElementType>; + + static constexpr JS::Scalar::Type Scalar = TypedArrayElementType; + + // This cannot be a static data member because on Windows, + // __declspec(dllexport) causes the class to be instantiated immediately, + // leading to errors when later explicit specializations of inline member + // functions are encountered ("error: explicit specialization of 'ClassPtr' + // after instantiation"). And those inlines need to be defined outside of the + // class due to order dependencies. This is the only way I could get it to + // work on both Windows and POSIX. + static const JSClass* clasp() { + return &TypedArray_base::classes[static_cast<int>(TypedArrayElementType)]; + } + + static TypedArray create(JSContext* cx, size_t nelements); + static TypedArray fromArray(JSContext* cx, HandleObject other); + static TypedArray fromBuffer(JSContext* cx, HandleObject arrayBuffer, + size_t byteOffset, int64_t length); + + // Return an interface wrapper around `obj`, or around nullptr if `obj` is not + // an unwrapped typed array of the correct type. + static TypedArray fromObject(JSObject* unwrapped) { + if (unwrapped && GetClass(unwrapped) == clasp()) { + return TypedArray(unwrapped); + } + return TypedArray(nullptr); + } + + static TypedArray unwrap(JSObject* maybeWrapped) { + if (!maybeWrapped) { + return TypedArray(nullptr); + } + TypedArray view = fromObject(maybeWrapped); + if (view) { + return view; + } + return fromObject(js::CheckedUnwrapStatic(maybeWrapped)); + } + + // Return a pointer to the start of the data referenced by a typed array. The + // data is still owned by the typed array, and should not be modified on + // another thread. Furthermore, the pointer can become invalid on GC (if the + // data is small and fits inside the array's GC header), so callers must take + // care not to hold on across anything that could GC. + // + // |obj| must have passed a JS_Is*Array test, or somehow be known that it + // would pass such a test: it is a typed array or a wrapper of a typed array, + // and the unwrapping will succeed. + // + // |*isSharedMemory| will be set to true if the typed array maps a + // SharedArrayBuffer, otherwise to false. + // + DataType* getLengthAndData(size_t* length, bool* isSharedMemory, + const JS::AutoRequireNoGC& nogc); + + DataType* getData(bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { + size_t length; + return getLengthAndData(&length, isSharedMemory, nogc); + } +}; + +ArrayBufferOrView ArrayBufferOrView::fromObject(JSObject* unwrapped) { + if (ArrayBuffer::fromObject(unwrapped) || + ArrayBufferView::fromObject(unwrapped)) { + return ArrayBufferOrView(unwrapped); + } + return ArrayBufferOrView(nullptr); +} + +ArrayBufferView ArrayBufferView::fromObject(JSObject* unwrapped) { + if (TypedArray_base::fromObject(unwrapped) || + DataView::fromObject(unwrapped)) { + return ArrayBufferView(unwrapped); + } + return ArrayBufferView(nullptr); +} + +} /* namespace JS */ + +/* + * JS_Get(type)ArrayData(JSObject* obj, + * bool* isSharedMemory, + * const JS::AutoRequireNoGC&) + * + * js::Get(type)ArrayLengthAndData(JSObject* obj, + * size_t* length, + * bool* isSharedMemory, + * const JS::AutoRequireNoGC&) + * + * Return a pointer to the start of the data referenced by a typed array. The + * data is still owned by the typed array, and should not be modified on + * another thread. Furthermore, the pointer can become invalid on GC (if the + * data is small and fits inside the array's GC header), so callers must take + * care not to hold on across anything that could GC. + * + * |obj| must have passed a JS_Is*Array test, or somehow be known that it would + * pass such a test: it is a typed array or a wrapper of a typed array, and the + * unwrapping will succeed. + * + * |*isSharedMemory| will be set to true if the typed array maps a + * SharedArrayBuffer, otherwise to false. + */ + +#define JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(ExternalType, NativeType, Name) \ + extern JS_PUBLIC_API ExternalType* JS_Get##Name##ArrayData( \ + JSObject* maybeWrapped, bool* isSharedMemory, \ + const JS::AutoRequireNoGC&); \ + \ + namespace js { \ + inline void Get##Name##ArrayLengthAndData(JSObject* unwrapped, \ + size_t* length, \ + bool* isSharedMemory, \ + ExternalType** data) { \ + MOZ_ASSERT(JS::GetClass(unwrapped) == \ + JS::TypedArray<JS::Scalar::Name>::clasp()); \ + const JS::Value& lenSlot = \ + JS::GetReservedSlot(unwrapped, detail::TypedArrayLengthSlot); \ + *length = size_t(lenSlot.toPrivate()); \ + *isSharedMemory = JS_GetTypedArraySharedness(unwrapped); \ + *data = JS::GetMaybePtrFromReservedSlot<ExternalType>( \ + unwrapped, detail::TypedArrayDataSlot); \ + } \ + \ + JS_PUBLIC_API JSObject* Unwrap##Name##Array(JSObject* maybeWrapped); \ + } /* namespace js */ + +JS_FOR_EACH_TYPED_ARRAY(JS_DEFINE_DATA_AND_LENGTH_ACCESSOR) +#undef JS_DEFINE_DATA_AND_LENGTH_ACCESSOR + +namespace JS { + +#define IMPL_TYPED_ARRAY_CLASS(ExternalType, NativeType, Name) \ + template <> \ + inline JS::TypedArray<JS::Scalar::Name> \ + JS::TypedArray<JS::Scalar::Name>::create(JSContext* cx, size_t nelements) { \ + return fromObject(JS_New##Name##Array(cx, nelements)); \ + }; \ + \ + template <> \ + inline JS::TypedArray<JS::Scalar::Name> \ + JS::TypedArray<JS::Scalar::Name>::fromArray(JSContext* cx, \ + HandleObject other) { \ + return fromObject(JS_New##Name##ArrayFromArray(cx, other)); \ + }; \ + \ + template <> \ + inline JS::TypedArray<JS::Scalar::Name> \ + JS::TypedArray<JS::Scalar::Name>::fromBuffer( \ + JSContext* cx, HandleObject arrayBuffer, size_t byteOffset, \ + int64_t length) { \ + return fromObject( \ + JS_New##Name##ArrayWithBuffer(cx, arrayBuffer, byteOffset, length)); \ + }; + +JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS) +#undef IMPL_TYPED_ARRAY_CLASS + +// Create simple names like Int8Array, Float32Array, etc. +#define JS_DECLARE_CLASS_ALIAS(ExternalType, NativeType, Name) \ + using Name##Array = TypedArray<js::Scalar::Name>; +JS_FOR_EACH_TYPED_ARRAY(JS_DECLARE_CLASS_ALIAS) +#undef JS_DECLARE_CLASS_ALIAS + +} // namespace JS + +namespace js { + +template <typename T> +using EnableIfABOVType = + std::enable_if_t<std::is_base_of_v<JS::ArrayBufferOrView, T>>; + +template <typename T, typename Wrapper> +class WrappedPtrOperations<T, Wrapper, EnableIfABOVType<T>> { + auto get() const { return static_cast<const Wrapper*>(this)->get(); } + + public: + explicit operator bool() const { return bool(get()); } + JSObject* asObject() const { return get().asObject(); } + bool isDetached() const { return get().isDetached(); } + bool isSharedMemory() const { return get().isSharedMemory(); } + + typename T::DataType* getLengthAndData(size_t* length, bool* isSharedMemory, + const JS::AutoRequireNoGC& nogc) { + return get().getLengthAndData(length, isSharedMemory, nogc); + } + + typename T::DataType* getData(bool* isSharedMemory, + const JS::AutoRequireNoGC& nogc) { + return get().getData(isSharedMemory, nogc); + } +}; + +// Allow usage within Heap<T>. +template <typename T> +struct IsHeapConstructibleType<T, EnableIfABOVType<T>> : public std::true_type { +}; + +template <typename T> +struct BarrierMethods<T, EnableIfABOVType<T>> { + static gc::Cell* asGCThingOrNull(T view) { + return reinterpret_cast<gc::Cell*>(view.asObjectUnbarriered()); + } + static void postWriteBarrier(T* viewp, T prev, T next) { + BarrierMethods<JSObject*>::postWriteBarrier(viewp->addressOfObject(), + prev.asObjectUnbarriered(), + next.asObjectUnbarriered()); + } + static void exposeToJS(T view) { view.exposeToActiveJS(); } + static void readBarrier(T view) { + JSObject* obj = view.asObjectUnbarriered(); + if (obj) { + js::gc::IncrementalReadBarrier(JS::GCCellPtr(obj)); + } + } +}; + +} // namespace js + +namespace JS { +template <typename T> +struct SafelyInitialized<T, js::EnableIfABOVType<T>> { + static T create() { return T::fromObject(nullptr); } +}; +} // namespace JS + +/* + * JS_Is(type)Array(JSObject* maybeWrapped) + * + * Test for specific typed array types. + */ + +#define DECLARE_IS_ARRAY_TEST(_1, _2, Name) \ + inline JS_PUBLIC_API bool JS_Is##Name##Array(JSObject* maybeWrapped) { \ + return JS::TypedArray<js::Scalar::Name>::unwrap(maybeWrapped).asObject(); \ + } +JS_FOR_EACH_TYPED_ARRAY(DECLARE_IS_ARRAY_TEST) +#undef DECLARE_IS_ARRAY_TEST + +#endif // js_experimental_TypedData_h diff --git a/js/public/friend/DOMProxy.h b/js/public/friend/DOMProxy.h new file mode 100644 index 0000000000..533c327adb --- /dev/null +++ b/js/public/friend/DOMProxy.h @@ -0,0 +1,91 @@ +/* -*- 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/. */ + +/* + * Specify information about DOMProxy proxies in the DOM, for use by ICs. + * + * Embedders who don't need to define particularly high-performance proxies that + * can have random properties added to them can ignore this header. + */ + +#ifndef js_friend_DOMProxy_h +#define js_friend_DOMProxy_h + +#include <stddef.h> // size_t +#include <stdint.h> // uint64_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Id.h" // JS::PropertyKey +#include "js/RootingAPI.h" // JS::Handle, JS::Heap +#include "js/Value.h" // JS::UndefinedValue, JS::Value + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; + +namespace JS { + +/* + * The DOMProxyShadowsCheck function will be called to check if the property for + * id should be gotten from the prototype, or if there is an own property that + * shadows it. + * * If ShadowsViaDirectExpando is returned, then the slot at + * listBaseExpandoSlot contains an expando object which has the property in + * question. + * * If ShadowsViaIndirectExpando is returned, then the slot at + * listBaseExpandoSlot contains a private pointer to an ExpandoAndGeneration + * and the expando object in the ExpandoAndGeneration has the property in + * question. + * * If DoesntShadow is returned then the slot at listBaseExpandoSlot should + * either be undefined or point to an expando object that would contain the + * own property. + * * If DoesntShadowUnique is returned then the slot at listBaseExpandoSlot + * should contain a private pointer to a ExpandoAndGeneration, which contains + * a JS::Value that should either be undefined or point to an expando object, + * and a uint64 value. If that value changes then the IC for getting a + * property will be invalidated. + * * If Shadows is returned, that means the property is an own property of the + * proxy but doesn't live on the expando object. + */ + +struct ExpandoAndGeneration { + ExpandoAndGeneration() : expando(JS::UndefinedValue()), generation(0) {} + + void OwnerUnlinked() { ++generation; } + + static constexpr size_t offsetOfExpando() { + return offsetof(ExpandoAndGeneration, expando); + } + + static constexpr size_t offsetOfGeneration() { + return offsetof(ExpandoAndGeneration, generation); + } + + Heap<Value> expando; + uint64_t generation; +}; + +enum class DOMProxyShadowsResult { + ShadowCheckFailed, + Shadows, + DoesntShadow, + DoesntShadowUnique, + ShadowsViaDirectExpando, + ShadowsViaIndirectExpando +}; + +using DOMProxyShadowsCheck = DOMProxyShadowsResult (*)(JSContext*, + Handle<JSObject*>, + Handle<JS::PropertyKey>); + +extern JS_PUBLIC_API void SetDOMProxyInformation( + const void* domProxyHandlerFamily, + DOMProxyShadowsCheck domProxyShadowsCheck, + const void* domRemoteProxyHandlerFamily); + +} // namespace JS + +#endif // js_friend_DOMProxy_h diff --git a/js/public/friend/DumpFunctions.h b/js/public/friend/DumpFunctions.h new file mode 100644 index 0000000000..3b69277edc --- /dev/null +++ b/js/public/friend/DumpFunctions.h @@ -0,0 +1,127 @@ +/* -*- 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/. */ + +/* Functions to print out values during debugging. */ + +#ifndef js_friend_DumpFunctions_h +#define js_friend_DumpFunctions_h + +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf + +#include <stddef.h> // size_t +#include <stdio.h> // FILE + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Printer.h" // js::GenericPrinter +#include "js/Utility.h" // JS::UniqueChars + +class JS_PUBLIC_API JSAtom; +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; +class JS_PUBLIC_API JSScript; +class JS_PUBLIC_API JSString; + +namespace JS { + +class JS_PUBLIC_API BigInt; +class JS_PUBLIC_API PropertyKey; +class JS_PUBLIC_API Value; + +} // namespace JS + +namespace js { + +class InterpreterFrame; + +} // namespace js + +namespace JS { + +/** Exposed for DumpJSStack */ +extern JS_PUBLIC_API JS::UniqueChars FormatStackDump(JSContext* cx, + bool showArgs, + bool showLocals, + bool showThisProps); + +} // namespace JS + +namespace js { + +/* + * These functions are FRIEND_API to help the debugger find them and to support + * temporarily hacking js::Dump* calls into other code. Note that there are + * overloads that do not require the FILE* parameter, which will default to + * stderr. + * + * These functions are no-ops unless built with DEBUG or JS_JITSPEW. + */ + +extern JS_PUBLIC_API void DumpString(JSString* str, FILE* fp); + +extern JS_PUBLIC_API void DumpAtom(JSAtom* atom, FILE* fp); + +extern JS_PUBLIC_API void DumpObject(JSObject* obj, FILE* fp); + +extern JS_PUBLIC_API void DumpChars(const char16_t* s, size_t n, FILE* fp); + +// DumpBigInt() outputs the value in decimal if it fits within a 64-bit int, and +// otherwise in hex, prefixed with "0x". In both cases the "n" is appended. +extern JS_PUBLIC_API void DumpBigInt(JS::BigInt* bi, FILE* fp); + +extern JS_PUBLIC_API void DumpValue(const JS::Value& val, FILE* fp); + +extern JS_PUBLIC_API void DumpId(JS::PropertyKey id, FILE* fp); + +extern JS_PUBLIC_API bool DumpPC(JSContext* cx, FILE* fp); + +extern JS_PUBLIC_API bool DumpScript(JSContext* cx, JSScript* scriptArg, + FILE* fp); + +// Versions for use directly in a debugger (default parameters are not handled +// well in gdb; built-in handles like stderr are not handled well in lldb.) +extern JS_PUBLIC_API void DumpString(JSString* str); +extern JS_PUBLIC_API void DumpAtom(JSAtom* atom); +extern JS_PUBLIC_API void DumpObject(JSObject* obj); +extern JS_PUBLIC_API void DumpChars(const char16_t* s, size_t n); +extern JS_PUBLIC_API void DumpBigInt(JS::BigInt* bi); +extern JS_PUBLIC_API void DumpValue(const JS::Value& val); +extern JS_PUBLIC_API void DumpId(JS::PropertyKey id); +extern JS_PUBLIC_API void DumpInterpreterFrame( + JSContext* cx, InterpreterFrame* start = nullptr); +extern JS_PUBLIC_API bool DumpPC(JSContext* cx); +extern JS_PUBLIC_API bool DumpScript(JSContext* cx, JSScript* scriptArg); + +// DumpBacktrace(), unlike the other dump functions, always dumps a backtrace -- +// regardless of DEBUG or JS_JITSPEW. + +extern JS_PUBLIC_API void DumpBacktrace(JSContext* cx, FILE* fp); + +extern JS_PUBLIC_API void DumpBacktrace(JSContext* cx, GenericPrinter& out); + +extern JS_PUBLIC_API void DumpBacktrace(JSContext* cx); + +enum DumpHeapNurseryBehaviour { + CollectNurseryBeforeDump, + IgnoreNurseryObjects +}; + +/** + * Dump the complete object graph of heap-allocated things. + * fp is the file for the dump output. + */ +extern JS_PUBLIC_API void DumpHeap( + JSContext* cx, FILE* fp, DumpHeapNurseryBehaviour nurseryBehaviour, + mozilla::MallocSizeOf mallocSizeOf = nullptr); + +extern JS_PUBLIC_API void DumpFmt(FILE* fp, const char* fmt, ...) + MOZ_FORMAT_PRINTF(2, 3); +extern JS_PUBLIC_API void DumpFmt(const char* fmt, ...) MOZ_FORMAT_PRINTF(1, 2); + +} // namespace js + +#endif // js_friend_DumpFunctions_h diff --git a/js/public/friend/ErrorMessages.h b/js/public/friend/ErrorMessages.h new file mode 100644 index 0000000000..1615c8e052 --- /dev/null +++ b/js/public/friend/ErrorMessages.h @@ -0,0 +1,47 @@ +/* -*- 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/. */ + +/* + * SpiderMonkey internal error numbering and error-formatting functionality + * (also for warnings). + * + * This functionality is moderately stable. JSErrNum and js::GetErrorMessage + * are widely used inside SpiderMonkey, and Gecko uses them to produce errors + * identical to those SpiderMonkey itself would produce, in various situations. + * However, the set of error numbers is not stable, error number values are not + * stable, error types are not stable, etc. Use your own error reporting code + * if you can. + */ + +#ifndef js_friend_ErrorMessages_h +#define js_friend_ErrorMessages_h + +#include "jstypes.h" // JS_PUBLIC_API + +struct JSErrorFormatString; + +enum JSErrNum { +#define MSG_DEF(name, count, exception, format) name, +#include "js/friend/ErrorNumbers.msg" +#undef MSG_DEF + JSErr_Limit +}; + +namespace js { + +/** + * A JSErrorCallback suitable for passing to |JS_ReportErrorNumberASCII| and + * similar functions in concert with one of the |JSErrNum| error numbers. + * + * This function is a function only of |errorNumber|: |userRef| and ambient + * state have no effect on its behavior. + */ +extern JS_PUBLIC_API const JSErrorFormatString* GetErrorMessage( + void* userRef, unsigned errorNumber); + +} // namespace js + +#endif // js_friend_ErrorMessages_h diff --git a/js/public/friend/ErrorNumbers.msg b/js/public/friend/ErrorNumbers.msg new file mode 100644 index 0000000000..47f6e640f8 --- /dev/null +++ b/js/public/friend/ErrorNumbers.msg @@ -0,0 +1,821 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +/* + * SpiderMonkey error messages. + * + * These are largely for internal use, but they're exposed publicly as a + * "friend" interface for embedders that want to replicate SpiderMonkey's exact + * error message behavior in particular circumstances. All names, arities, + * types, and messages are subject to change or removal. However, the + * longer-lived the error message, the less likely it is to change. + */ + +/* + * This is the JavaScript error message file. + * + * The format for each JS error message is: + * + * MSG_DEF(<SYMBOLIC_NAME>, <ARGUMENT_COUNT>, <EXCEPTION_NAME>, + * <FORMAT_STRING>) + * + * where ; + * <SYMBOLIC_NAME> is a legal C identifer that will be used in the + * JS engine source. + * + * <ARGUMENT_COUNT> is an integer literal specifying the total number of + * replaceable arguments in the following format string. + * + * <EXCEPTION_NAME> is an enum JSExnType value, defined in js/ErrorReport.h. + * + * <FORMAT_STRING> is a string literal, optionally containing sequences + * {X} where X is an integer representing the argument number that will + * be replaced with a string value when the error is reported. + * + * e.g. + * + * MSG_DEF(JSMSG_NOT_A_SUBSPECIES, 2, JSEXN_TYPEERROR, + * "{0} is not a member of the {1} family") + * + * can be used: + * + * JS_ReportErrorNumberASCII(JSMSG_NOT_A_SUBSPECIES, "Rhino", "Monkey"); + * + * to report: + * + * "TypeError: Rhino is not a member of the Monkey family" + */ + +// clang-format off +MSG_DEF(JSMSG_NOT_AN_ERROR, 0, JSEXN_ERR, "<Error #0 is reserved>") +MSG_DEF(JSMSG_NOT_DEFINED, 1, JSEXN_REFERENCEERR, "{0} is not defined") +MSG_DEF(JSMSG_MORE_ARGS_NEEDED, 4, JSEXN_TYPEERR, "{0}: At least {1} argument{2} required, but only {3} passed") +MSG_DEF(JSMSG_INCOMPATIBLE_PROTO, 3, JSEXN_TYPEERR, "{0}.prototype.{1} called on incompatible {2}") +MSG_DEF(JSMSG_INCOMPATIBLE_PROTO2, 3, JSEXN_TYPEERR, "{0}.prototype[{1}] called on incompatible {2}") +MSG_DEF(JSMSG_NO_CONSTRUCTOR, 1, JSEXN_TYPEERR, "{0} has no constructor") +MSG_DEF(JSMSG_BAD_SORT_ARG, 0, JSEXN_TYPEERR, "invalid Array.prototype.sort argument") +MSG_DEF(JSMSG_BAD_TOSORTED_ARG, 0, JSEXN_TYPEERR, "non-function passed to Array.prototype.toSorted") +MSG_DEF(JSMSG_READ_ONLY, 1, JSEXN_TYPEERR, "{0} is read-only") +MSG_DEF(JSMSG_CANT_DELETE, 1, JSEXN_TYPEERR, "property {0} is non-configurable and can't be deleted") +MSG_DEF(JSMSG_CANT_TRUNCATE_ARRAY, 0, JSEXN_TYPEERR, "can't delete non-configurable array element") +MSG_DEF(JSMSG_NOT_FUNCTION, 1, JSEXN_TYPEERR, "{0} is not a function") +MSG_DEF(JSMSG_PROPERTY_NOT_CALLABLE, 1, JSEXN_TYPEERR, "{0} property is not callable") +MSG_DEF(JSMSG_NOT_CONSTRUCTOR, 1, JSEXN_TYPEERR, "{0} is not a constructor") +MSG_DEF(JSMSG_BOGUS_CONSTRUCTOR, 1, JSEXN_TYPEERR, "{0} constructor can't be used directly") +MSG_DEF(JSMSG_CANT_CONVERT_TO, 2, JSEXN_TYPEERR, "can't convert {0} to {1}") +MSG_DEF(JSMSG_TOPRIMITIVE_NOT_CALLABLE, 2, JSEXN_TYPEERR, "can't convert {0} to {1}: its [Symbol.toPrimitive] property is not a function") +MSG_DEF(JSMSG_TOPRIMITIVE_RETURNED_OBJECT, 2, JSEXN_TYPEERR, "can't convert {0} to {1}: its [Symbol.toPrimitive] method returned an object") +MSG_DEF(JSMSG_NO_PROPERTIES, 1, JSEXN_TYPEERR, "{0} has no properties") +MSG_DEF(JSMSG_PROPERTY_FAIL, 2, JSEXN_TYPEERR, "can't access property {0} of {1}") +MSG_DEF(JSMSG_PROPERTY_FAIL_EXPR, 3, JSEXN_TYPEERR, "can't access property {0}, {1} is {2}") +MSG_DEF(JSMSG_BAD_REGEXP_FLAG, 1, JSEXN_SYNTAXERR, "invalid regular expression flag {0}") +MSG_DEF(JSMSG_INVALID_DATA_VIEW_LENGTH, 0, JSEXN_RANGEERR, "invalid data view length") +MSG_DEF(JSMSG_OFFSET_LARGER_THAN_FILESIZE, 0, JSEXN_RANGEERR, "offset is larger than filesize") +MSG_DEF(JSMSG_OFFSET_OUT_OF_BUFFER, 0, JSEXN_RANGEERR, "start offset is outside the bounds of the buffer") +MSG_DEF(JSMSG_OFFSET_OUT_OF_DATAVIEW, 0, JSEXN_RANGEERR, "offset is outside the bounds of the DataView") +MSG_DEF(JSMSG_SPREAD_TOO_LARGE, 0, JSEXN_RANGEERR, "array too large due to spread operand(s)") +MSG_DEF(JSMSG_BAD_WEAKMAP_KEY, 0, JSEXN_TYPEERR, "cannot use the given object as a weak map key") +MSG_DEF(JSMSG_BAD_GETTER_OR_SETTER, 1, JSEXN_TYPEERR, "invalid {0} usage") +MSG_DEF(JSMSG_BAD_ARRAY_LENGTH, 0, JSEXN_RANGEERR, "invalid array length") +MSG_DEF(JSMSG_SOURCE_ARRAY_TOO_LONG, 0, JSEXN_RANGEERR, "source array is too long") +MSG_DEF(JSMSG_REDECLARED_PREV, 2, JSEXN_NOTE, "Previously declared at line {0}, column {1}") +MSG_DEF(JSMSG_REDECLARED_VAR, 2, JSEXN_SYNTAXERR, "redeclaration of {0} {1}") +MSG_DEF(JSMSG_UNDECLARED_VAR, 1, JSEXN_REFERENCEERR, "assignment to undeclared variable {0}") +MSG_DEF(JSMSG_GET_MISSING_PRIVATE, 0, JSEXN_TYPEERR, "can't access private field or method: object is not the right class") +MSG_DEF(JSMSG_SET_MISSING_PRIVATE, 0, JSEXN_TYPEERR, "can't set private field: object is not the right class") +MSG_DEF(JSMSG_GETTER_ONLY, 1, JSEXN_TYPEERR, "setting getter-only property {0}") +MSG_DEF(JSMSG_PRIVATE_SETTER_ONLY, 0, JSEXN_TYPEERR, "getting private setter-only property") +MSG_DEF(JSMSG_OVERWRITING_ACCESSOR, 1, JSEXN_TYPEERR, "can't overwrite accessor property {0}") +MSG_DEF(JSMSG_INVALID_MAP_ITERABLE, 1, JSEXN_TYPEERR, "iterable for {0} should have array-like objects") +MSG_DEF(JSMSG_NESTING_GENERATOR, 0, JSEXN_TYPEERR, "already executing generator") +MSG_DEF(JSMSG_INCOMPATIBLE_METHOD, 3, JSEXN_TYPEERR, "{0} {1} called on incompatible {2}") +MSG_DEF(JSMSG_BAD_SURROGATE_CHAR, 1, JSEXN_TYPEERR, "bad surrogate character {0}") +MSG_DEF(JSMSG_UTF8_CHAR_TOO_LARGE, 1, JSEXN_TYPEERR, "UTF-8 character {0} too large") +MSG_DEF(JSMSG_MALFORMED_UTF8_CHAR, 1, JSEXN_TYPEERR, "malformed UTF-8 character sequence at offset {0}") +MSG_DEF(JSMSG_BUILTIN_CTOR_NO_NEW, 1, JSEXN_TYPEERR, "calling a builtin {0} constructor without new is forbidden") +MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE, 0, JSEXN_TYPEERR, "reduce of empty array with no initial value") +MSG_DEF(JSMSG_EMPTY_ITERATOR_REDUCE, 0, JSEXN_TYPEERR, "reduce of empty iterator with no initial value") +MSG_DEF(JSMSG_UNEXPECTED_TYPE, 2, JSEXN_TYPEERR, "{0} is {1}") +MSG_DEF(JSMSG_MISSING_FUN_ARG, 2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}") +MSG_DEF(JSMSG_OBJECT_REQUIRED, 1, JSEXN_TYPEERR, "{0} is not a non-null object") +MSG_DEF(JSMSG_OBJECT_REQUIRED_ARG, 3, JSEXN_TYPEERR, "{0} argument of {1} must be an object, got {2}") +MSG_DEF(JSMSG_OBJECT_REQUIRED_WEAKMAP_KEY, 1, JSEXN_TYPEERR, "WeakMap key must be an object, got {0}") +MSG_DEF(JSMSG_OBJECT_REQUIRED_WEAKSET_VAL, 1, JSEXN_TYPEERR, "WeakSet value must be an object, got {0}") +MSG_DEF(JSMSG_OBJECT_REQUIRED_PROP_DESC, 1, JSEXN_TYPEERR, "Property descriptor must be an object, got {0}") +MSG_DEF(JSMSG_OBJECT_REQUIRED_RET_OWNKEYS, 1, JSEXN_TYPEERR, "ownKeys trap must be an object, got {0}") +MSG_DEF(JSMSG_WRONG_TYPE_ARG, 4, JSEXN_TYPEERR, "argument {0} to {1} must be an object of type {2}, got {3}") +MSG_DEF(JSMSG_SET_NON_OBJECT_RECEIVER, 2, JSEXN_TYPEERR, "can't assign to property {1} on {0}: not an object") +MSG_DEF(JSMSG_INVALID_DESCRIPTOR, 0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified") +MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE, 1, JSEXN_TYPEERR, "{0}: Object is not extensible") +MSG_DEF(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE, 2, JSEXN_TYPEERR, "can't define property {1}: {0} is not extensible") +MSG_DEF(JSMSG_CANT_REDEFINE_PROP, 1, JSEXN_TYPEERR, "can't redefine non-configurable property {0}") +MSG_DEF(JSMSG_CANT_REDEFINE_ARRAY_LENGTH, 0, JSEXN_TYPEERR, "can't redefine array length") +MSG_DEF(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH, 0, JSEXN_TYPEERR, "can't define array index property past the end of an array with non-writable length") +MSG_DEF(JSMSG_BAD_GET_SET_FIELD, 1, JSEXN_TYPEERR, "property descriptor's {0} field is neither undefined nor a function") +MSG_DEF(JSMSG_THROW_TYPE_ERROR, 0, JSEXN_TYPEERR, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them") +MSG_DEF(JSMSG_NOT_EXPECTED_TYPE, 3, JSEXN_TYPEERR, "{0}: expected {1}, got {2}") +MSG_DEF(JSMSG_NOT_ITERABLE, 1, JSEXN_TYPEERR, "{0} is not iterable") +MSG_DEF(JSMSG_ALREADY_HAS_PRAGMA, 2, JSEXN_WARN, "{0} is being assigned a {1}, but already has one") +MSG_DEF(JSMSG_GET_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.iterator]() returned a non-object value") +MSG_DEF(JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, 1, JSEXN_TYPEERR, "iterator.{0}() returned a non-object value") +MSG_DEF(JSMSG_CANT_SET_PROTO, 0, JSEXN_TYPEERR, "can't set prototype of this object") +MSG_DEF(JSMSG_CANT_SET_PROTO_OF, 1, JSEXN_TYPEERR, "can't set prototype of {0}") +MSG_DEF(JSMSG_CANT_SET_PROTO_CYCLE, 0, JSEXN_TYPEERR, "can't set prototype: it would cause a prototype chain cycle") +MSG_DEF(JSMSG_INVALID_ARG_TYPE, 3, JSEXN_TYPEERR, "Invalid type: {0} can't be a{1} {2}") +MSG_DEF(JSMSG_TERMINATED, 1, JSEXN_ERR, "Script terminated by timeout at:\n{0}") +MSG_DEF(JSMSG_CANT_CALL_CLASS_CONSTRUCTOR, 0, JSEXN_TYPEERR, "class constructors must be invoked with 'new'") +MSG_DEF(JSMSG_UNINITIALIZED_THIS, 0, JSEXN_REFERENCEERR, "must call super constructor before using 'this' in derived class constructor") +MSG_DEF(JSMSG_BAD_DERIVED_RETURN, 1, JSEXN_TYPEERR, "derived class constructor returned invalid value {0}") +MSG_DEF(JSMSG_BAD_HERITAGE, 2, JSEXN_TYPEERR, "class heritage {0} is {1}") +MSG_DEF(JSMSG_NOT_OBJORNULL, 1, JSEXN_TYPEERR, "{0} is not an object or null") +MSG_DEF(JSMSG_CONSTRUCTOR_DISABLED, 1, JSEXN_TYPEERR, "{0} is disabled") + +// JSON +MSG_DEF(JSMSG_JSON_BAD_PARSE, 3, JSEXN_SYNTAXERR, "JSON.parse: {0} at line {1} column {2} of the JSON data") +MSG_DEF(JSMSG_JSON_CYCLIC_VALUE, 0, JSEXN_TYPEERR, "cyclic object value") + +// Runtime errors +MSG_DEF(JSMSG_ASSIGN_TO_CALL, 0, JSEXN_REFERENCEERR, "cannot assign to function call") +MSG_DEF(JSMSG_ASSIGN_TO_PRIVATE_METHOD, 0, JSEXN_TYPEERR, "cannot assign to private method") +MSG_DEF(JSMSG_BAD_INSTANCEOF_RHS, 1, JSEXN_TYPEERR, "invalid 'instanceof' operand {0}") +MSG_DEF(JSMSG_BAD_PROTOTYPE, 1, JSEXN_TYPEERR, "'prototype' property of {0} is not an object") +MSG_DEF(JSMSG_IN_NOT_OBJECT, 1, JSEXN_TYPEERR, "right-hand side of 'in' should be an object, got {0}") +MSG_DEF(JSMSG_IN_STRING, 2, JSEXN_TYPEERR, "cannot use 'in' operator to search for {0} in {1}") +MSG_DEF(JSMSG_TOO_MANY_CON_SPREADARGS, 0, JSEXN_RANGEERR, "too many constructor arguments") +MSG_DEF(JSMSG_TOO_MANY_FUN_SPREADARGS, 0, JSEXN_RANGEERR, "too many function arguments") +MSG_DEF(JSMSG_UNINITIALIZED_LEXICAL, 1, JSEXN_REFERENCEERR, "can't access lexical declaration '{0}' before initialization") +MSG_DEF(JSMSG_BAD_CONST_ASSIGN, 1, JSEXN_TYPEERR, "invalid assignment to const '{0}'") +MSG_DEF(JSMSG_CANT_DECLARE_GLOBAL_BINDING, 2, JSEXN_TYPEERR, "cannot declare global binding '{0}': {1}") + +// Date +MSG_DEF(JSMSG_INVALID_DATE, 0, JSEXN_RANGEERR, "invalid date") +MSG_DEF(JSMSG_BAD_TOISOSTRING_PROP, 0, JSEXN_TYPEERR, "toISOString property is not callable") + +// String +MSG_DEF(JSMSG_BAD_URI, 0, JSEXN_URIERR, "malformed URI sequence") +MSG_DEF(JSMSG_INVALID_NORMALIZE_FORM, 0, JSEXN_RANGEERR, "form must be one of 'NFC', 'NFD', 'NFKC', or 'NFKD'") +MSG_DEF(JSMSG_NEGATIVE_REPETITION_COUNT, 0, JSEXN_RANGEERR, "repeat count must be non-negative") +MSG_DEF(JSMSG_NOT_A_CODEPOINT, 1, JSEXN_RANGEERR, "{0} is not a valid code point") +MSG_DEF(JSMSG_RESULTING_STRING_TOO_LARGE, 0, JSEXN_RANGEERR, "repeat count must be less than infinity and not overflow maximum string size") +MSG_DEF(JSMSG_FLAGS_UNDEFINED_OR_NULL, 0, JSEXN_TYPEERR, "'flags' property must neither be undefined nor null") +MSG_DEF(JSMSG_REQUIRES_GLOBAL_REGEXP, 1, JSEXN_TYPEERR, "{0} must be called with a global RegExp") + +// Number +MSG_DEF(JSMSG_BAD_RADIX, 0, JSEXN_RANGEERR, "radix must be an integer at least 2 and no greater than 36") +MSG_DEF(JSMSG_PRECISION_RANGE, 1, JSEXN_RANGEERR, "precision {0} out of range") + +// Function +MSG_DEF(JSMSG_BAD_APPLY_ARGS, 1, JSEXN_TYPEERR, "second argument to Function.prototype.{0} must be an array") +MSG_DEF(JSMSG_DEPRECATED_USAGE, 1, JSEXN_REFERENCEERR, "deprecated {0} usage") +MSG_DEF(JSMSG_NO_REST_NAME, 0, JSEXN_SYNTAXERR, "no parameter name after ...") +MSG_DEF(JSMSG_PARAMETER_AFTER_REST, 0, JSEXN_SYNTAXERR, "parameter after rest parameter") +MSG_DEF(JSMSG_TOO_MANY_ARGUMENTS, 0, JSEXN_RANGEERR, "too many arguments provided for a function call") + +// CSP +MSG_DEF(JSMSG_CSP_BLOCKED_EVAL, 0, JSEXN_EVALERR, "call to eval() blocked by CSP") +MSG_DEF(JSMSG_CSP_BLOCKED_FUNCTION, 0, JSEXN_EVALERR, "call to Function() blocked by CSP") +MSG_DEF(JSMSG_CSP_BLOCKED_WASM, 1, JSEXN_WASMCOMPILEERROR, "call to {0}() blocked by CSP") +MSG_DEF(JSMSG_CSP_BLOCKED_SHADOWREALM, 0, JSEXN_EVALERR, "call to ShadowRealm.prototype.evaluate blocked by CSP") + +// Wrappers +MSG_DEF(JSMSG_ACCESSOR_DEF_DENIED, 1, JSEXN_ERR, "Permission denied to define accessor property {0}") +MSG_DEF(JSMSG_DEAD_OBJECT, 0, JSEXN_TYPEERR, "can't access dead object") +MSG_DEF(JSMSG_OBJECT_ACCESS_DENIED, 0, JSEXN_ERR, "Permission denied to access object") +MSG_DEF(JSMSG_PROPERTY_ACCESS_DENIED, 1, JSEXN_ERR, "Permission denied to access property {0}") + +// JSAPI-only (Not thrown as JS exceptions) +MSG_DEF(JSMSG_CANT_CLONE_OBJECT, 0, JSEXN_TYPEERR, "can't clone object") +MSG_DEF(JSMSG_CANT_OPEN, 2, JSEXN_ERR, "can't open {0}: {1}") +MSG_DEF(JSMSG_SUPPORT_NOT_ENABLED, 1, JSEXN_ERR, "support for {0} is not enabled") +MSG_DEF(JSMSG_USER_DEFINED_ERROR, 0, JSEXN_ERR, "JS_ReportError was called") + +// Internal errors +MSG_DEF(JSMSG_ALLOC_OVERFLOW, 0, JSEXN_INTERNALERR, "allocation size overflow") +MSG_DEF(JSMSG_BAD_BYTECODE, 1, JSEXN_INTERNALERR, "unimplemented JavaScript bytecode {0}") +MSG_DEF(JSMSG_BUFFER_TOO_SMALL, 0, JSEXN_INTERNALERR, "buffer too small") +MSG_DEF(JSMSG_BYTECODE_TOO_BIG, 2, JSEXN_INTERNALERR, "bytecode {0} too large (limit {1})") +MSG_DEF(JSMSG_DECODE_FAILURE, 0, JSEXN_INTERNALERR, "failed to decode cache") +MSG_DEF(JSMSG_NEED_DIET, 1, JSEXN_INTERNALERR, "{0} too large") +MSG_DEF(JSMSG_OUT_OF_MEMORY, 0, JSEXN_INTERNALERR, "out of memory") +MSG_DEF(JSMSG_OVER_RECURSED, 0, JSEXN_INTERNALERR, "too much recursion") +MSG_DEF(JSMSG_TOO_DEEP, 1, JSEXN_INTERNALERR, "{0} nested too deeply") +MSG_DEF(JSMSG_UNCAUGHT_EXCEPTION, 1, JSEXN_INTERNALERR, "uncaught exception: {0}") +MSG_DEF(JSMSG_UNKNOWN_FORMAT, 1, JSEXN_INTERNALERR, "unknown bytecode format {0}") +MSG_DEF(JSMSG_UNSAFE_FILENAME, 1, JSEXN_INTERNALERR, "unsafe filename: {0}") + +// Frontend +MSG_DEF(JSMSG_ACCESSOR_WRONG_ARGS, 3, JSEXN_SYNTAXERR, "{0} functions must have {1} argument{2}") +MSG_DEF(JSMSG_ARRAY_INIT_TOO_BIG, 0, JSEXN_INTERNALERR, "array initializer too large") +MSG_DEF(JSMSG_AS_AFTER_IMPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'as' after import *") +MSG_DEF(JSMSG_AS_AFTER_RESERVED_WORD, 1, JSEXN_SYNTAXERR, "missing keyword 'as' after reserved word '{0}'") +MSG_DEF(JSMSG_AS_AFTER_STRING, 0, JSEXN_SYNTAXERR, "missing keyword 'as' after string literal") +MSG_DEF(JSMSG_AWAIT_IN_PARAMETER, 0, JSEXN_SYNTAXERR, "await expression can't be used in parameter") +MSG_DEF(JSMSG_AWAIT_OUTSIDE_ASYNC, 0, JSEXN_SYNTAXERR, "await is only valid in async functions and async generators") +MSG_DEF(JSMSG_AWAIT_OUTSIDE_ASYNC_OR_MODULE, 0, JSEXN_SYNTAXERR, "await is only valid in async functions, async generators and modules") +MSG_DEF(JSMSG_TOP_LEVEL_AWAIT_NOT_SUPPORTED, 0, JSEXN_SYNTAXERR, "top level await is not supported in this context") +MSG_DEF(JSMSG_BAD_ARROW_ARGS, 0, JSEXN_SYNTAXERR, "invalid arrow-function arguments (parentheses around the arrow-function may help)") +MSG_DEF(JSMSG_BAD_COALESCE_MIXING, 0, JSEXN_SYNTAXERR, "cannot use `??` unparenthesized within `||` and `&&` expressions") +MSG_DEF(JSMSG_BAD_CONST_DECL, 0, JSEXN_SYNTAXERR, "missing = in const declaration") +MSG_DEF(JSMSG_BAD_CONTINUE, 0, JSEXN_SYNTAXERR, "continue must be inside loop") +MSG_DEF(JSMSG_BAD_DESTRUCT_ASS, 0, JSEXN_SYNTAXERR, "invalid destructuring assignment operator") +MSG_DEF(JSMSG_BAD_DESTRUCT_TARGET, 0, JSEXN_SYNTAXERR, "invalid destructuring target") +MSG_DEF(JSMSG_BAD_DESTRUCT_PARENS, 0, JSEXN_SYNTAXERR, "destructuring patterns in assignments can't be parenthesized") +MSG_DEF(JSMSG_BAD_DESTRUCT_DECL, 0, JSEXN_SYNTAXERR, "missing = in destructuring declaration") +MSG_DEF(JSMSG_BAD_DUP_ARGS, 0, JSEXN_SYNTAXERR, "duplicate argument names not allowed in this context") +MSG_DEF(JSMSG_BAD_FOR_LEFTSIDE, 0, JSEXN_SYNTAXERR, "invalid for-in/of left-hand side") +MSG_DEF(JSMSG_LEXICAL_DECL_DEFINES_LET,0, JSEXN_SYNTAXERR, "a lexical declaration can't define a 'let' binding") +MSG_DEF(JSMSG_BAD_STARTING_FOROF_LHS, 1, JSEXN_SYNTAXERR, "an expression X in 'for (X of Y)' must not start with '{0}'") +MSG_DEF(JSMSG_BAD_INCOP_OPERAND, 0, JSEXN_SYNTAXERR, "invalid increment/decrement operand") +MSG_DEF(JSMSG_BAD_LEFTSIDE_OF_ASS, 0, JSEXN_SYNTAXERR, "invalid assignment left-hand side") +MSG_DEF(JSMSG_BAD_LOCAL_STRING_EXPORT, 0, JSEXN_SYNTAXERR, "string exports can't be used without 'from'") +MSG_DEF(JSMSG_BAD_METHOD_DEF, 0, JSEXN_SYNTAXERR, "bad method definition") +MSG_DEF(JSMSG_BAD_POW_LEFTSIDE, 0, JSEXN_SYNTAXERR, "unparenthesized unary expression can't appear on the left-hand side of '**'") +MSG_DEF(JSMSG_BAD_PROP_ID, 0, JSEXN_SYNTAXERR, "invalid property id") +MSG_DEF(JSMSG_BAD_RETURN_OR_YIELD, 1, JSEXN_SYNTAXERR, "{0} not in function") +MSG_DEF(JSMSG_BAD_STRICT_ASSIGN, 1, JSEXN_SYNTAXERR, "'{0}' can't be defined or assigned to in strict mode code") +MSG_DEF(JSMSG_BAD_STRICT_ASSIGN_ARGUMENTS, 0, JSEXN_SYNTAXERR, "'arguments' can't be defined or assigned to in strict mode code") +MSG_DEF(JSMSG_BAD_STRICT_ASSIGN_EVAL, 0, JSEXN_SYNTAXERR, "'eval' can't be defined or assigned to in strict mode code") +MSG_DEF(JSMSG_BAD_SWITCH, 0, JSEXN_SYNTAXERR, "invalid switch statement") +MSG_DEF(JSMSG_BAD_SUPER, 0, JSEXN_SYNTAXERR, "invalid use of keyword 'super'") +MSG_DEF(JSMSG_BAD_SUPERPROP, 1, JSEXN_SYNTAXERR, "use of super {0} accesses only valid within methods or eval code within methods") +MSG_DEF(JSMSG_BAD_SUPERPRIVATE, 0, JSEXN_SYNTAXERR, "invalid access of private field on 'super'") +MSG_DEF(JSMSG_BAD_SUPERCALL, 0, JSEXN_SYNTAXERR, "super() is only valid in derived class constructors") +MSG_DEF(JSMSG_BAD_ARGUMENTS, 0, JSEXN_SYNTAXERR, "arguments is not valid in fields") +MSG_DEF(JSMSG_BRACKET_AFTER_LIST, 0, JSEXN_SYNTAXERR, "missing ] after element list") +MSG_DEF(JSMSG_BRACKET_IN_INDEX, 0, JSEXN_SYNTAXERR, "missing ] in index expression") +MSG_DEF(JSMSG_BRACKET_OPENED, 2, JSEXN_NOTE, "[ opened at line {0}, column {1}") +MSG_DEF(JSMSG_CATCH_IDENTIFIER, 0, JSEXN_SYNTAXERR, "missing identifier in catch") +MSG_DEF(JSMSG_CATCH_OR_FINALLY, 0, JSEXN_SYNTAXERR, "missing catch or finally after try") +MSG_DEF(JSMSG_CATCH_WITHOUT_TRY, 0, JSEXN_SYNTAXERR, "catch without try") +MSG_DEF(JSMSG_COLON_AFTER_CASE, 0, JSEXN_SYNTAXERR, "missing : after case label") +MSG_DEF(JSMSG_COLON_AFTER_ID, 0, JSEXN_SYNTAXERR, "missing : after property id") +MSG_DEF(JSMSG_COLON_IN_COND, 0, JSEXN_SYNTAXERR, "missing : in conditional expression") +MSG_DEF(JSMSG_COMP_PROP_UNTERM_EXPR, 0, JSEXN_SYNTAXERR, "missing ] in computed property name") +MSG_DEF(JSMSG_CURLY_AFTER_BODY, 0, JSEXN_SYNTAXERR, "missing } after function body") +MSG_DEF(JSMSG_CURLY_OPENED, 2, JSEXN_NOTE, "{ opened at line {0}, column {1}") +MSG_DEF(JSMSG_CURLY_AFTER_CATCH, 0, JSEXN_SYNTAXERR, "missing } after catch block") +MSG_DEF(JSMSG_CURLY_AFTER_FINALLY, 0, JSEXN_SYNTAXERR, "missing } after finally block") +MSG_DEF(JSMSG_CURLY_AFTER_LIST, 0, JSEXN_SYNTAXERR, "missing } after property list") +MSG_DEF(JSMSG_CURLY_AFTER_TRY, 0, JSEXN_SYNTAXERR, "missing } after try block") +MSG_DEF(JSMSG_CURLY_BEFORE_BODY, 0, JSEXN_SYNTAXERR, "missing { before function body") +MSG_DEF(JSMSG_CURLY_BEFORE_CATCH, 0, JSEXN_SYNTAXERR, "missing { before catch block") +MSG_DEF(JSMSG_CURLY_BEFORE_CLASS, 0, JSEXN_SYNTAXERR, "missing { before class body") +MSG_DEF(JSMSG_CURLY_BEFORE_FINALLY, 0, JSEXN_SYNTAXERR, "missing { before finally block") +MSG_DEF(JSMSG_CURLY_BEFORE_SWITCH, 0, JSEXN_SYNTAXERR, "missing { before switch body") +MSG_DEF(JSMSG_CURLY_BEFORE_TRY, 0, JSEXN_SYNTAXERR, "missing { before try block") +MSG_DEF(JSMSG_CURLY_IN_COMPOUND, 0, JSEXN_SYNTAXERR, "missing } in compound statement") +MSG_DEF(JSMSG_DECLARATION_AFTER_EXPORT,0, JSEXN_SYNTAXERR, "missing declaration after 'export' keyword") +MSG_DEF(JSMSG_DECLARATION_AFTER_IMPORT,0, JSEXN_SYNTAXERR, "missing declaration after 'import' keyword") +MSG_DEF(JSMSG_DEPRECATED_DELETE_OPERAND, 0, JSEXN_SYNTAXERR, "applying the 'delete' operator to an unqualified name is deprecated") +MSG_DEF(JSMSG_DEPRECATED_OCTAL_LITERAL,0, JSEXN_SYNTAXERR, "\"0\"-prefixed octal literals are deprecated; use the \"0o\" prefix instead") +MSG_DEF(JSMSG_DEPRECATED_OCTAL_ESCAPE ,0, JSEXN_SYNTAXERR, "octal escape sequences can't be used in untagged template literals or in strict mode code") +MSG_DEF(JSMSG_DEPRECATED_EIGHT_OR_NINE_ESCAPE, 0, JSEXN_SYNTAXERR, "the escapes \\8 and \\9 can't be used in untagged template literals or in strict mode code") +MSG_DEF(JSMSG_DEPRECATED_PRAGMA, 1, JSEXN_WARN, "Using //@ to indicate {0} pragmas is deprecated. Use //# instead") +MSG_DEF(JSMSG_DUPLICATE_EXPORT_NAME, 1, JSEXN_SYNTAXERR, "duplicate export name '{0}'") +MSG_DEF(JSMSG_DUPLICATE_FORMAL, 1, JSEXN_SYNTAXERR, "duplicate formal argument {0}") +MSG_DEF(JSMSG_DUPLICATE_LABEL, 0, JSEXN_SYNTAXERR, "duplicate label") +MSG_DEF(JSMSG_DUPLICATE_PROPERTY, 1, JSEXN_SYNTAXERR, "property name {0} appears more than once in object literal") +MSG_DEF(JSMSG_DUPLICATE_PROTO_PROPERTY, 0, JSEXN_SYNTAXERR, "property name __proto__ appears more than once in object literal") +MSG_DEF(JSMSG_EQUAL_AS_ASSIGN, 0, JSEXN_SYNTAXERR, "test for equality (==) mistyped as assignment (=)?") +MSG_DEF(JSMSG_EXPORT_DECL_AT_TOP_LEVEL,0, JSEXN_SYNTAXERR, "export declarations may only appear at top level of a module") +MSG_DEF(JSMSG_FINALLY_WITHOUT_TRY, 0, JSEXN_SYNTAXERR, "finally without try") +MSG_DEF(JSMSG_FORBIDDEN_AS_STATEMENT, 1, JSEXN_SYNTAXERR, "{0} can't appear in single-statement context") +MSG_DEF(JSMSG_FOR_AWAIT_OUTSIDE_ASYNC, 0, JSEXN_SYNTAXERR, "for await (... of ...) is only valid in async functions and async generators") +MSG_DEF(JSMSG_FROM_AFTER_IMPORT_CLAUSE, 0, JSEXN_SYNTAXERR, "missing keyword 'from' after import clause") +MSG_DEF(JSMSG_FROM_AFTER_EXPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'from' after export *") +MSG_DEF(JSMSG_GARBAGE_AFTER_INPUT, 2, JSEXN_SYNTAXERR, "unexpected garbage after {0}, starting with {1}") +MSG_DEF(JSMSG_IDSTART_AFTER_NUMBER, 0, JSEXN_SYNTAXERR, "identifier starts immediately after numeric literal") +MSG_DEF(JSMSG_BAD_ESCAPE, 0, JSEXN_SYNTAXERR, "invalid escape sequence") +MSG_DEF(JSMSG_MISSING_PRIVATE_NAME, 0, JSEXN_SYNTAXERR, "'#' not followed by identifier") +MSG_DEF(JSMSG_PRIVATE_DELETE, 0, JSEXN_SYNTAXERR, "private fields can't be deleted") +MSG_DEF(JSMSG_MISSING_PRIVATE_DECL, 1, JSEXN_SYNTAXERR, "reference to undeclared private field or method {0}") +MSG_DEF(JSMSG_ILLEGAL_CHARACTER, 1, JSEXN_SYNTAXERR, "illegal character {0}") +MSG_DEF(JSMSG_IMPORT_META_OUTSIDE_MODULE, 0, JSEXN_SYNTAXERR, "import.meta may only appear in a module") +MSG_DEF(JSMSG_IMPORT_DECL_AT_TOP_LEVEL, 0, JSEXN_SYNTAXERR, "import declarations may only appear at top level of a module") +MSG_DEF(JSMSG_OF_AFTER_FOR_LOOP_DECL, 0, JSEXN_SYNTAXERR, "a declaration in the head of a for-of loop can't have an initializer") +MSG_DEF(JSMSG_IN_AFTER_LEXICAL_FOR_DECL,0,JSEXN_SYNTAXERR, "a lexical declaration in the head of a for-in loop can't have an initializer") +MSG_DEF(JSMSG_INVALID_FOR_IN_DECL_WITH_INIT,0,JSEXN_SYNTAXERR,"for-in loop head declarations may not have initializers") +MSG_DEF(JSMSG_INVALID_ID, 1, JSEXN_SYNTAXERR, "{0} is an invalid identifier") +MSG_DEF(JSMSG_SEPARATOR_IN_ZERO_PREFIXED_NUMBER, 0, JSEXN_SYNTAXERR, "numeric separators '_' are not allowed in numbers that start with '0'") +MSG_DEF(JSMSG_LABEL_NOT_FOUND, 0, JSEXN_SYNTAXERR, "label not found") +MSG_DEF(JSMSG_GENERATOR_LABEL, 0, JSEXN_SYNTAXERR, "generator functions cannot be labelled") +MSG_DEF(JSMSG_FUNCTION_LABEL, 0, JSEXN_SYNTAXERR, "functions cannot be labelled") +MSG_DEF(JSMSG_SLOPPY_FUNCTION_LABEL, 0, JSEXN_SYNTAXERR, "functions can only be labelled inside blocks") +MSG_DEF(JSMSG_LINE_BREAK_AFTER_THROW, 0, JSEXN_SYNTAXERR, "no line break is allowed between 'throw' and its expression") +MSG_DEF(JSMSG_MALFORMED_ESCAPE, 1, JSEXN_SYNTAXERR, "malformed {0} character escape sequence") +MSG_DEF(JSMSG_MISSING_BINARY_DIGITS, 0, JSEXN_SYNTAXERR, "missing binary digits after '0b'") +MSG_DEF(JSMSG_MISSING_EXPONENT, 0, JSEXN_SYNTAXERR, "missing exponent") +MSG_DEF(JSMSG_MISSING_EXPR_AFTER_THROW,0, JSEXN_SYNTAXERR, "throw statement is missing an expression") +MSG_DEF(JSMSG_MISSING_FORMAL, 0, JSEXN_SYNTAXERR, "missing formal parameter") +MSG_DEF(JSMSG_MISSING_HEXDIGITS, 0, JSEXN_SYNTAXERR, "missing hexadecimal digits after '0x'") +MSG_DEF(JSMSG_MISSING_OCTAL_DIGITS, 0, JSEXN_SYNTAXERR, "missing octal digits after '0o'") +MSG_DEF(JSMSG_NUMBER_END_WITH_UNDERSCORE, 0, JSEXN_SYNTAXERR, "underscore can appear only between digits, not after the last digit in a number") +MSG_DEF(JSMSG_NUMBER_MULTIPLE_ADJACENT_UNDERSCORES, 0, JSEXN_SYNTAXERR, "number cannot contain multiple adjacent underscores") +MSG_DEF(JSMSG_MODULE_SPEC_AFTER_FROM, 0, JSEXN_SYNTAXERR, "missing module specifier after 'from' keyword") +MSG_DEF(JSMSG_NAME_AFTER_DOT, 0, JSEXN_SYNTAXERR, "missing name after . operator") +MSG_DEF(JSMSG_NAMED_IMPORTS_OR_NAMESPACE_IMPORT, 0, JSEXN_SYNTAXERR, "expected named imports or namespace import after comma") +MSG_DEF(JSMSG_NO_BINDING_NAME, 0, JSEXN_SYNTAXERR, "missing binding name") +MSG_DEF(JSMSG_NO_EXPORT_NAME, 0, JSEXN_SYNTAXERR, "missing export name") +MSG_DEF(JSMSG_NO_IMPORT_NAME, 0, JSEXN_SYNTAXERR, "missing import name") +MSG_DEF(JSMSG_NO_VARIABLE_NAME, 0, JSEXN_SYNTAXERR, "missing variable name") +MSG_DEF(JSMSG_PAREN_AFTER_ARGS, 0, JSEXN_SYNTAXERR, "missing ) after argument list") +MSG_DEF(JSMSG_PAREN_AFTER_CATCH, 0, JSEXN_SYNTAXERR, "missing ) after catch") +MSG_DEF(JSMSG_PAREN_AFTER_COND, 0, JSEXN_SYNTAXERR, "missing ) after condition") +MSG_DEF(JSMSG_PAREN_AFTER_FOR, 0, JSEXN_SYNTAXERR, "missing ( after for") +MSG_DEF(JSMSG_PAREN_AFTER_FORMAL, 0, JSEXN_SYNTAXERR, "missing ) after formal parameters") +MSG_DEF(JSMSG_PAREN_AFTER_FOR_CTRL, 0, JSEXN_SYNTAXERR, "missing ) after for-loop control") +MSG_DEF(JSMSG_PAREN_AFTER_SWITCH, 0, JSEXN_SYNTAXERR, "missing ) after switch expression") +MSG_DEF(JSMSG_PAREN_AFTER_WITH, 0, JSEXN_SYNTAXERR, "missing ) after with-statement object") +MSG_DEF(JSMSG_PAREN_BEFORE_CATCH, 0, JSEXN_SYNTAXERR, "missing ( before catch") +MSG_DEF(JSMSG_PAREN_BEFORE_COND, 0, JSEXN_SYNTAXERR, "missing ( before condition") +MSG_DEF(JSMSG_PAREN_BEFORE_FORMAL, 0, JSEXN_SYNTAXERR, "missing ( before formal parameters") +MSG_DEF(JSMSG_PAREN_BEFORE_SWITCH, 0, JSEXN_SYNTAXERR, "missing ( before switch expression") +MSG_DEF(JSMSG_PAREN_BEFORE_WITH, 0, JSEXN_SYNTAXERR, "missing ( before with-statement object") +MSG_DEF(JSMSG_PAREN_IN_PAREN, 0, JSEXN_SYNTAXERR, "missing ) in parenthetical") +MSG_DEF(JSMSG_PAREN_AFTER_DECORATOR, 0, JSEXN_SYNTAXERR, "missing ) in decorator expression") +MSG_DEF(JSMSG_RC_AFTER_EXPORT_SPEC_LIST, 0, JSEXN_SYNTAXERR, "missing '}' after export specifier list") +MSG_DEF(JSMSG_RC_AFTER_IMPORT_SPEC_LIST, 0, JSEXN_SYNTAXERR, "missing '}' after module specifier list") +MSG_DEF(JSMSG_RESERVED_ID, 1, JSEXN_SYNTAXERR, "{0} is a reserved identifier") +MSG_DEF(JSMSG_REST_WITH_COMMA, 0, JSEXN_SYNTAXERR, "rest element may not have a trailing comma") +MSG_DEF(JSMSG_REST_WITH_DEFAULT, 0, JSEXN_SYNTAXERR, "rest parameter may not have a default") +MSG_DEF(JSMSG_SELFHOSTED_METHOD_CALL, 0, JSEXN_SYNTAXERR, "self-hosted code may not contain direct method calls. Use callFunction() or callContentFunction()") +MSG_DEF(JSMSG_SEMI_AFTER_FOR_COND, 0, JSEXN_SYNTAXERR, "missing ; after for-loop condition") +MSG_DEF(JSMSG_SEMI_AFTER_FOR_INIT, 0, JSEXN_SYNTAXERR, "missing ; after for-loop initializer") +MSG_DEF(JSMSG_SOURCE_TOO_LONG, 0, JSEXN_RANGEERR, "source is too long") +MSG_DEF(JSMSG_STMT_AFTER_RETURN, 0, JSEXN_WARN, "unreachable code after return statement") +MSG_DEF(JSMSG_STRICT_CODE_WITH, 0, JSEXN_SYNTAXERR, "strict mode code may not contain 'with' statements") +MSG_DEF(JSMSG_STRICT_NON_SIMPLE_PARAMS, 1, JSEXN_SYNTAXERR, "\"use strict\" not allowed in function with {0} parameter") +MSG_DEF(JSMSG_TEMPLSTR_UNTERM_EXPR, 0, JSEXN_SYNTAXERR, "missing } in template string") +MSG_DEF(JSMSG_TOO_MANY_CASES, 0, JSEXN_INTERNALERR, "too many switch cases") +MSG_DEF(JSMSG_TOO_MANY_CON_ARGS, 0, JSEXN_SYNTAXERR, "too many constructor arguments") +MSG_DEF(JSMSG_TOO_MANY_DEFAULTS, 0, JSEXN_SYNTAXERR, "more than one switch default") +MSG_DEF(JSMSG_TOO_MANY_FUN_ARGS, 0, JSEXN_SYNTAXERR, "too many function arguments") +MSG_DEF(JSMSG_TOO_MANY_LOCALS, 0, JSEXN_SYNTAXERR, "too many local variables") +MSG_DEF(JSMSG_TOO_MANY_RESUME_INDEXES, 0, JSEXN_SYNTAXERR, "too many yield/await/finally/case locations") +MSG_DEF(JSMSG_TOUGH_BREAK, 0, JSEXN_SYNTAXERR, "unlabeled break must be inside loop or switch") +MSG_DEF(JSMSG_UNEXPECTED_TOKEN, 2, JSEXN_SYNTAXERR, "expected {0}, got {1}") +MSG_DEF(JSMSG_UNEXPECTED_TOKEN_NO_EXPECT, 1, JSEXN_SYNTAXERR, "unexpected token: {0}") +MSG_DEF(JSMSG_UNEXPECTED_PARAMLIST_END,0, JSEXN_SYNTAXERR, "unexpected end of function parameter list") +MSG_DEF(JSMSG_UNNAMED_CLASS_STMT, 0, JSEXN_SYNTAXERR, "class statement requires a name") +MSG_DEF(JSMSG_UNNAMED_FUNCTION_STMT, 0, JSEXN_SYNTAXERR, "function statement requires a name") +MSG_DEF(JSMSG_UNPAIRED_SURROGATE_EXPORT, 0, JSEXN_SYNTAXERR, "module export name contains unpaired surrogate") +MSG_DEF(JSMSG_UNTERMINATED_COMMENT, 0, JSEXN_SYNTAXERR, "unterminated comment") +MSG_DEF(JSMSG_UNTERMINATED_REGEXP, 0, JSEXN_SYNTAXERR, "unterminated regular expression literal") +MSG_DEF(JSMSG_UNTERMINATED_STATIC_CLASS_BLOCK, 0, JSEXN_SYNTAXERR, "unterminated static class block") +MSG_DEF(JSMSG_EOF_BEFORE_END_OF_LITERAL,1,JSEXN_SYNTAXERR, "{0} literal not terminated before end of script") +MSG_DEF(JSMSG_EOL_BEFORE_END_OF_STRING,1, JSEXN_SYNTAXERR, "{0} string literal contains an unescaped line break") +MSG_DEF(JSMSG_EOF_IN_ESCAPE_IN_LITERAL,1, JSEXN_SYNTAXERR, "reached end of script in the middle of an escape sequence in a {0} literal") +MSG_DEF(JSMSG_USE_ASM_DIRECTIVE_FAIL, 0, JSEXN_SYNTAXERR, "\"use asm\" is only meaningful in the Directive Prologue of a function body") +MSG_DEF(JSMSG_VAR_HIDES_ARG, 1, JSEXN_TYPEERR, "variable {0} redeclares argument") +MSG_DEF(JSMSG_WHILE_AFTER_DO, 0, JSEXN_SYNTAXERR, "missing while after do-loop body") +MSG_DEF(JSMSG_YIELD_IN_PARAMETER, 0, JSEXN_SYNTAXERR, "yield expression can't be used in parameter") +MSG_DEF(JSMSG_YIELD_OUTSIDE_GENERATOR, 0, JSEXN_SYNTAXERR, "yield expression is only valid in generators") +MSG_DEF(JSMSG_BAD_COLUMN_NUMBER, 0, JSEXN_RANGEERR, "column number out of range") +MSG_DEF(JSMSG_BAD_LINE_NUMBER, 0, JSEXN_RANGEERR, "line number out of range") +MSG_DEF(JSMSG_BAD_NEWTARGET, 0, JSEXN_SYNTAXERR, "new.target only allowed within functions") +MSG_DEF(JSMSG_BAD_NEW_OPTIONAL, 0, JSEXN_SYNTAXERR, "new keyword cannot be used with an optional chain") +MSG_DEF(JSMSG_BAD_OPTIONAL_TEMPLATE, 0, JSEXN_SYNTAXERR, "tagged template cannot be used with optional chain") +MSG_DEF(JSMSG_IMPORT_ASSERTIONS_NOT_SUPPORTED, 0, JSEXN_SYNTAXERR, "import assertions are not currently supported") +MSG_DEF(JSMSG_ILLEGAL_PRIVATE_FIELD, 0, JSEXN_SYNTAXERR, "private fields aren't valid in this context") +MSG_DEF(JSMSG_ILLEGAL_PRIVATE_NAME, 0, JSEXN_SYNTAXERR, "private names aren't valid in this context") +MSG_DEF(JSMSG_INVALID_PRIVATE_NAME_PRECEDENCE, 0, JSEXN_SYNTAXERR, "invalid use of private name due to operator precedence") +MSG_DEF(JSMSG_INVALID_PRIVATE_NAME_IN_UNARY_EXPR, 0, JSEXN_SYNTAXERR, "invalid use of private name in unary expression without object reference") +MSG_DEF(JSMSG_ILLEGAL_PRIVATE_EXOTIC, 0, JSEXN_TYPEERR, "private fields or private methods aren't allowed on this exotic object") +MSG_DEF(JSMSG_PRIVATE_FIELD_DOUBLE, 0, JSEXN_TYPEERR, "Initializing an object twice is an error with private fields") +MSG_DEF(JSMSG_PRIVATE_BRAND_DOUBLE, 0, JSEXN_TYPEERR, "Initializing an object twice is an error with private methods") +MSG_DEF(JSMSG_CURLY_AFTER_ASSERT, 0, JSEXN_SYNTAXERR, "missing '{' after assert") +MSG_DEF(JSMSG_DUPLICATE_ASSERT_KEY, 1, JSEXN_SYNTAXERR, "duplicate assert key '{0}'") +MSG_DEF(JSMSG_COLON_AFTER_ASSERT_KEY, 0, JSEXN_SYNTAXERR, "missing : after assert key") +MSG_DEF(JSMSG_ASSERT_STRING_LITERAL, 0, JSEXN_SYNTAXERR, "expected string literal") +MSG_DEF(JSMSG_ASSERT_KEY_EXPECTED, 0, JSEXN_SYNTAXERR, "expected assertion key") +MSG_DEF(JSMSG_DECORATOR_NAME_EXPECTED, 0, JSEXN_SYNTAXERR, "expected property name in decorator expression") +MSG_DEF(JSMSG_CLASS_EXPECTED, 0, JSEXN_SYNTAXERR, "expected class") + +// UTF-8 source text encoding errors +MSG_DEF(JSMSG_BAD_LEADING_UTF8_UNIT, 1, JSEXN_SYNTAXERR, "{0} byte doesn't begin a valid UTF-8 code point") +MSG_DEF(JSMSG_NOT_ENOUGH_CODE_UNITS, 5, JSEXN_SYNTAXERR, "{0} byte in UTF-8 must be followed by {1} byte{2}, but {3} byte{4} present") +MSG_DEF(JSMSG_BAD_TRAILING_UTF8_UNIT, 1, JSEXN_SYNTAXERR, "bad trailing UTF-8 byte {0} doesn't match the pattern 0b10xxxxxx") +MSG_DEF(JSMSG_FORBIDDEN_UTF8_CODE_POINT,2,JSEXN_SYNTAXERR, "{0} isn't a valid code point because {1}") +MSG_DEF(JSMSG_BAD_CODE_UNITS, 1, JSEXN_NOTE, "the code units comprising this invalid code point were: {0}") + +// System encoding errors +MSG_DEF(JSMSG_CANT_CONVERT_TO_NARROW, 0, JSEXN_NOTE, "can't convert to narrow string") +MSG_DEF(JSMSG_CANT_CONVERT_TO_WIDE, 0, JSEXN_NOTE, "can't convert to wide string") +MSG_DEF(JSMSG_CANT_CONVERT_WIDE_TO_UTF8, 0, JSEXN_NOTE, "can't convert wide string to UTF-8") +MSG_DEF(JSMSG_CANT_CONVERT_UTF8_TO_WIDE, 0, JSEXN_NOTE, "can't convert UTF-8 to wide string") + +// SmooshMonkey +MSG_DEF(JSMSG_SMOOSH_COMPILE_ERROR, 1, JSEXN_SYNTAXERR, "{0}") +MSG_DEF(JSMSG_SMOOSH_UNIMPLEMENTED, 1, JSEXN_INTERNALERR, "{0}") + +// asm.js +MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL, 1, JSEXN_TYPEERR, "asm.js type error: {0}") +MSG_DEF(JSMSG_USE_ASM_LINK_FAIL, 1, JSEXN_TYPEERR, "asm.js link error: {0}") +MSG_DEF(JSMSG_USE_ASM_TYPE_OK, 1, JSEXN_WARN, "Successfully compiled asm.js code (total compilation time {0}ms)") +MSG_DEF(JSMSG_USE_ASM_TYPE_OK_NO_TIME, 0, JSEXN_WARN, "Successfully compiled asm.js code ()") + +// wasm +MSG_DEF(JSMSG_WASM_VERBOSE, 1, JSEXN_WARN, "WebAssembly verbose: {0}") +MSG_DEF(JSMSG_WASM_COMPILE_WARNING, 1, JSEXN_WARN, "WebAssembly module validated with warning: {0}") +MSG_DEF(JSMSG_WASM_HUGE_MEMORY_FAILED, 0, JSEXN_WARN, "WebAssembly.Memory failed to reserve a large virtual memory region. This may be due to low configured virtual memory limits on this system.") +MSG_DEF(JSMSG_WASM_COMPILE_ERROR, 1, JSEXN_WASMCOMPILEERROR, "{0}") +MSG_DEF(JSMSG_WASM_BAD_IMPORT_TYPE, 2, JSEXN_WASMLINKERROR, "import object field '{0}' is not a {1}") +MSG_DEF(JSMSG_WASM_BAD_IMPORT_SIG, 2, JSEXN_WASMLINKERROR, "imported function '{0}.{1}' signature mismatch") +MSG_DEF(JSMSG_WASM_BAD_TAG_SIG, 2, JSEXN_WASMLINKERROR, "imported tag '{0}.{1}' signature mismatch") +MSG_DEF(JSMSG_WASM_BAD_IMP_INDEX, 1, JSEXN_WASMLINKERROR, "imported {0} with incompatible index type") +MSG_DEF(JSMSG_WASM_BAD_IMP_SIZE, 1, JSEXN_WASMLINKERROR, "imported {0} with incompatible size") +MSG_DEF(JSMSG_WASM_BAD_IMP_MAX, 1, JSEXN_WASMLINKERROR, "imported {0} with incompatible maximum size") +MSG_DEF(JSMSG_WASM_IMP_SHARED_REQD, 0, JSEXN_WASMLINKERROR, "imported unshared memory but shared required") +MSG_DEF(JSMSG_WASM_IMP_SHARED_BANNED, 0, JSEXN_WASMLINKERROR, "imported shared memory but unshared required") +MSG_DEF(JSMSG_WASM_NO_SHMEM_LINK, 0, JSEXN_WASMLINKERROR, "shared memory is disabled") +MSG_DEF(JSMSG_WASM_NO_MEM64_LINK, 0, JSEXN_WASMLINKERROR, "memory64 is disabled") +MSG_DEF(JSMSG_WASM_BAD_GLOB_MUT_LINK, 0, JSEXN_WASMLINKERROR, "imported global mutability mismatch") +MSG_DEF(JSMSG_WASM_BAD_GLOB_TYPE_LINK, 0, JSEXN_WASMLINKERROR, "imported global type mismatch") +MSG_DEF(JSMSG_WASM_BAD_TBL_TYPE_LINK, 0, JSEXN_WASMLINKERROR, "imported table type mismatch") +MSG_DEF(JSMSG_WASM_IND_CALL_TO_NULL, 0, JSEXN_WASMRUNTIMEERROR, "indirect call to null") +MSG_DEF(JSMSG_WASM_IND_CALL_BAD_SIG, 0, JSEXN_WASMRUNTIMEERROR, "indirect call signature mismatch") +MSG_DEF(JSMSG_WASM_UNREACHABLE, 0, JSEXN_WASMRUNTIMEERROR, "unreachable executed") +MSG_DEF(JSMSG_WASM_INTEGER_OVERFLOW, 0, JSEXN_WASMRUNTIMEERROR, "integer overflow") +MSG_DEF(JSMSG_WASM_INVALID_CONVERSION, 0, JSEXN_WASMRUNTIMEERROR, "invalid conversion to integer") +MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0, JSEXN_WASMRUNTIMEERROR, "integer divide by zero") +MSG_DEF(JSMSG_WASM_OUT_OF_BOUNDS, 0, JSEXN_WASMRUNTIMEERROR, "index out of bounds") +MSG_DEF(JSMSG_WASM_UNALIGNED_ACCESS, 0, JSEXN_WASMRUNTIMEERROR, "unaligned memory access") +MSG_DEF(JSMSG_WASM_WAKE_OVERFLOW, 0, JSEXN_WASMRUNTIMEERROR, "too many woken agents") +MSG_DEF(JSMSG_WASM_DEREF_NULL, 0, JSEXN_WASMRUNTIMEERROR, "dereferencing null pointer") +MSG_DEF(JSMSG_WASM_BAD_CAST, 0, JSEXN_WASMRUNTIMEERROR, "bad cast") +MSG_DEF(JSMSG_WASM_MEM_IMP_LIMIT, 0, JSEXN_WASMRUNTIMEERROR, "too many memory pages") +MSG_DEF(JSMSG_WASM_TABLE_IMP_LIMIT, 0, JSEXN_WASMRUNTIMEERROR, "too many table elements") +MSG_DEF(JSMSG_WASM_ARRAY_IMP_LIMIT, 0, JSEXN_WASMRUNTIMEERROR, "too many array elements") +MSG_DEF(JSMSG_WASM_BAD_RANGE, 2, JSEXN_RANGEERR, "bad {0} {1}") +MSG_DEF(JSMSG_WASM_BAD_GROW, 1, JSEXN_RANGEERR, "failed to grow {0}") +MSG_DEF(JSMSG_WASM_TABLE_OUT_OF_BOUNDS, 0, JSEXN_WASMRUNTIMEERROR, "table index out of bounds") +MSG_DEF(JSMSG_WASM_BAD_ENFORCE_RANGE, 2, JSEXN_TYPEERR, "bad {0} {1}") +MSG_DEF(JSMSG_WASM_BAD_BUF_ARG, 0, JSEXN_TYPEERR, "first argument must be an ArrayBuffer or typed array object") +MSG_DEF(JSMSG_WASM_BAD_MOD_ARG, 0, JSEXN_TYPEERR, "first argument must be a WebAssembly.Module") +MSG_DEF(JSMSG_WASM_BAD_BUF_MOD_ARG, 0, JSEXN_TYPEERR, "first argument must be a WebAssembly.Module, ArrayBuffer or typed array object") +MSG_DEF(JSMSG_WASM_BAD_DESC_ARG, 1, JSEXN_TYPEERR, "first argument must be a {0} descriptor") +MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG, 0, JSEXN_TYPEERR, "second argument must be an object") +MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD, 1, JSEXN_TYPEERR, "import object field '{0}' is not an Object") +MSG_DEF(JSMSG_WASM_BAD_REF_NONNULLABLE_VALUE, 0, JSEXN_TYPEERR, "cannot pass null to non-nullable WebAssembly reference") +MSG_DEF(JSMSG_WASM_BAD_FUNCREF_VALUE, 0, JSEXN_TYPEERR, "can only pass WebAssembly exported functions to funcref") +MSG_DEF(JSMSG_WASM_BAD_ANYREF_VALUE, 0, JSEXN_TYPEERR, "can only pass a WebAssembly GC object to an anyref") +MSG_DEF(JSMSG_WASM_BAD_NULL_EXTERNREF_VALUE, 0, JSEXN_TYPEERR, "can only pass null to nullexternref") +MSG_DEF(JSMSG_WASM_BAD_NULL_FUNCREF_VALUE, 0, JSEXN_TYPEERR, "can only pass null to nullfuncref") +MSG_DEF(JSMSG_WASM_BAD_NULL_ANYREF_VALUE, 0, JSEXN_TYPEERR, "can only pass null to nullref") +MSG_DEF(JSMSG_WASM_BAD_EQREF_VALUE, 0, JSEXN_TYPEERR, "can only pass a WebAssembly GC object to an eqref") +MSG_DEF(JSMSG_WASM_BAD_STRUCTREF_VALUE, 0, JSEXN_TYPEERR, "can only pass a WebAssembly struct object to a structref") +MSG_DEF(JSMSG_WASM_BAD_ARRAYREF_VALUE, 0, JSEXN_TYPEERR, "can only pass a WebAssembly array object to an arrayref") +MSG_DEF(JSMSG_WASM_BAD_TYPEREF_VALUE, 0, JSEXN_TYPEERR, "bad type") +MSG_DEF(JSMSG_WASM_BAD_VAL_TYPE, 0, JSEXN_TYPEERR, "cannot pass v128 to or from JS") +MSG_DEF(JSMSG_WASM_BAD_STRING_VAL_TYPE, 0, JSEXN_TYPEERR, "bad value type") +MSG_DEF(JSMSG_WASM_BAD_STRING_IDX_TYPE, 0, JSEXN_TYPEERR, "bad index type") +MSG_DEF(JSMSG_WASM_BAD_EXN_ARG, 0, JSEXN_TYPEERR, "first argument must be a WebAssembly.Tag") +MSG_DEF(JSMSG_WASM_BAD_EXN_PAYLOAD, 0, JSEXN_TYPEERR, "second argument must be an object") +MSG_DEF(JSMSG_WASM_BAD_EXN_PAYLOAD_LEN, 2, JSEXN_TYPEERR, "expected {0} values but got {1}") +MSG_DEF(JSMSG_WASM_BAD_EXN_TAG, 0, JSEXN_TYPEERR, "exception's tag did not match the provided exception tag") +MSG_DEF(JSMSG_WASM_BAD_EXN_OPTIONS, 0, JSEXN_TYPEERR, "argument cannot be converted to an ExceptionOptions") +MSG_DEF(JSMSG_WASM_BAD_FUNCTION_VALUE, 0, JSEXN_TYPEERR, "second argument must be a function") +MSG_DEF(JSMSG_WASM_NO_TRANSFER, 0, JSEXN_TYPEERR, "cannot transfer WebAssembly/asm.js ArrayBuffer") +MSG_DEF(JSMSG_WASM_TEXT_FAIL, 1, JSEXN_SYNTAXERR, "wasm text error: {0}") +MSG_DEF(JSMSG_WASM_MISSING_MAXIMUM, 0, JSEXN_TYPEERR, "'shared' is true but maximum is not specified") +MSG_DEF(JSMSG_WASM_GLOBAL_IMMUTABLE, 0, JSEXN_TYPEERR, "can't set value of immutable global") +MSG_DEF(JSMSG_WASM_WRONG_NUMBER_OF_VALUES, 2, JSEXN_TYPEERR, "wrong number of values returned by JavaScript to WebAssembly (expected {0}, got {1})") +MSG_DEF(JSMSG_WASM_NONSHARED_WAIT, 0, JSEXN_WASMRUNTIMEERROR, "atomic wait on non-shared memory") +MSG_DEF(JSMSG_WASM_SUPPLY_ONLY_ONE, 2, JSEXN_TYPEERR, "exactly one of {0} and {1} must be supplied") +MSG_DEF(JSMSG_WASM_MISSING_REQUIRED, 1, JSEXN_TYPEERR, "Missing required argument {0}") + +// Proxy +MSG_DEF(JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler returned a non-object, non-null value") +MSG_DEF(JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler didn't return the target object's prototype") +MSG_DEF(JSMSG_PROXY_SETPROTOTYPEOF_RETURNED_FALSE, 0, JSEXN_TYPEERR, "proxy setPrototypeOf handler returned false") +MSG_DEF(JSMSG_INCONSISTENT_SETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy setPrototypeOf handler returned true, even though the target's prototype is immutable because the target is non-extensible") +MSG_DEF(JSMSG_CANT_CHANGE_EXTENSIBILITY, 0, JSEXN_TYPEERR, "can't change object's extensibility") +MSG_DEF(JSMSG_CANT_DEFINE_INVALID, 2, JSEXN_TYPEERR, "proxy can't define an incompatible property descriptor ('{0}', {1})") +MSG_DEF(JSMSG_CANT_DEFINE_NEW, 1, JSEXN_TYPEERR, "proxy can't define a new property '{0}' on a non-extensible object") +MSG_DEF(JSMSG_CANT_DEFINE_NE_AS_NC, 1, JSEXN_TYPEERR, "proxy can't define a non-existent '{0}' property as non-configurable") +MSG_DEF(JSMSG_PROXY_DEFINE_RETURNED_FALSE, 1, JSEXN_TYPEERR, "proxy defineProperty handler returned false for property '{0}'") +MSG_DEF(JSMSG_PROXY_DELETE_RETURNED_FALSE, 1, JSEXN_TYPEERR, "can't delete property '{0}': proxy deleteProperty handler returned false") +MSG_DEF(JSMSG_PROXY_PREVENTEXTENSIONS_RETURNED_FALSE, 0, JSEXN_TYPEERR, "proxy preventExtensions handler returned false") +MSG_DEF(JSMSG_PROXY_SET_RETURNED_FALSE, 1, JSEXN_TYPEERR, "proxy set handler returned false for property '{0}'") +MSG_DEF(JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE, 0, JSEXN_TYPEERR, "proxy can't report an extensible object as non-extensible") +MSG_DEF(JSMSG_CANT_DELETE_NON_EXTENSIBLE, 1, JSEXN_TYPEERR, "proxy can't delete property '{0}' on a non-extensible object") +MSG_DEF(JSMSG_CANT_REPORT_C_AS_NC, 1, JSEXN_TYPEERR, "proxy can't report existing configurable property '{0}' as non-configurable") +MSG_DEF(JSMSG_CANT_REPORT_E_AS_NE, 1, JSEXN_TYPEERR, "proxy can't report an existing own property '{0}' as non-existent on a non-extensible object") +MSG_DEF(JSMSG_CANT_REPORT_INVALID, 2, JSEXN_TYPEERR, "proxy can't report an incompatible property descriptor ('{0}', {1})") +MSG_DEF(JSMSG_CANT_REPORT_NC_AS_NE, 1, JSEXN_TYPEERR, "proxy can't report a non-configurable own property '{0}' as non-existent") +MSG_DEF(JSMSG_CANT_REPORT_NEW, 1, JSEXN_TYPEERR, "proxy can't report a new property '{0}' on a non-extensible object") +MSG_DEF(JSMSG_CANT_REPORT_NE_AS_NC, 1, JSEXN_TYPEERR, "proxy can't report a non-existent property '{0}' as non-configurable") +MSG_DEF(JSMSG_CANT_REPORT_W_AS_NW, 1, JSEXN_TYPEERR, "proxy can't report existing writable property '{0}' as non-writable") +MSG_DEF(JSMSG_CANT_SET_NW_NC, 1, JSEXN_TYPEERR, "proxy can't successfully set a non-writable, non-configurable property '{0}'") +MSG_DEF(JSMSG_CANT_SET_WO_SETTER, 1, JSEXN_TYPEERR, "proxy can't succesfully set an accessor property '{0}' without a setter") +MSG_DEF(JSMSG_CANT_SKIP_NC, 1, JSEXN_TYPEERR, "proxy can't skip a non-configurable property '{0}'") +MSG_DEF(JSMSG_OWNKEYS_STR_SYM, 0, JSEXN_TYPEERR, "proxy [[OwnPropertyKeys]] must return an array with only string and symbol elements") +MSG_DEF(JSMSG_OWNKEYS_DUPLICATE, 1, JSEXN_TYPEERR, "proxy [[OwnPropertyKeys]] can't report property '{0}' more than once") +MSG_DEF(JSMSG_MUST_REPORT_SAME_VALUE, 1, JSEXN_TYPEERR, "proxy must report the same value for the non-writable, non-configurable property '{0}'") +MSG_DEF(JSMSG_MUST_REPORT_UNDEFINED, 1, JSEXN_TYPEERR, "proxy must report undefined for a non-configurable accessor property '{0}' without a getter") +MSG_DEF(JSMSG_PROXY_CONSTRUCT_OBJECT, 0, JSEXN_TYPEERR, "proxy [[Construct]] must return an object") +MSG_DEF(JSMSG_PROXY_EXTENSIBILITY, 0, JSEXN_TYPEERR, "proxy must report same extensiblitity as target") +MSG_DEF(JSMSG_PROXY_GETOWN_OBJORUNDEF, 1, JSEXN_TYPEERR, "proxy [[GetOwnProperty]] must return an object or undefined for property '{0}'") +MSG_DEF(JSMSG_PROXY_REVOKED, 0, JSEXN_TYPEERR, "illegal operation attempted on a revoked proxy") +MSG_DEF(JSMSG_BAD_TRAP, 1, JSEXN_TYPEERR, "proxy handler's {0} trap wasn't undefined, null, or callable") + +// Structured cloning +MSG_DEF(JSMSG_SC_BAD_CLONE_VERSION, 0, JSEXN_ERR, "unsupported structured clone version") +MSG_DEF(JSMSG_SC_BAD_SERIALIZED_DATA, 1, JSEXN_INTERNALERR, "bad serialized structured data ({0})") +MSG_DEF(JSMSG_SC_DUP_TRANSFERABLE, 0, JSEXN_TYPEERR, "duplicate transferable for structured clone") +MSG_DEF(JSMSG_SC_NOT_TRANSFERABLE, 0, JSEXN_TYPEERR, "invalid transferable array for structured clone") +MSG_DEF(JSMSG_SC_UNSUPPORTED_TYPE, 0, JSEXN_TYPEERR, "unsupported type for structured data") +MSG_DEF(JSMSG_SC_NOT_CLONABLE, 1, JSEXN_TYPEERR, "The {0} object cannot be serialized. The Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy HTTP headers will enable this in the future.") +MSG_DEF(JSMSG_SC_NOT_CLONABLE_WITH_COOP_COEP, 1, JSEXN_TYPEERR, "The {0} object cannot be serialized. The Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy HTTP headers can be used to enable this.") +MSG_DEF(JSMSG_SC_SAB_DISABLED, 0, JSEXN_TYPEERR, "SharedArrayBuffer not cloned - shared memory disabled in receiver") +MSG_DEF(JSMSG_SC_SAB_REFCNT_OFLO, 0, JSEXN_TYPEERR, "SharedArrayBuffer has too many references") +MSG_DEF(JSMSG_SC_SHMEM_TRANSFERABLE, 0, JSEXN_TYPEERR, "Shared memory objects must not be in the transfer list") +MSG_DEF(JSMSG_SC_SHMEM_POLICY, 0, JSEXN_TYPEERR, "Policy object must forbid cloning shared memory objects cross-process") + +// Debugger +MSG_DEF(JSMSG_ASSIGN_FUNCTION_OR_NULL, 1, JSEXN_TYPEERR, "value assigned to {0} must be a function or null") +MSG_DEF(JSMSG_DEBUG_BAD_LINE, 0, JSEXN_TYPEERR, "invalid line number") +MSG_DEF(JSMSG_DEBUG_BAD_OFFSET, 0, JSEXN_TYPEERR, "invalid script offset") +MSG_DEF(JSMSG_DEBUG_BREAKPOINT_NOT_ALLOWED, 0, JSEXN_ERR, "breakpoint is not allowed for this opcode") +MSG_DEF(JSMSG_DEBUG_BAD_REFERENT, 2, JSEXN_TYPEERR, "{0} does not refer to {1}") +MSG_DEF(JSMSG_DEBUG_BAD_RESUMPTION, 0, JSEXN_TYPEERR, "debugger resumption value must be undefined, {throw: val}, {return: val}, or null") +MSG_DEF(JSMSG_DEBUG_RESUMPTION_CONFLICT, 0, JSEXN_TYPEERR, "debugger hook returned a resumption, but an earlier hook already did") +MSG_DEF(JSMSG_DEBUG_CANT_DEBUG_GLOBAL, 0, JSEXN_TYPEERR, "passing non-debuggable global to addDebuggee") +MSG_DEF(JSMSG_DEBUG_SAME_COMPARTMENT, 0, JSEXN_TYPEERR, "debugger and debuggee must be in different compartments") +MSG_DEF(JSMSG_DEBUG_CCW_REQUIRED, 1, JSEXN_TYPEERR, "{0}: argument must be an object from a different compartment") +MSG_DEF(JSMSG_DEBUG_COMPARTMENT_MISMATCH, 2, JSEXN_TYPEERR, "{0}: descriptor .{1} property is an object in a different compartment than the target object") +MSG_DEF(JSMSG_DEBUG_LOOP, 0, JSEXN_TYPEERR, "cannot debug an object in same compartment as debugger or a compartment that is already debugging the debugger") +MSG_DEF(JSMSG_DEBUG_NOT_DEBUGGEE, 2, JSEXN_ERR, "{0} is not a debuggee {1}") +MSG_DEF(JSMSG_DEBUG_NOT_DEBUGGING, 0, JSEXN_ERR, "can't set breakpoint: script global is not a debuggee") +MSG_DEF(JSMSG_DEBUG_NOT_IDLE, 0, JSEXN_ERR, "can't start debugging: a debuggee script is on the stack") +MSG_DEF(JSMSG_DEBUG_NOT_ON_STACK, 1, JSEXN_ERR, "{0} is not on stack") +MSG_DEF(JSMSG_DEBUG_NOT_ON_STACK_OR_SUSPENDED, 1, JSEXN_ERR, "{0} is not on stack or suspended") +MSG_DEF(JSMSG_DEBUG_NO_ENV_OBJECT, 0, JSEXN_TYPEERR, "declarative Environments don't have binding objects") +MSG_DEF(JSMSG_DEBUG_PROTO, 2, JSEXN_TYPEERR, "{0}.prototype is not a valid {1} instance") +MSG_DEF(JSMSG_DEBUG_WRONG_OWNER, 1, JSEXN_TYPEERR, "{0} belongs to a different Debugger") +MSG_DEF(JSMSG_DEBUG_OPTIMIZED_OUT, 1, JSEXN_ERR, "variable '{0}' has been optimized out") +MSG_DEF(JSMSG_DEBUG_OPTIMIZED_OUT_FUN, 0, JSEXN_ERR, "function is optimized out") +MSG_DEF(JSMSG_DEBUG_FORCED_RETURN_DISALLOWED, 0, JSEXN_TYPEERR, "can't force return from a generator before the initial yield") +MSG_DEF(JSMSG_DEBUG_RESUMPTION_VALUE_DISALLOWED, 0, JSEXN_TYPEERR, "resumption values are disallowed in this hook") +MSG_DEF(JSMSG_DEBUG_VARIABLE_NOT_FOUND,0, JSEXN_TYPEERR, "variable not found in environment") +MSG_DEF(JSMSG_DEBUG_WRAPPER_IN_WAY, 3, JSEXN_TYPEERR, "{0} is {1}{2}a global object, but a direct reference is required") +MSG_DEF(JSMSG_DEBUGGEE_WOULD_RUN, 2, JSEXN_DEBUGGEEWOULDRUN, "debuggee '{0}:{1}' would run") +MSG_DEF(JSMSG_NOT_CALLABLE_OR_UNDEFINED, 0, JSEXN_TYPEERR, "value is not a function or undefined") +MSG_DEF(JSMSG_NOT_TRACKING_ALLOCATIONS, 1, JSEXN_ERR, "Cannot call {0} without setting trackingAllocationSites to true") +MSG_DEF(JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET, 0, JSEXN_ERR, "Cannot track object allocation, because other tools are already doing so") +MSG_DEF(JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL, 0, JSEXN_TYPEERR, "findScripts query object with 'innermost' property must have 'line' and either 'displayURL', 'url', or 'source'") +MSG_DEF(JSMSG_QUERY_LINE_WITHOUT_URL, 0, JSEXN_TYPEERR, "findScripts query object has 'line' property, but no 'displayURL', 'url', or 'source' property") +MSG_DEF(JSMSG_DEBUG_CANT_SET_OPT_ENV, 1, JSEXN_REFERENCEERR, "can't set '{0}' in an optimized-out environment") +MSG_DEF(JSMSG_DEBUG_INVISIBLE_COMPARTMENT, 0, JSEXN_TYPEERR, "object in compartment marked as invisible to Debugger") +MSG_DEF(JSMSG_DEBUG_CENSUS_BREAKDOWN, 1, JSEXN_TYPEERR, "unrecognized 'by' value in takeCensus breakdown: {0}") +MSG_DEF(JSMSG_DEBUG_PROMISE_NOT_RESOLVED, 0, JSEXN_TYPEERR, "Promise hasn't been resolved") +MSG_DEF(JSMSG_DEBUG_PROMISE_NOT_FULFILLED, 0, JSEXN_TYPEERR, "Promise hasn't been fulfilled") +MSG_DEF(JSMSG_DEBUG_PROMISE_NOT_REJECTED, 0, JSEXN_TYPEERR, "Promise hasn't been rejected") +MSG_DEF(JSMSG_DEBUG_NO_BINARY_SOURCE, 0, JSEXN_ERR, "WebAssembly binary source is not available") + +// Testing functions +MSG_DEF(JSMSG_TESTING_SCRIPTS_ONLY, 0, JSEXN_TYPEERR, "only works on scripts") +MSG_DEF(JSMSG_INVALID_ARGS, 1, JSEXN_ERR, "{0}: invalid arguments") + +// Tracelogger +MSG_DEF(JSMSG_TRACELOGGER_ENABLE_FAIL, 1, JSEXN_ERR, "enabling tracelogger failed: {0}") + +// Intl +MSG_DEF(JSMSG_DATE_NOT_FINITE, 2, JSEXN_RANGEERR, "date value is not finite in {0}.{1}()") +MSG_DEF(JSMSG_DUPLICATE_VARIANT_SUBTAG, 0, JSEXN_RANGEERR, "duplicate variant subtag") +MSG_DEF(JSMSG_INTERNAL_INTL_ERROR, 0, JSEXN_ERR, "internal error while computing Intl data") +MSG_DEF(JSMSG_INVALID_CURRENCY_CODE, 1, JSEXN_RANGEERR, "invalid currency code in NumberFormat(): {0}") +MSG_DEF(JSMSG_INVALID_UNIT_IDENTIFIER, 1, JSEXN_RANGEERR, "invalid unit identifier in NumberFormat(): {0}") +MSG_DEF(JSMSG_INVALID_DIGITS_VALUE, 1, JSEXN_RANGEERR, "invalid digits value: {0}") +MSG_DEF(JSMSG_INVALID_KEY, 1, JSEXN_RANGEERR, "invalid key: {0}") +MSG_DEF(JSMSG_INVALID_LANGUAGE_TAG, 1, JSEXN_RANGEERR, "invalid language tag: {0}") +MSG_DEF(JSMSG_INVALID_LOCALES_ELEMENT, 0, JSEXN_TYPEERR, "invalid element in locales argument") +MSG_DEF(JSMSG_INVALID_LOCALE_MATCHER, 1, JSEXN_RANGEERR, "invalid locale matcher in supportedLocalesOf(): {0}") +MSG_DEF(JSMSG_INVALID_OPTION_VALUE, 2, JSEXN_RANGEERR, "invalid value {1} for option {0}") +MSG_DEF(JSMSG_INVALID_TIME_ZONE, 1, JSEXN_RANGEERR, "invalid time zone in DateTimeFormat(): {0}") +MSG_DEF(JSMSG_INVALID_DATETIME_OPTION, 2, JSEXN_TYPEERR, "can't set option {0} when {1} is used") +MSG_DEF(JSMSG_INVALID_DATETIME_STYLE, 2, JSEXN_TYPEERR, "can't set option {0} in Date.{1}()") +MSG_DEF(JSMSG_UNDEFINED_CURRENCY, 0, JSEXN_TYPEERR, "undefined currency in NumberFormat() with currency style") +MSG_DEF(JSMSG_UNDEFINED_UNIT, 0, JSEXN_TYPEERR, "undefined unit in NumberFormat() with unit style") +MSG_DEF(JSMSG_UNDEFINED_DATE, 2, JSEXN_TYPEERR, "undefined {0}-date in DateTimeFormat.{1}()") +MSG_DEF(JSMSG_UNDEFINED_NUMBER, 3, JSEXN_TYPEERR, "undefined {0} number in {1}.{2}()") +MSG_DEF(JSMSG_UNDEFINED_TYPE, 0, JSEXN_TYPEERR, "missing \"type\" option in DisplayNames()") +MSG_DEF(JSMSG_EXPONENT_TOO_LARGE, 0, JSEXN_RANGEERR, "exponent is too large") +MSG_DEF(JSMSG_NAN_NUMBER_RANGE, 3, JSEXN_RANGEERR, "range can't {0} with NaN in {1}.{2}()") +MSG_DEF(JSMSG_INVALID_NUMBER_OPTION, 2, JSEXN_TYPEERR, "can't set option {0} when {1} is used") +MSG_DEF(JSMSG_UNEQUAL_FRACTION_DIGITS, 0, JSEXN_RANGEERR, "fraction digits must be the same when roundingIncrement is used") + +// RegExp +MSG_DEF(JSMSG_BAD_CLASS_RANGE, 0, JSEXN_SYNTAXERR, "invalid range in character class") +MSG_DEF(JSMSG_ESCAPE_AT_END_OF_REGEXP, 0, JSEXN_SYNTAXERR, "\\ at end of pattern") +MSG_DEF(JSMSG_EXEC_NOT_OBJORNULL, 0, JSEXN_TYPEERR, "RegExp exec method should return object or null") +MSG_DEF(JSMSG_INVALID_DECIMAL_ESCAPE, 0, JSEXN_SYNTAXERR, "invalid decimal escape in regular expression") +MSG_DEF(JSMSG_INVALID_GROUP, 0, JSEXN_SYNTAXERR, "invalid regexp group") +MSG_DEF(JSMSG_INVALID_IDENTITY_ESCAPE, 0, JSEXN_SYNTAXERR, "invalid identity escape in regular expression") +MSG_DEF(JSMSG_INVALID_UNICODE_ESCAPE, 0, JSEXN_SYNTAXERR, "invalid unicode escape in regular expression") +MSG_DEF(JSMSG_MISSING_PAREN, 0, JSEXN_SYNTAXERR, "unterminated parenthetical") +MSG_DEF(JSMSG_NEWREGEXP_FLAGGED, 0, JSEXN_TYPEERR, "can't supply flags when constructing one RegExp from another") +MSG_DEF(JSMSG_NOTHING_TO_REPEAT, 0, JSEXN_SYNTAXERR, "nothing to repeat") +MSG_DEF(JSMSG_NUMBERS_OUT_OF_ORDER, 0, JSEXN_SYNTAXERR, "numbers out of order in {} quantifier.") +MSG_DEF(JSMSG_RANGE_WITH_CLASS_ESCAPE, 0, JSEXN_SYNTAXERR, "character class escape cannot be used in class range in regular expression") +MSG_DEF(JSMSG_RAW_BRACKET_IN_REGEXP, 0, JSEXN_SYNTAXERR, "raw bracket is not allowed in regular expression with unicode flag") +MSG_DEF(JSMSG_TOO_MANY_PARENS, 0, JSEXN_INTERNALERR, "too many parentheses in regular expression") +MSG_DEF(JSMSG_UNICODE_OVERFLOW, 1, JSEXN_SYNTAXERR, "Unicode codepoint must not be greater than 0x10FFFF in {0}") +MSG_DEF(JSMSG_UNMATCHED_RIGHT_PAREN, 0, JSEXN_SYNTAXERR, "unmatched ) in regular expression") +MSG_DEF(JSMSG_UNTERM_CLASS, 0, JSEXN_SYNTAXERR, "unterminated character class") +MSG_DEF(JSMSG_INVALID_PROPERTY_NAME, 0, JSEXN_SYNTAXERR, "invalid property name in regular expression") +MSG_DEF(JSMSG_INVALID_CLASS_PROPERTY_NAME, 0, JSEXN_SYNTAXERR, "invalid class property name in regular expression") +MSG_DEF(JSMSG_INCOMPLETE_QUANTIFIER, 0, JSEXN_SYNTAXERR, "incomplete quantifier in regular expression") +MSG_DEF(JSMSG_INVALID_QUANTIFIER, 0, JSEXN_SYNTAXERR, "invalid quantifier in regular expression") +MSG_DEF(JSMSG_INVALID_CAPTURE_NAME, 0, JSEXN_SYNTAXERR, "invalid capture group name in regular expression") +MSG_DEF(JSMSG_DUPLICATE_CAPTURE_NAME, 0, JSEXN_SYNTAXERR, "duplicate capture group name in regular expression") +MSG_DEF(JSMSG_INVALID_NAMED_REF, 0, JSEXN_SYNTAXERR, "invalid named reference in regular expression") +MSG_DEF(JSMSG_INVALID_NAMED_CAPTURE_REF, 0, JSEXN_SYNTAXERR, "invalid named capture reference in regular expression") +MSG_DEF(JSMSG_INCOMPATIBLE_REGEXP_GETTER, 2, JSEXN_TYPEERR, "RegExp.prototype.{0} getter called on non-RegExp object: {1}") + +// Self-hosting +MSG_DEF(JSMSG_DEFAULT_LOCALE_ERROR, 0, JSEXN_ERR, "internal error getting the default locale") + +// Typed object +MSG_DEF(JSMSG_TYPEDOBJECT_SETTING_IMMUTABLE, 0, JSEXN_ERR, "setting immutable field") + +// Array +MSG_DEF(JSMSG_TOO_LONG_ARRAY, 0, JSEXN_TYPEERR, "Too long array") + +// Typed array +MSG_DEF(JSMSG_BAD_INDEX, 0, JSEXN_RANGEERR, "invalid or out-of-range index") +MSG_DEF(JSMSG_DEFINE_BAD_INDEX, 0, JSEXN_TYPEERR, "can't define element for invalid or out-of-range index") +MSG_DEF(JSMSG_NON_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected ArrayBuffer, but species constructor returned non-ArrayBuffer") +MSG_DEF(JSMSG_SAME_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different ArrayBuffer, but species constructor returned same ArrayBuffer") +MSG_DEF(JSMSG_SHORT_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected ArrayBuffer with at least {0} bytes, but species constructor returns ArrayBuffer with {1} bytes") +MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS, 0, JSEXN_TYPEERR, "invalid arguments") +MSG_DEF(JSMSG_TYPED_ARRAY_DETACHED, 0, JSEXN_TYPEERR, "attempting to access detached ArrayBuffer") +MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS, 2, JSEXN_RANGEERR, "start offset of {0}Array should be a multiple of {1}") +MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_MISALIGNED, 2, JSEXN_RANGEERR, "buffer length for {0}Array should be a multiple of {1}") +MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_LENGTH_BOUNDS, 1, JSEXN_RANGEERR, "size of buffer is too small for {0}Array with byteOffset") +MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_ARRAY_LENGTH_BOUNDS, 1, JSEXN_RANGEERR, "attempting to construct out-of-bounds {0}Array on ArrayBuffer") +MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_TOO_LARGE, 1, JSEXN_RANGEERR, "{0}Array too large") + +MSG_DEF(JSMSG_TYPED_ARRAY_CALL_OR_CONSTRUCT, 1, JSEXN_TYPEERR, "cannot directly {0} builtin %TypedArray%") +MSG_DEF(JSMSG_NON_TYPED_ARRAY_RETURNED, 0, JSEXN_TYPEERR, "constructor didn't return TypedArray object") +MSG_DEF(JSMSG_SHORT_TYPED_ARRAY_RETURNED, 2, JSEXN_TYPEERR, "expected TypedArray of at least length {0}, but constructor returned TypedArray of length {1}") +MSG_DEF(JSMSG_TYPED_ARRAY_NOT_COMPATIBLE, 2, JSEXN_TYPEERR, "{0} elements are incompatible with {1}") +MSG_DEF(JSMSG_ARRAYBUFFER_REQUIRED, 0, JSEXN_TYPEERR, "ArrayBuffer object required") + +MSG_DEF(JSMSG_ARRAYBUFFER_COPY_RANGE, 0, JSEXN_RANGEERR, "ArrayBuffer range incorrect for copying") + +// Shared array buffer +MSG_DEF(JSMSG_SHARED_ARRAY_BAD_LENGTH, 0, JSEXN_RANGEERR, "length argument out of range") +MSG_DEF(JSMSG_NON_SHARED_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected SharedArrayBuffer, but species constructor returned non-SharedArrayBuffer") +MSG_DEF(JSMSG_SAME_SHARED_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different SharedArrayBuffer, but species constructor returned same SharedArrayBuffer") +MSG_DEF(JSMSG_SHORT_SHARED_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected SharedArrayBuffer with at least {0} bytes, but species constructor returns SharedArrayBuffer with {1} bytes") + +// Reflect +MSG_DEF(JSMSG_BAD_PARSE_NODE, 0, JSEXN_INTERNALERR, "bad parse node") + +// Symbol +MSG_DEF(JSMSG_SYMBOL_TO_STRING, 0, JSEXN_TYPEERR, "can't convert symbol to string") +MSG_DEF(JSMSG_SYMBOL_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert symbol to number") + +// Atomics and futexes +MSG_DEF(JSMSG_ATOMICS_BAD_ARRAY, 0, JSEXN_TYPEERR, "invalid array type for the operation") +MSG_DEF(JSMSG_ATOMICS_WAIT_NOT_ALLOWED, 0, JSEXN_TYPEERR, "waiting is not allowed on this thread") + +// XPConnect wrappers and DOM bindings +MSG_DEF(JSMSG_CANT_SET_INTERPOSED, 1, JSEXN_TYPEERR, "unable to set interposed data property '{0}'") +MSG_DEF(JSMSG_CANT_DEFINE_WINDOW_ELEMENT, 0, JSEXN_TYPEERR, "can't define elements on a Window object") +MSG_DEF(JSMSG_CANT_DELETE_WINDOW_ELEMENT, 0, JSEXN_TYPEERR, "can't delete elements from a Window object") +MSG_DEF(JSMSG_CANT_DEFINE_WINDOW_NAMED_PROPERTY, 1, JSEXN_TYPEERR, "can't define property {0} on window's named properties object") +MSG_DEF(JSMSG_CANT_DELETE_WINDOW_NAMED_PROPERTY, 1, JSEXN_TYPEERR, "can't delete property {0} from window's named properties object") +MSG_DEF(JSMSG_CANT_PREVENT_EXTENSIONS, 0, JSEXN_TYPEERR, "can't prevent extensions on this proxy object") +MSG_DEF(JSMSG_CANT_DEFINE_WINDOW_NC, 0, JSEXN_TYPEERR, "can't define non-configurable property on WindowProxy") +MSG_DEF(JSMSG_NO_NAMED_SETTER, 2, JSEXN_TYPEERR, "{0} doesn't have a named property setter for '{1}'") +MSG_DEF(JSMSG_NO_INDEXED_SETTER, 2, JSEXN_TYPEERR, "{0} doesn't have an indexed property setter for '{1}'") +MSG_DEF(JSMSG_NOT_DATA_DESCRIPTOR, 2, JSEXN_TYPEERR, "can't define a getter/setter for element '{1}' of {0} object") + +// Super +MSG_DEF(JSMSG_CANT_DELETE_SUPER, 0, JSEXN_REFERENCEERR, "invalid delete involving 'super'") +MSG_DEF(JSMSG_REINIT_THIS, 0, JSEXN_REFERENCEERR, "super() called twice in derived class constructor") + +// Modules +MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "indirect export not found") +MSG_DEF(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "ambiguous indirect export") +MSG_DEF(JSMSG_MISSING_IMPORT, 0, JSEXN_SYNTAXERR, "import not found") +MSG_DEF(JSMSG_AMBIGUOUS_IMPORT, 0, JSEXN_SYNTAXERR, "ambiguous import") +MSG_DEF(JSMSG_MISSING_EXPORT, 1, JSEXN_SYNTAXERR, "local binding for export '{0}' not found") +MSG_DEF(JSMSG_BAD_MODULE_STATUS, 1, JSEXN_INTERNALERR, "module record has unexpected status: {0}") +MSG_DEF(JSMSG_DYNAMIC_IMPORT_FAILED, 1, JSEXN_TYPEERR, "error loading dynamically imported module: {0}") +MSG_DEF(JSMSG_DYNAMIC_IMPORT_NOT_SUPPORTED, 0, JSEXN_TYPEERR, "Dynamic import not supported in this context") + +// Import maps +MSG_DEF(JSMSG_IMPORT_MAPS_PARSE_FAILED, 1, JSEXN_SYNTAXERR, "Failed to parse import map: Invalid JSON format. {0}") +MSG_DEF(JSMSG_IMPORT_MAPS_NOT_A_MAP, 0, JSEXN_TYPEERR, "the top-level value needs to be a JSON object") +MSG_DEF(JSMSG_IMPORT_MAPS_IMPORTS_NOT_A_MAP, 0, JSEXN_TYPEERR, "the imports top-level key needs to be a JSON object") +MSG_DEF(JSMSG_IMPORT_MAPS_SCOPES_NOT_A_MAP, 0, JSEXN_TYPEERR, "the scopes top-level key needs to be a JSON object") +MSG_DEF(JSMSG_IMPORT_MAPS_SCOPE_VALUE_NOT_A_MAP, 1, JSEXN_TYPEERR, "the value of the scope with prefix '{0}' needs to be a JSON object") + +// Promise +MSG_DEF(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF, 0, JSEXN_TYPEERR, "A promise cannot be resolved with itself.") +MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.") +MSG_DEF(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.") +MSG_DEF(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.") +MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "Promise rejection value is a non-unwrappable cross-compartment wrapper.") +MSG_DEF(JSMSG_PROMISE_ANY_REJECTION, 0, JSEXN_AGGREGATEERR, "No Promise in Promise.any was resolved") + +// Iterator +MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of iterator is not callable") +MSG_DEF(JSMSG_ITERATOR_NO_THROW, 0, JSEXN_TYPEERR, "iterator does not have a 'throw' method") + +// Async Function +MSG_DEF(JSMSG_UNHANDLABLE_PROMISE_REJECTION_WARNING, 0, JSEXN_WARN, "unhandlable error after resolving async function's promise") + +// Async Iteration +MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_SYNTAXERR, "'for await' loop should be used with 'of'") +MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR, 0, JSEXN_TYPEERR, "Not an async generator") +MSG_DEF(JSMSG_NOT_AN_ASYNC_ITERATOR, 0, JSEXN_TYPEERR, "Not an async from sync iterator") +MSG_DEF(JSMSG_GET_ASYNC_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.asyncIterator]() returned a non-object value") +MSG_DEF(JSMSG_SUSPENDED_QUEUE_NOT_EMPTY, 0, JSEXN_INTERNALERR, "Async generator is in invalid state due to debugger interaction") + +// ReadableStream +MSG_DEF(JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG,0, JSEXN_RANGEERR,"'underlyingSource.type' must be \"bytes\" or undefined.") +MSG_DEF(JSMSG_READABLESTREAM_BYTES_TYPE_NOT_IMPLEMENTED, 0, JSEXN_RANGEERR,"support for 'new ReadableStream({ type: \"bytes\" })' is not yet implemented") +MSG_DEF(JSMSG_READABLESTREAM_BYOB_READER_FOR_NON_BYTE_STREAM,0,JSEXN_TYPEERR,"can't get a BYOB reader for a non-byte stream") +MSG_DEF(JSMSG_READABLESTREAM_INVALID_READER_MODE, 0, JSEXN_TYPEERR,"'mode' must be \"byob\" or undefined.") +MSG_DEF(JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, 1, JSEXN_RANGEERR, "'{0}' must be a finite, non-negative number.") +MSG_DEF(JSMSG_READABLESTREAM_LOCKED_METHOD, 1, JSEXN_TYPEERR, "'{0}' can't be called on a locked stream.") +MSG_DEF(JSMSG_READABLESTREAM_LOCKED, 0, JSEXN_TYPEERR, "A Reader may only be created for an unlocked ReadableStream.") +MSG_DEF(JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER, 1, JSEXN_TYPEERR, "{0} requires a ReadableStreamDefaultController.") +MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_OWNED, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may only be called on a reader owned by a stream.") +MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_EMPTY, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may not be called on a reader with read requests.") +MSG_DEF(JSMSG_READABLESTREAMREADER_RELEASED, 0, JSEXN_TYPEERR, "The ReadableStream reader was released.") +MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_CLOSED, 1, JSEXN_TYPEERR, "'{0}' called on a stream already closing.") +MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, 1, JSEXN_TYPEERR, "'{0}' may only be called on a stream in the 'readable' state.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE,0, JSEXN_RANGEERR, "ReadableByteStreamController requires a positive integer or undefined for 'autoAllocateChunkSize'.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK, 1, JSEXN_TYPEERR, "{0} passed a bad chunk.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL, 0, JSEXN_TYPEERR, "The ReadableByteStreamController cannot be closed while the buffer is being filled.") + +// Other Stream-related +MSG_DEF(JSMSG_STREAM_MISSING_HIGHWATERMARK, 0, JSEXN_TYPEERR, "'highWaterMark' must not be undefined.") +MSG_DEF(JSMSG_STREAM_INVALID_HIGHWATERMARK, 0, JSEXN_RANGEERR, "'highWaterMark' must be a non-negative, non-NaN number.") +MSG_DEF(JSMSG_STREAM_CONSUME_ERROR, 0, JSEXN_TYPEERR, "error consuming stream body") + +// (wasm) Response-related +MSG_DEF(JSMSG_WASM_ERROR_CONSUMING_RESPONSE, 0, JSEXN_TYPEERR, "WebAssembly: There was an error consuming the Response") +MSG_DEF(JSMSG_WASM_BAD_RESPONSE_VALUE, 0, JSEXN_TYPEERR, "WebAssembly: Expected Response or Promise resolving to Response") +MSG_DEF(JSMSG_WASM_BAD_RESPONSE_MIME_TYPE, 2, JSEXN_TYPEERR, "WebAssembly: Response has unsupported MIME type '{0}' expected '{1}'") +MSG_DEF(JSMSG_WASM_BAD_RESPONSE_CORS_SAME_ORIGIN, 0, JSEXN_TYPEERR, "WebAssembly: Response.type must be 'basic', 'cors' or 'default'") +MSG_DEF(JSMSG_WASM_BAD_RESPONSE_STATUS, 0, JSEXN_TYPEERR, "WebAssembly: Response does not have ok status") +MSG_DEF(JSMSG_WASM_RESPONSE_ALREADY_CONSUMED, 0, JSEXN_TYPEERR, "WebAssembly: Response already consumed") + +// BigInt +MSG_DEF(JSMSG_BIGINT_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert BigInt to number") +MSG_DEF(JSMSG_NONINTEGER_NUMBER_TO_BIGINT, 1, JSEXN_RANGEERR, "{0} can't be converted to BigInt because it isn't an integer") +MSG_DEF(JSMSG_BIGINT_TOO_LARGE, 0, JSEXN_RANGEERR, "BigInt is too large to allocate") +MSG_DEF(JSMSG_BIGINT_DIVISION_BY_ZERO, 0, JSEXN_RANGEERR, "BigInt division by zero") +MSG_DEF(JSMSG_BIGINT_NEGATIVE_EXPONENT, 0, JSEXN_RANGEERR, "BigInt negative exponent") +MSG_DEF(JSMSG_BIGINT_INVALID_SYNTAX, 0, JSEXN_SYNTAXERR, "invalid BigInt syntax") +MSG_DEF(JSMSG_BIGINT_NOT_SERIALIZABLE, 0, JSEXN_TYPEERR, "BigInt value can't be serialized in JSON") + +// FinalizationRegistry +MSG_DEF(JSMSG_NOT_A_FINALIZATION_REGISTRY, 1, JSEXN_TYPEERR, "{0} is not a FinalizationRegistry") +MSG_DEF(JSMSG_BAD_HELD_VALUE, 0, JSEXN_TYPEERR, "The heldValue parameter passed to FinalizationRegistry.register must not be the same as the target parameter") +MSG_DEF(JSMSG_BAD_UNREGISTER_TOKEN, 1, JSEXN_TYPEERR, "Invalid unregister token passed to {0}") +MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_OBJECT, 0, JSEXN_TYPEERR, "cannot register the given object with a FinalizationRegistry") + +// WeakRef +MSG_DEF(JSMSG_NOT_A_WEAK_REF, 1, JSEXN_TYPEERR, "{0} is not a WeakRef") +MSG_DEF(JSMSG_BAD_WEAKREF_TARGET, 0, JSEXN_TYPEERR, "cannot use the given object as the target of a WeakRef") + +// Iterator Helpers +MSG_DEF(JSMSG_NEGATIVE_LIMIT, 0, JSEXN_RANGEERR, "Iterator limits cannot be negative") + +// Record and Tuple +MSG_DEF(JSMSG_RECORD_TUPLE_NO_OBJECT, 0, JSEXN_TYPEERR, "Record and Tuple can only contain primitive values") +MSG_DEF(JSMSG_RECORD_NO_PROTO, 0, JSEXN_SYNTAXERR, "__proto__ is not a valid literal key in records") +MSG_DEF(JSMSG_RECORD_NO_SYMBOL_KEY, 0, JSEXN_TYPEERR, "Symbols cannot be used as record keys") +MSG_DEF(JSMSG_BAD_TUPLE_INDEX, 0, JSEXN_RANGEERR, "index out of range for tuple") +MSG_DEF(JSMSG_BAD_TUPLE_OBJECT, 0, JSEXN_TYPEERR, "value of TupleObject must be a Tuple") +MSG_DEF(JSMSG_RECORD_TUPLE_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert Record or Tuple to number") + + +// Shadow Realms +MSG_DEF(JSMSG_NOT_SHADOW_REALM, 0, JSEXN_TYPEERR, "Object is not a ShadowRealm") +MSG_DEF(JSMSG_SHADOW_REALM_EVALUATE_NOT_STRING, 0, JSEXN_TYPEERR, "a ShadowRealm can only evaluate a string") +MSG_DEF(JSMSG_SHADOW_REALM_INVALID_RETURN, 0, JSEXN_TYPEERR, "return value not primitive or callable") +MSG_DEF(JSMSG_SHADOW_REALM_WRAP_FAILURE, 0, JSEXN_TYPEERR, "unable to wrap callable return object") +MSG_DEF(JSMSG_SHADOW_REALM_EVALUATE_FAILURE, 0, JSEXN_TYPEERR, "evaluate failed.") +MSG_DEF(JSMSG_SHADOW_REALM_EVALUATE_FAILURE_DETAIL, 1, JSEXN_TYPEERR, "evaluate failed, error was {0}") +MSG_DEF(JSMSG_SHADOW_REALM_WRAPPED_EXECUTION_FAILURE, 0, JSEXN_TYPEERR, "wrapped function threw.") +MSG_DEF(JSMSG_SHADOW_REALM_WRAPPED_EXECUTION_FAILURE_DETAIL, 1, JSEXN_TYPEERR, "wrapped function threw, error was {0}") + +MSG_DEF(JSMSG_SHADOW_REALM_EXPORT_NOT_STRING, 0, JSEXN_TYPEERR, "exportName must be a string") +MSG_DEF(JSMSG_SHADOW_REALM_IMPORTVALUE_FAILED, 0, JSEXN_TYPEERR, "import value failed") +MSG_DEF(JSMSG_SHADOW_REALM_VALUE_NOT_EXPORTED, 0, JSEXN_TYPEERR, "value not exported") + +// Decorators +MSG_DEF(JSMSG_DECORATOR_INVALID_RETURN_TYPE, 0, JSEXN_TYPEERR, "Invalid value returned from decorator: decorators can only return undefined or a callable") + +//clang-format on diff --git a/js/public/friend/JSMEnvironment.h b/js/public/friend/JSMEnvironment.h new file mode 100644 index 0000000000..ad16f2ba62 --- /dev/null +++ b/js/public/friend/JSMEnvironment.h @@ -0,0 +1,91 @@ +/* -*- 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/. */ + +/* + * Functionality provided for the JSM component loader in Gecko, that requires + * its own unique manner of global environment and currently requires assistance + * from SpiderMonkey to do so. + * + * Embedders who aren't Gecko can ignore this header. + */ + +#ifndef js_friend_JSMEnvironment_h +#define js_friend_JSMEnvironment_h + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/GCVector.h" // JS::StackGCVector +#include "js/TypeDecls.h" + +// A 'JSMEnvironment' refers to an environment chain constructed for JSM loading +// in a shared global. Internally it is a NonSyntacticVariablesObject with a +// corresponding extensible LexicalEnvironmentObject that is accessible by +// JS_ExtensibleLexicalEnvironment. The |this| value of that lexical environment +// is the NSVO itself. +// +// Normal global environment (ES6): JSM "global" environment: +// +// * - extensible lexical environment +// | (code runs in this environment; +// | `let/const` bindings go here) +// | +// * - JSMEnvironment (=== `this`) +// | (`var` bindings go here) +// | +// * - extensible lexical environment * - extensible lexical environment +// | (code runs in this environment; | (empty) +// | `let/const` bindings go here) | +// | | +// * - actual global (=== `this`) * - shared JSM global +// (var bindings go here; and (Object, Math, etc. live here) +// Object, Math, etc. live here) + +namespace JS { + +/** + * Allocate a new environment in the current compartment that is compatible with + * JSM shared loading. + */ +extern JS_PUBLIC_API JSObject* NewJSMEnvironment(JSContext* cx); + +/** + * Execute the given script (copied into the current compartment if necessary) + * in the given JSMEnvironment. The script must have been compiled for + * hasNonSyntacticScope. The |jsmEnv| must have been previously allocated by + * |NewJSMEnvironment|. + * + * NOTE: The associated extensible lexical environment is reused. + */ +extern JS_PUBLIC_API bool ExecuteInJSMEnvironment(JSContext* cx, + Handle<JSScript*> script, + Handle<JSObject*> jsmEnv); + +// Additionally, target objects may be specified as required by the Gecko +// subscript loader. These are wrapped in non-syntactic WithEnvironments and +// temporarily placed on the environment chain. +extern JS_PUBLIC_API bool ExecuteInJSMEnvironment( + JSContext* cx, Handle<JSScript*> script, Handle<JSObject*> jsmEnv, + Handle<StackGCVector<JSObject*>> targetObj); + +// Used by native methods to determine the JSMEnvironment of caller if possible +// by looking at stack frames. Returns nullptr if top frame isn't a scripted +// caller in a JSM. +// +// NOTE: This may find NonSyntacticVariablesObject generated by other embedding +// such as a Gecko FrameScript. Caller can check the compartment if needed. +extern JS_PUBLIC_API JSObject* GetJSMEnvironmentOfScriptedCaller(JSContext* cx); + +/** + * Determine if obj is a JSMEnvironment + * + * NOTE: This may return true for an NonSyntacticVariablesObject generated by + * other embedding such as a Gecko FrameScript. Caller can check compartment. + */ +extern JS_PUBLIC_API bool IsJSMEnvironment(JSObject* obj); + +} // namespace JS + +#endif // js_friend_JSMEnvironment_h diff --git a/js/public/friend/PerformanceHint.h b/js/public/friend/PerformanceHint.h new file mode 100644 index 0000000000..ca743e3ab7 --- /dev/null +++ b/js/public/friend/PerformanceHint.h @@ -0,0 +1,29 @@ +/* -*- 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 js_friend_PerformanceHint_h +#define js_friend_PerformanceHint_h + +#include "jstypes.h" // JS_PUBLIC_API +#include "js/TypeDecls.h" // JSContext + +namespace js { +namespace gc { + +// API to let the DOM tell us whether we're currently in pageload. +// +// This currently affects nursery sizing; we tolerate large nursery sizes (and +// hence longer minor GC pauses) during pageload so as not to limit performance. + +enum class PerformanceHint { Normal, InPageLoad }; + +extern JS_PUBLIC_API void SetPerformanceHint(JSContext* cx, + PerformanceHint hint); + +} /* namespace gc */ +} /* namespace js */ + +#endif // js_friend_PerformanceHint_h diff --git a/js/public/friend/StackLimits.h b/js/public/friend/StackLimits.h new file mode 100644 index 0000000000..061c0efcf0 --- /dev/null +++ b/js/public/friend/StackLimits.h @@ -0,0 +1,323 @@ +/* -*- 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 js_friend_StackLimits_h +#define js_friend_StackLimits_h + +#include "mozilla/Attributes.h" // MOZ_ALWAYS_INLINE, MOZ_COLD +#include "mozilla/Likely.h" // MOZ_LIKELY +#include "mozilla/Variant.h" // mozilla::Variant, mozilla::AsVariant + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/HeapAPI.h" // JS::StackKind, JS::StackForTrustedScript, JS::StackForUntrustedScript +#include "js/RootingAPI.h" // JS::RootingContext +#include "js/Stack.h" // JS::NativeStackLimit +#include "js/Utility.h" // JS_STACK_OOM_POSSIBLY_FAIL + +struct JS_PUBLIC_API JSContext; + +#ifndef JS_STACK_GROWTH_DIRECTION +# ifdef __hppa +# define JS_STACK_GROWTH_DIRECTION (1) +# else +# define JS_STACK_GROWTH_DIRECTION (-1) +# endif +#endif + +namespace js { + +class FrontendContext; + +#ifdef __wasi__ +extern MOZ_COLD JS_PUBLIC_API void IncWasiRecursionDepth(JSContext* cx); +extern MOZ_COLD JS_PUBLIC_API void DecWasiRecursionDepth(JSContext* cx); +extern MOZ_COLD JS_PUBLIC_API bool CheckWasiRecursionLimit(JSContext* cx); + +extern MOZ_COLD JS_PUBLIC_API void IncWasiRecursionDepth(FrontendContext* fc); +extern MOZ_COLD JS_PUBLIC_API void DecWasiRecursionDepth(FrontendContext* fc); +extern MOZ_COLD JS_PUBLIC_API bool CheckWasiRecursionLimit(FrontendContext* fc); +#endif // __wasi__ + +// AutoCheckRecursionLimit can be used to check whether we're close to using up +// the C++ stack. +// +// Typical usage is like this: +// +// AutoCheckRecursionLimit recursion(cx); +// if (!recursion.check(cx)) { +// return false; +// } +// +// The check* functions return |false| if we are close to the stack limit. +// They also report an overrecursion error, except for the DontReport variants. +// +// The checkSystem variant gives us a little extra space so we can ensure that +// crucial code is able to run. +// +// checkConservative allows less space than any other check, including a safety +// buffer (as in, it uses the untrusted limit and subtracts a little more from +// it). +class MOZ_RAII AutoCheckRecursionLimit { + [[nodiscard]] MOZ_ALWAYS_INLINE bool checkLimitImpl( + JS::NativeStackLimit limit, void* sp) const; + + MOZ_ALWAYS_INLINE JS::NativeStackLimit getStackLimitSlow(JSContext* cx) const; + MOZ_ALWAYS_INLINE JS::NativeStackLimit getStackLimitHelper( + JSContext* cx, JS::StackKind kind, int extraAllowance) const; + + JS::NativeStackLimit getStackLimit(FrontendContext* fc) const; + + JS_PUBLIC_API JS::StackKind stackKindForCurrentPrincipal(JSContext* cx) const; + + JS_PUBLIC_API void assertMainThread(JSContext* cx) const; + +#ifdef __wasi__ + // The JSContext outlives AutoCheckRecursionLimit so it is safe to use raw + // pointer here. + mozilla::Variant<JSContext*, FrontendContext*> context_; +#endif // __wasi__ + + public: + explicit MOZ_ALWAYS_INLINE AutoCheckRecursionLimit(JSContext* cx) +#ifdef __wasi__ + : context_(mozilla::AsVariant(cx)) +#endif // __wasi__ + { +#ifdef __wasi__ + incWasiRecursionDepth(); +#endif // __wasi__ + } + + explicit MOZ_ALWAYS_INLINE AutoCheckRecursionLimit(FrontendContext* fc) +#ifdef __wasi__ + : context_(mozilla::AsVariant(fc)) +#endif // __wasi__ + { +#ifdef __wasi__ + incWasiRecursionDepth(); +#endif // __wasi__ + } + + MOZ_ALWAYS_INLINE ~AutoCheckRecursionLimit() { +#ifdef __wasi__ + decWasiRecursionDepth(); +#endif // __wasi__ + } + +#ifdef __wasi__ + MOZ_ALWAYS_INLINE void incWasiRecursionDepth() { + if (context_.is<JSContext*>()) { + JSContext* cx = context_.as<JSContext*>(); + IncWasiRecursionDepth(cx); + } else { + FrontendContext* fc = context_.as<FrontendContext*>(); + IncWasiRecursionDepth(fc); + } + } + + MOZ_ALWAYS_INLINE void decWasiRecursionDepth() { + if (context_.is<JSContext*>()) { + JSContext* cx = context_.as<JSContext*>(); + DecWasiRecursionDepth(cx); + } else { + FrontendContext* fc = context_.as<FrontendContext*>(); + DecWasiRecursionDepth(fc); + } + } + + MOZ_ALWAYS_INLINE bool checkWasiRecursionLimit() const { + if (context_.is<JSContext*>()) { + JSContext* cx = context_.as<JSContext*>(); + if (!CheckWasiRecursionLimit(cx)) { + return false; + } + } else { + FrontendContext* fc = context_.as<FrontendContext*>(); + if (!CheckWasiRecursionLimit(fc)) { + return false; + } + } + + return true; + } +#endif // __wasi__ + + AutoCheckRecursionLimit(const AutoCheckRecursionLimit&) = delete; + void operator=(const AutoCheckRecursionLimit&) = delete; + + [[nodiscard]] MOZ_ALWAYS_INLINE bool check(JSContext* cx) const; + [[nodiscard]] MOZ_ALWAYS_INLINE bool check(FrontendContext* fc) const; + [[nodiscard]] MOZ_ALWAYS_INLINE bool checkDontReport(JSContext* cx) const; + [[nodiscard]] MOZ_ALWAYS_INLINE bool checkDontReport( + FrontendContext* fc) const; + [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithExtra(JSContext* cx, + size_t extra) const; + [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithStackPointerDontReport( + JSContext* cx, void* sp) const; + [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithStackPointerDontReport( + FrontendContext* fc, void* sp) const; + + [[nodiscard]] MOZ_ALWAYS_INLINE bool checkConservative(JSContext* cx) const; + [[nodiscard]] MOZ_ALWAYS_INLINE bool checkConservativeDontReport( + JSContext* cx) const; + [[nodiscard]] MOZ_ALWAYS_INLINE bool checkConservativeDontReport( + JS::NativeStackLimit limit) const; + + [[nodiscard]] MOZ_ALWAYS_INLINE bool checkSystem(JSContext* cx) const; + [[nodiscard]] MOZ_ALWAYS_INLINE bool checkSystemDontReport( + JSContext* cx) const; +}; + +extern MOZ_COLD JS_PUBLIC_API void ReportOverRecursed(JSContext* maybecx); +extern MOZ_COLD JS_PUBLIC_API void ReportOverRecursed(FrontendContext* fc); + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkLimitImpl( + JS::NativeStackLimit limit, void* sp) const { + JS_STACK_OOM_POSSIBLY_FAIL(); + +#ifdef __wasi__ + if (!checkWasiRecursionLimit()) { + return false; + } +#endif // __wasi__ + +#if JS_STACK_GROWTH_DIRECTION > 0 + return MOZ_LIKELY(JS::NativeStackLimit(sp) < limit); +#else + return MOZ_LIKELY(JS::NativeStackLimit(sp) > limit); +#endif +} + +MOZ_ALWAYS_INLINE JS::NativeStackLimit +AutoCheckRecursionLimit::getStackLimitSlow(JSContext* cx) const { + JS::StackKind kind = stackKindForCurrentPrincipal(cx); + return getStackLimitHelper(cx, kind, 0); +} + +MOZ_ALWAYS_INLINE JS::NativeStackLimit +AutoCheckRecursionLimit::getStackLimitHelper(JSContext* cx, JS::StackKind kind, + int extraAllowance) const { + assertMainThread(cx); + JS::NativeStackLimit limit = + JS::RootingContext::get(cx)->nativeStackLimit[kind]; +#if JS_STACK_GROWTH_DIRECTION > 0 + limit += extraAllowance; +#else + limit -= extraAllowance; +#endif + return limit; +} + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::check(JSContext* cx) const { + if (MOZ_UNLIKELY(!checkDontReport(cx))) { + ReportOverRecursed(cx); + return false; + } + return true; +} + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::check( + FrontendContext* fc) const { + if (MOZ_UNLIKELY(!checkDontReport(fc))) { + ReportOverRecursed(fc); + return false; + } + return true; +} + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkDontReport( + JSContext* cx) const { + int stackDummy; + return checkWithStackPointerDontReport(cx, &stackDummy); +} + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkDontReport( + FrontendContext* fc) const { + int stackDummy; + return checkWithStackPointerDontReport(fc, &stackDummy); +} + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithStackPointerDontReport( + JSContext* cx, void* sp) const { + // getStackLimitSlow(cx) is pretty slow because it has to do an uninlined + // call to stackKindForCurrentPrincipal to determine which stack limit to + // use. To work around this, check the untrusted limit first to avoid the + // overhead in most cases. + JS::NativeStackLimit untrustedLimit = + getStackLimitHelper(cx, JS::StackForUntrustedScript, 0); + if (MOZ_LIKELY(checkLimitImpl(untrustedLimit, sp))) { + return true; + } + return checkLimitImpl(getStackLimitSlow(cx), sp); +} + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithStackPointerDontReport( + FrontendContext* fc, void* sp) const { + return checkLimitImpl(getStackLimit(fc), sp); +} + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithExtra( + JSContext* cx, size_t extra) const { + char stackDummy; + char* sp = &stackDummy; +#if JS_STACK_GROWTH_DIRECTION > 0 + sp += extra; +#else + sp -= extra; +#endif + if (MOZ_UNLIKELY(!checkWithStackPointerDontReport(cx, sp))) { + ReportOverRecursed(cx); + return false; + } + return true; +} + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkSystem( + JSContext* cx) const { + if (MOZ_UNLIKELY(!checkSystemDontReport(cx))) { + ReportOverRecursed(cx); + return false; + } + return true; +} + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkSystemDontReport( + JSContext* cx) const { + JS::NativeStackLimit limit = + getStackLimitHelper(cx, JS::StackForSystemCode, 0); + int stackDummy; + return checkLimitImpl(limit, &stackDummy); +} + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkConservative( + JSContext* cx) const { + if (MOZ_UNLIKELY(!checkConservativeDontReport(cx))) { + ReportOverRecursed(cx); + return false; + } + return true; +} + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkConservativeDontReport( + JSContext* cx) const { + JS::NativeStackLimit limit = getStackLimitHelper( + cx, JS::StackForUntrustedScript, -4096 * int(sizeof(size_t))); + int stackDummy; + return checkLimitImpl(limit, &stackDummy); +} + +MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkConservativeDontReport( + JS::NativeStackLimit limit) const { + int stackDummy; + return checkLimitImpl(limit, &stackDummy); +} + +} // namespace js + +#endif // js_friend_StackLimits_h diff --git a/js/public/friend/UsageStatistics.h b/js/public/friend/UsageStatistics.h new file mode 100644 index 0000000000..0c23b0128f --- /dev/null +++ b/js/public/friend/UsageStatistics.h @@ -0,0 +1,100 @@ +/* -*- 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/. */ + +/* Telemetry and use counter functionality. */ + +#ifndef js_friend_UsageStatistics_h +#define js_friend_UsageStatistics_h + +#include <stdint.h> // uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; + +/* + * Telemetry reasons passed to the accumulate telemetry callback. + * + * It's OK for these enum values to change as they will be mapped to a fixed + * member of the mozilla::Telemetry::HistogramID enum by the callback. + */ +#define FOR_EACH_JS_METRIC(_) \ + _(GC_REASON_2, Enumeration) \ + _(GC_IS_COMPARTMENTAL, Boolean) \ + _(GC_ZONE_COUNT, QuantityDistribution) \ + _(GC_ZONES_COLLECTED, QuantityDistribution) \ + _(GC_MS, TimeDuration_MS) \ + _(GC_BUDGET_MS_2, TimeDuration_MS) \ + _(GC_BUDGET_WAS_INCREASED, Boolean) \ + _(GC_SLICE_WAS_LONG, Boolean) \ + _(GC_BUDGET_OVERRUN, TimeDuration_US) \ + _(GC_ANIMATION_MS, TimeDuration_MS) \ + _(GC_MAX_PAUSE_MS_2, TimeDuration_MS) \ + _(GC_PREPARE_MS, TimeDuration_MS) \ + _(GC_MARK_MS, TimeDuration_MS) \ + _(GC_SWEEP_MS, TimeDuration_MS) \ + _(GC_COMPACT_MS, TimeDuration_MS) \ + _(GC_MARK_ROOTS_US, TimeDuration_US) \ + _(GC_MARK_GRAY_MS_2, TimeDuration_MS) \ + _(GC_MARK_WEAK_MS, TimeDuration_MS) \ + _(GC_SLICE_MS, TimeDuration_MS) \ + _(GC_SLOW_PHASE, Enumeration) \ + _(GC_SLOW_TASK, Enumeration) \ + _(GC_MMU_50, Percentage) \ + _(GC_RESET, Boolean) \ + _(GC_RESET_REASON, Enumeration) \ + _(GC_NON_INCREMENTAL, Boolean) \ + _(GC_NON_INCREMENTAL_REASON, Enumeration) \ + _(GC_MINOR_REASON, Enumeration) \ + _(GC_MINOR_REASON_LONG, Enumeration) \ + _(GC_MINOR_US, TimeDuration_US) \ + _(GC_NURSERY_BYTES_2, MemoryDistribution) \ + _(GC_PRETENURE_COUNT_2, QuantityDistribution) \ + _(GC_NURSERY_PROMOTION_RATE, Percentage) \ + _(GC_TENURED_SURVIVAL_RATE, Percentage) \ + _(GC_MARK_RATE_2, QuantityDistribution) \ + _(GC_TIME_BETWEEN_S, TimeDuration_S) \ + _(GC_TIME_BETWEEN_SLICES_MS, TimeDuration_MS) \ + _(GC_SLICE_COUNT, QuantityDistribution) \ + _(DESERIALIZE_BYTES, MemoryDistribution) \ + _(DESERIALIZE_ITEMS, QuantityDistribution) \ + _(DESERIALIZE_US, TimeDuration_US) \ + _(GC_EFFECTIVENESS, MemoryDistribution) \ + _(GC_PARALLEL_MARK_SPEEDUP, Integer) \ + _(GC_PARALLEL_MARK_UTILIZATION, Percentage) \ + _(GC_PARALLEL_MARK_INTERRUPTIONS, Integer) \ + _(GC_TASK_START_DELAY_US, TimeDuration_US) + +// clang-format off +#define ENUM_DEF(NAME, _) NAME, +enum class JSMetric { + FOR_EACH_JS_METRIC(ENUM_DEF) + Count +}; +#undef ENUM_DEF +// clang-format on + +using JSAccumulateTelemetryDataCallback = void (*)(JSMetric, uint32_t); + +extern JS_PUBLIC_API void JS_SetAccumulateTelemetryCallback( + JSContext* cx, JSAccumulateTelemetryDataCallback callback); + +/* + * Use counter names passed to the accumulate use counter callback. + * + * It's OK to for these enum values to change as they will be mapped to a + * fixed member of the mozilla::UseCounter enum by the callback. + */ + +enum class JSUseCounter { ASMJS, WASM }; + +using JSSetUseCounterCallback = void (*)(JSObject*, JSUseCounter); + +extern JS_PUBLIC_API void JS_SetSetUseCounterCallback( + JSContext* cx, JSSetUseCounterCallback callback); + +#endif // js_friend_UsageStatistics_h diff --git a/js/public/friend/WindowProxy.h b/js/public/friend/WindowProxy.h new file mode 100644 index 0000000000..9023e9e96b --- /dev/null +++ b/js/public/friend/WindowProxy.h @@ -0,0 +1,105 @@ +/* -*- 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/. */ + +/* + * Window and WindowProxy. + * + * For silly obscure reasons embedders are better off not knowing, the web wants + * every global object to exist as two linked components: a Window component + * that stores global variables and appears in environment chains but can't be + * directly referred to by any script, and a WindowProxy component that + * intermediates access to its Window that *can* be directly referred to by + * script. (Thus the global |window| and |globalThis| properties, |this| in + * global code, the value of |(function() { return this; })()| in non-strict + * mode code, and similar values are WindowProxy objects, not Windows.) + * + * Maintaining an invariant of never exposing a Window to script requires + * substituting in its WindowProxy in a variety of apparently arbitrary (but + * actually *very* carefully and nervously selected) places throughout the + * engine and indeed the universe. + * + * This header defines functions that let embeddings convert from a WindowProxy + * to its Window and vice versa. + * + * If you're not embedding SpiderMonkey in a web browser, you can almost + * certainly ignore this header. + */ + +#ifndef js_friend_WindowProxy_h +#define js_friend_WindowProxy_h + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Class.h" // JSCLASS_IS_GLOBAL +#include "js/Object.h" // JS::GetClass +#include "js/RootingAPI.h" // JS::Handle + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; + +namespace js { + +/** + * Tell the JS engine which Class is used for WindowProxy objects. Used by the + * functions below. + */ +extern JS_PUBLIC_API void SetWindowProxyClass(JSContext* cx, + const JSClass* clasp); + +/** + * Associates a WindowProxy with a Window (global object). `windowProxy` must + * have the Class set by SetWindowProxyClass. + */ +extern JS_PUBLIC_API void SetWindowProxy(JSContext* cx, + JS::Handle<JSObject*> global, + JS::Handle<JSObject*> windowProxy); + +namespace detail { + +extern JS_PUBLIC_API bool IsWindowSlow(JSObject* obj); + +extern JS_PUBLIC_API JSObject* ToWindowProxyIfWindowSlow(JSObject* obj); + +} // namespace detail + +/** + * Returns true iff `obj` is a global object with an associated WindowProxy, + * see SetWindowProxy. + */ +inline bool IsWindow(JSObject* obj) { + if (JS::GetClass(obj)->flags & JSCLASS_IS_GLOBAL) { + return detail::IsWindowSlow(obj); + } + return false; +} + +/** + * Returns true iff `obj` has the WindowProxy Class (see SetWindowProxyClass). + */ +extern JS_PUBLIC_API bool IsWindowProxy(JSObject* obj); + +/** + * If `obj` is a Window, get its associated WindowProxy (or a CCW or dead + * wrapper if the page was navigated away from), else return `obj`. This + * function is infallible and never returns nullptr. + */ +MOZ_ALWAYS_INLINE JSObject* ToWindowProxyIfWindow(JSObject* obj) { + if (JS::GetClass(obj)->flags & JSCLASS_IS_GLOBAL) { + return detail::ToWindowProxyIfWindowSlow(obj); + } + return obj; +} + +/** + * If `obj` is a WindowProxy, get its associated Window (the compartment's + * global), else return `obj`. This function is infallible and never returns + * nullptr. + */ +extern JS_PUBLIC_API JSObject* ToWindowIfWindowProxy(JSObject* obj); + +} // namespace js + +#endif // js_friend_WindowProxy_h diff --git a/js/public/friend/XrayJitInfo.h b/js/public/friend/XrayJitInfo.h new file mode 100644 index 0000000000..ddeec6a44f --- /dev/null +++ b/js/public/friend/XrayJitInfo.h @@ -0,0 +1,57 @@ +/* -*- 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/. */ + +/* + * JIT info so SpiderMonkey can efficiently work with Gecko XrayWrapper + * instances. + * + * This header is completely irrelevant to non-Gecko embedders. + */ + +#ifndef js_friend_XrayJitInfo_h +#define js_friend_XrayJitInfo_h + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +class JS_PUBLIC_API JSObject; + +namespace js { + +class JS_PUBLIC_API BaseProxyHandler; + +} // namespace js + +namespace JS { + +// Callbacks and other information for use by the JITs when optimizing accesses +// on xray wrappers. +struct XrayJitInfo { + // Test whether a proxy handler is a cross compartment xray with no + // security checks. + bool (*isCrossCompartmentXray)(const js::BaseProxyHandler* handler); + + // Test whether xrays in |obj|'s compartment have expandos of their own, + // instead of sharing them with Xrays from other compartments. + bool (*compartmentHasExclusiveExpandos)(JSObject* obj); + + // Proxy reserved slot used by xrays in sandboxes to store their holder + // object. + size_t xrayHolderSlot; + + // Reserved slot used by xray holders to store the xray's expando object. + size_t holderExpandoSlot; + + // Reserved slot used by xray expandos to store a custom prototype. + size_t expandoProtoSlot; +}; + +extern JS_PUBLIC_API void SetXrayJitInfo(XrayJitInfo* info); + +} // namespace JS + +#endif // js_friend_XrayJitInfo_h diff --git a/js/public/shadow/Function.h b/js/public/shadow/Function.h new file mode 100644 index 0000000000..a2c4dcec67 --- /dev/null +++ b/js/public/shadow/Function.h @@ -0,0 +1,51 @@ +/* -*- 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/. */ + +/* Shadow definition of |JSFunction| innards. Do not use this directly! */ + +#ifndef js_shadow_Function_h +#define js_shadow_Function_h + +#include <stdint.h> // uint16_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/CallArgs.h" // JSNative +#include "js/shadow/Object.h" // JS::shadow::Object +#include "js/Value.h" // JS::Value + +class JS_PUBLIC_API JSFunction; +class JSJitInfo; + +namespace JS { + +namespace shadow { + +struct Function : shadow::Object { + enum { + FlagsAndArgCountSlot, + NativeFuncOrInterpretedEnvSlot, + NativeJitInfoOrInterpretedScriptSlot, + AtomSlot + }; + uint32_t flagsAndArgCount() const { + return fixedSlots()[FlagsAndArgCountSlot].toPrivateUint32(); + } + + void* jitInfoOrScript() const { + return fixedSlots()[NativeJitInfoOrInterpretedScriptSlot].toPrivate(); + } + + void setJitInfoOrScript(void* ptr) { + fixedSlots()[NativeJitInfoOrInterpretedScriptSlot] = JS::PrivateValue(ptr); + } +}; + +} // namespace shadow + +} // namespace JS + +#endif // js_shadow_Function_h diff --git a/js/public/shadow/Object.h b/js/public/shadow/Object.h new file mode 100644 index 0000000000..6f77de71ba --- /dev/null +++ b/js/public/shadow/Object.h @@ -0,0 +1,67 @@ +/* -*- 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/. */ + +/* + * Shadow definition of |JSObject| innards. (|js::NativeObject| is more + * accurate, but portions can sometimes be used in some non-native objects.) Do + * not use this directly! + */ + +#ifndef js_shadow_Object_h +#define js_shadow_Object_h + +#include <stddef.h> // size_t + +#include "js/shadow/Shape.h" // JS::shadow::Shape +#include "js/Value.h" // JS::Value + +class JS_PUBLIC_API JSObject; + +namespace JS { + +class JS_PUBLIC_API Value; + +namespace shadow { + +/** + * This layout is shared by all native objects. For non-native objects, the + * shape may always be accessed safely, and other members may be as well, + * depending on the object's specific layout. + */ +struct Object { + shadow::Shape* shape; +#ifndef JS_64BIT + uint32_t padding_; +#endif + Value* slots; + void* _1; + + static constexpr size_t MAX_FIXED_SLOTS = 16; + + size_t numFixedSlots() const { + return (shape->immutableFlags & shadow::Shape::FIXED_SLOTS_MASK) >> + shadow::Shape::FIXED_SLOTS_SHIFT; + } + + Value* fixedSlots() const { + auto address = reinterpret_cast<uintptr_t>(this); + return reinterpret_cast<JS::Value*>(address + sizeof(shadow::Object)); + } + + Value& slotRef(size_t slot) const { + size_t nfixed = numFixedSlots(); + if (slot < nfixed) { + return fixedSlots()[slot]; + } + return slots[slot - nfixed]; + } +}; + +} // namespace shadow + +} // namespace JS + +#endif // js_shadow_Object_h diff --git a/js/public/shadow/Realm.h b/js/public/shadow/Realm.h new file mode 100644 index 0000000000..e3243d3483 --- /dev/null +++ b/js/public/shadow/Realm.h @@ -0,0 +1,38 @@ +/* -*- 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/. */ + +/* Shadow definition of |JS::Realm| innards. Do not use this directly! */ + +#ifndef js_shadow_Realm_h +#define js_shadow_Realm_h + +#include "jstypes.h" // JS_PUBLIC_API + +namespace JS { + +class JS_PUBLIC_API Compartment; +class JS_PUBLIC_API Realm; + +namespace shadow { + +class Realm { + protected: + JS::Compartment* compartment_; + + explicit Realm(JS::Compartment* comp) : compartment_(comp) {} + + public: + JS::Compartment* compartment() { return compartment_; } + static shadow::Realm* get(JS::Realm* realm) { + return reinterpret_cast<shadow::Realm*>(realm); + } +}; + +} // namespace shadow + +} // namespace JS + +#endif // js_shadow_Realm_h diff --git a/js/public/shadow/Shape.h b/js/public/shadow/Shape.h new file mode 100644 index 0000000000..c83d320bf2 --- /dev/null +++ b/js/public/shadow/Shape.h @@ -0,0 +1,65 @@ +/* -*- 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/. */ + +/* + * Shadow definition of |js::BaseShape| and |js::Shape| innards. Do not use + * this directly! + */ + +#ifndef js_shadow_Shape_h +#define js_shadow_Shape_h + +#include <stdint.h> // uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Id.h" // JS::PropertyKey + +struct JSClass; +class JS_PUBLIC_API JSObject; + +namespace JS { + +namespace shadow { + +struct BaseShape { + const JSClass* clasp; + JS::Realm* realm; +}; + +class Shape { + public: + shadow::BaseShape* base; + uint32_t immutableFlags; + + enum class Kind : uint8_t { + // SharedShape or DictionaryShape for NativeObject. + // (Only) these two kinds must have the low bit set, to allow for fast + // is-native checking. + Shared = 1, + Dictionary = 3, + // ProxyShape for ProxyObject. + Proxy = 0, + // WasmGCShape for WasmGCObject. + WasmGC = 2, + }; + + static constexpr uint32_t KIND_SHIFT = 4; + static constexpr uint32_t KIND_MASK = 0b11; + + static constexpr uint32_t FIXED_SLOTS_SHIFT = 6; + static constexpr uint32_t FIXED_SLOTS_MASK = 0x1f << FIXED_SLOTS_SHIFT; + + bool isProxy() const { + return Kind((immutableFlags >> KIND_SHIFT) & KIND_MASK) == Kind::Proxy; + } +}; + +} // namespace shadow + +} // namespace JS + +#endif // js_shadow_Shape_h diff --git a/js/public/shadow/String.h b/js/public/shadow/String.h new file mode 100644 index 0000000000..570c9f189b --- /dev/null +++ b/js/public/shadow/String.h @@ -0,0 +1,120 @@ +/* -*- 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/. */ + +/* Shadow definition of |JSString| innards. Don't use this directly! */ + +#ifndef js_shadow_String_h +#define js_shadow_String_h + +#include <stdint.h> // uint32_t, uintptr_t + +#include "jstypes.h" // JS_PUBLIC_API, js::Bit, js::BitMask, JS_BITS_PER_WORD + +#include "js/TypeDecls.h" // JS::Latin1Char + +class JS_PUBLIC_API JSAtom; +struct JSExternalStringCallbacks; +class JSLinearString; +class JS_PUBLIC_API JSString; + +namespace js { +namespace gc { +struct Cell; +} // namespace gc +} // namespace js + +namespace JS { + +namespace shadow { + +struct String { + static constexpr uint32_t ATOM_BIT = js::Bit(3); + static constexpr uint32_t LINEAR_BIT = js::Bit(4); + static constexpr uint32_t INLINE_CHARS_BIT = js::Bit(6); + static constexpr uint32_t LATIN1_CHARS_BIT = js::Bit(9); + static constexpr uint32_t EXTERNAL_FLAGS = LINEAR_BIT | js::Bit(8); + static constexpr uint32_t TYPE_FLAGS_MASK = js::BitMask(9) - js::BitMask(3); + static constexpr uint32_t PERMANENT_ATOM_MASK = ATOM_BIT | js::Bit(8); + + uintptr_t flags_; +#if JS_BITS_PER_WORD == 32 + uint32_t length_; +#endif + + union { + const JS::Latin1Char* nonInlineCharsLatin1; + const char16_t* nonInlineCharsTwoByte; + JS::Latin1Char inlineStorageLatin1[1]; + char16_t inlineStorageTwoByte[1]; + }; + const JSExternalStringCallbacks* externalCallbacks; + + uint32_t flags() const { return static_cast<uint32_t>(flags_); } + uint32_t length() const { +#if JS_BITS_PER_WORD == 32 + return length_; +#else + return static_cast<uint32_t>(flags_ >> 32); +#endif + } + + static bool isPermanentAtom(const js::gc::Cell* cell) { + uint32_t flags = reinterpret_cast<const String*>(cell)->flags(); + return (flags & PERMANENT_ATOM_MASK) == PERMANENT_ATOM_MASK; + } + + bool isLinear() const { return flags() & LINEAR_BIT; } + bool hasLatin1Chars() const { return flags() & LATIN1_CHARS_BIT; } + + // For hot code, prefer other type queries. + bool isExternal() const { + return (flags() & TYPE_FLAGS_MASK) == EXTERNAL_FLAGS; + } + + const JS::Latin1Char* latin1LinearChars() const { + MOZ_ASSERT(isLinear()); + MOZ_ASSERT(hasLatin1Chars()); + return (flags() & String::INLINE_CHARS_BIT) ? inlineStorageLatin1 + : nonInlineCharsLatin1; + } + + const char16_t* twoByteLinearChars() const { + MOZ_ASSERT(isLinear()); + MOZ_ASSERT(!hasLatin1Chars()); + return (flags() & String::INLINE_CHARS_BIT) ? inlineStorageTwoByte + : nonInlineCharsTwoByte; + } +}; + +inline const String* AsShadowString(const JSString* str) { + return reinterpret_cast<const String*>(str); +} + +inline String* AsShadowString(JSString* str) { + return reinterpret_cast<String*>(str); +} + +inline const String* AsShadowString(const JSLinearString* str) { + return reinterpret_cast<const String*>(str); +} + +inline String* AsShadowString(JSLinearString* str) { + return reinterpret_cast<String*>(str); +} + +inline const String* AsShadowString(const JSAtom* str) { + return reinterpret_cast<const String*>(str); +} + +inline String* AsShadowString(JSAtom* str) { + return reinterpret_cast<String*>(str); +} + +} // namespace shadow + +} // namespace JS + +#endif // js_shadow_String_h diff --git a/js/public/shadow/Symbol.h b/js/public/shadow/Symbol.h new file mode 100644 index 0000000000..a4b40139cc --- /dev/null +++ b/js/public/shadow/Symbol.h @@ -0,0 +1,38 @@ +/* -*- 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/. */ + +/* Shadow definition of |JS::Symbol| innards. Do not use this directly! */ + +#ifndef js_shadow_Symbol_h +#define js_shadow_Symbol_h + +#include <stdint.h> // uint32_t + +namespace js { +namespace gc { +struct Cell; +} // namespace gc +} // namespace js + +namespace JS { + +namespace shadow { + +struct Symbol { + void* _1; + uint32_t code_; + static constexpr uint32_t WellKnownAPILimit = 0x80000000; + + static bool isWellKnownSymbol(const js::gc::Cell* cell) { + return reinterpret_cast<const Symbol*>(cell)->code_ < WellKnownAPILimit; + } +}; + +} // namespace shadow + +} // namespace JS + +#endif // js_shadow_Symbol_h diff --git a/js/public/shadow/Zone.h b/js/public/shadow/Zone.h new file mode 100644 index 0000000000..02cd57a14b --- /dev/null +++ b/js/public/shadow/Zone.h @@ -0,0 +1,126 @@ +/* -*- 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/. */ + +/* Shadow definition of |JS::Zone| innards. Do not use this directly! */ + +#ifndef js_shadow_Zone_h +#define js_shadow_Zone_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Atomics.h" + +#include <stdint.h> // uint8_t, uint32_t + +#include "jspubtd.h" // js::CurrentThreadCanAccessRuntime +#include "jstypes.h" // js::Bit + +struct JS_PUBLIC_API JSRuntime; +class JS_PUBLIC_API JSTracer; + +namespace JS { + +namespace shadow { + +struct Zone { + enum GCState : uint32_t { + NoGC = 0, + Prepare, + MarkBlackOnly, + MarkBlackAndGray, + Sweep, + Finished, + Compact, + VerifyPreBarriers, + + Limit + }; + + using BarrierState = mozilla::Atomic<uint32_t, mozilla::Relaxed>; + + enum Kind : uint8_t { NormalZone, AtomsZone, SystemZone }; + + protected: + JSRuntime* const runtime_; + JSTracer* const barrierTracer_; // A pointer to the JSRuntime's |gcMarker|. + BarrierState needsIncrementalBarrier_; + GCState gcState_ = NoGC; + const Kind kind_; + + Zone(JSRuntime* runtime, JSTracer* barrierTracerArg, Kind kind) + : runtime_(runtime), barrierTracer_(barrierTracerArg), kind_(kind) { + MOZ_ASSERT(!needsIncrementalBarrier()); + } + + public: + bool needsIncrementalBarrier() const { return needsIncrementalBarrier_; } + + JSTracer* barrierTracer() { + MOZ_ASSERT(needsIncrementalBarrier_); + MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtime_)); + return barrierTracer_; + } + + JSRuntime* runtimeFromMainThread() const { + MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtime_)); + return runtime_; + } + + // Note: Unrestricted access to the zone's runtime from an arbitrary + // thread can easily lead to races. Use this method very carefully. + JSRuntime* runtimeFromAnyThread() const { return runtime_; } + + GCState gcState() const { return GCState(uint32_t(gcState_)); } + + static constexpr uint32_t gcStateMask(GCState state) { + static_assert(uint32_t(Limit) < 32); + return js::Bit(state); + } + + bool hasAnyGCState(uint32_t stateMask) const { + return js::Bit(gcState_) & stateMask; + } + + bool wasGCStarted() const { return gcState() != NoGC; } + bool isGCPreparing() const { return gcState() == Prepare; } + bool isGCMarkingBlackOnly() const { return gcState() == MarkBlackOnly; } + bool isGCMarkingBlackAndGray() const { return gcState() == MarkBlackAndGray; } + bool isGCSweeping() const { return gcState() == Sweep; } + bool isGCFinished() const { return gcState() == Finished; } + bool isGCCompacting() const { return gcState() == Compact; } + bool isGCMarking() const { + return hasAnyGCState(gcStateMask(MarkBlackOnly) | + gcStateMask(MarkBlackAndGray)); + } + bool isGCMarkingOrSweeping() const { + return hasAnyGCState(gcStateMask(MarkBlackOnly) | + gcStateMask(MarkBlackAndGray) | gcStateMask(Sweep)); + } + bool isGCMarkingOrVerifyingPreBarriers() const { + return hasAnyGCState(gcStateMask(MarkBlackOnly) | + gcStateMask(MarkBlackAndGray) | + gcStateMask(VerifyPreBarriers)); + } + bool isGCSweepingOrCompacting() const { + return hasAnyGCState(gcStateMask(Sweep) | gcStateMask(Compact)); + } + bool isVerifyingPreBarriers() const { return gcState() == VerifyPreBarriers; } + + bool isAtomsZone() const { return kind_ == AtomsZone; } + bool isSystemZone() const { return kind_ == SystemZone; } + + static shadow::Zone* from(JS::Zone* zone) { + return reinterpret_cast<shadow::Zone*>(zone); + } + static const shadow::Zone* from(const JS::Zone* zone) { + return reinterpret_cast<const shadow::Zone*>(zone); + } +}; + +} // namespace shadow + +} // namespace JS + +#endif // js_shadow_Zone_h |