From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- js/public/AllocPolicy.h | 248 +++++ js/public/AllocationLogging.h | 75 ++ js/public/AllocationRecording.h | 73 ++ js/public/Array.h | 120 +++ js/public/ArrayBuffer.h | 300 ++++++ js/public/ArrayBufferMaybeShared.h | 101 ++ js/public/BigInt.h | 229 +++++ js/public/BuildId.h | 82 ++ js/public/CallAndConstruct.h | 143 +++ js/public/CallArgs.h | 363 +++++++ js/public/CallNonGenericMethod.h | 123 +++ js/public/CharacterEncoding.h | 439 ++++++++ js/public/Class.h | 837 +++++++++++++++ js/public/ComparisonOperators.h | 237 +++++ js/public/CompilationAndEvaluation.h | 233 +++++ js/public/CompileOptions.h | 700 +++++++++++++ js/public/Context.h | 111 ++ js/public/ContextOptions.h | 252 +++++ js/public/Conversions.h | 601 +++++++++++ js/public/Date.h | 208 ++++ js/public/Debug.h | 354 +++++++ js/public/Equality.h | 65 ++ js/public/ErrorInterceptor.h | 45 + js/public/ErrorReport.h | 541 ++++++++++ js/public/Exception.h | 196 ++++ js/public/ForOfIterator.h | 118 +++ js/public/GCAPI.h | 1347 ++++++++++++++++++++++++ js/public/GCAnnotations.h | 116 +++ js/public/GCHashTable.h | 785 ++++++++++++++ js/public/GCPolicyAPI.h | 235 +++++ js/public/GCTypeMacros.h | 45 + js/public/GCVariant.h | 182 ++++ js/public/GCVector.h | 363 +++++++ js/public/GlobalObject.h | 99 ++ js/public/HashTable.h | 38 + js/public/HeapAPI.h | 834 +++++++++++++++ js/public/HelperThreadAPI.h | 40 + js/public/Id.h | 356 +++++++ js/public/Initialization.h | 210 ++++ js/public/Interrupt.h | 41 + js/public/JSON.h | 125 +++ js/public/JitCodeAPI.h | 93 ++ js/public/LocaleSensitive.h | 95 ++ js/public/MapAndSet.h | 85 ++ js/public/MemoryCallbacks.h | 47 + js/public/MemoryFunctions.h | 91 ++ js/public/MemoryMetrics.h | 916 +++++++++++++++++ js/public/Modules.h | 304 ++++++ js/public/Object.h | 147 +++ js/public/OffThreadScriptCompilation.h | 58 ++ js/public/Principals.h | 173 ++++ js/public/Printer.h | 230 +++++ js/public/Printf.h | 34 + js/public/ProfilingCategory.h | 65 ++ js/public/ProfilingFrameIterator.h | 261 +++++ js/public/ProfilingStack.h | 578 +++++++++++ js/public/Promise.h | 599 +++++++++++ js/public/PropertyAndElement.h | 486 +++++++++ js/public/PropertyDescriptor.h | 498 +++++++++ js/public/PropertySpec.h | 447 ++++++++ js/public/ProtoKey.h | 136 +++ js/public/Proxy.h | 768 ++++++++++++++ js/public/Realm.h | 202 ++++ js/public/RealmIterators.h | 81 ++ js/public/RealmOptions.h | 414 ++++++++ js/public/RefCounted.h | 96 ++ js/public/RegExp.h | 106 ++ js/public/RegExpFlags.h | 158 +++ js/public/Result.h | 279 +++++ js/public/RootingAPI.h | 1599 +++++++++++++++++++++++++++++ js/public/SavedFrameAPI.h | 157 +++ js/public/ScalarType.h | 232 +++++ js/public/ScriptPrivate.h | 51 + js/public/ShadowRealmCallbacks.h | 50 + js/public/SharedArrayBuffer.h | 80 ++ js/public/SliceBudget.h | 144 +++ js/public/SourceText.h | 353 +++++++ js/public/StableStringChars.h | 119 +++ js/public/Stack.h | 234 +++++ js/public/StreamConsumer.h | 114 ++ js/public/String.h | 538 ++++++++++ js/public/StructuredClone.h | 782 ++++++++++++++ js/public/SweepingAPI.h | 121 +++ js/public/Symbol.h | 110 ++ js/public/TelemetryTimers.h | 34 + js/public/TraceKind.h | 273 +++++ js/public/TracingAPI.h | 421 ++++++++ js/public/Transcoding.h | 143 +++ js/public/TypeDecls.h | 151 +++ js/public/UbiNode.h | 1210 ++++++++++++++++++++++ js/public/UbiNodeBreadthFirst.h | 271 +++++ js/public/UbiNodeCensus.h | 231 +++++ js/public/UbiNodeDominatorTree.h | 691 +++++++++++++ js/public/UbiNodePostOrder.h | 191 ++++ js/public/UbiNodeShortestPaths.h | 344 +++++++ js/public/UbiNodeUtils.h | 51 + js/public/UniquePtr.h | 56 + js/public/Utility.h | 680 ++++++++++++ js/public/Value.h | 1528 +++++++++++++++++++++++++++ js/public/ValueArray.h | 130 +++ js/public/Vector.h | 38 + js/public/WaitCallbacks.h | 54 + js/public/Warnings.h | 98 ++ js/public/WasmFeatures.h | 172 ++++ js/public/WasmModule.h | 46 + js/public/WeakMap.h | 34 + js/public/WeakMapPtr.h | 48 + js/public/Wrapper.h | 505 +++++++++ js/public/WrapperCallbacks.h | 40 + js/public/Zone.h | 86 ++ js/public/experimental/CTypes.h | 105 ++ js/public/experimental/CodeCoverage.h | 40 + js/public/experimental/CompileScript.h | 120 +++ js/public/experimental/Intl.h | 50 + js/public/experimental/JSStencil.h | 302 ++++++ js/public/experimental/JitInfo.h | 336 ++++++ js/public/experimental/PCCountProfiling.h | 162 +++ js/public/experimental/SourceHook.h | 99 ++ js/public/experimental/TypedData.h | 719 +++++++++++++ js/public/friend/DOMProxy.h | 91 ++ js/public/friend/DumpFunctions.h | 127 +++ js/public/friend/ErrorMessages.h | 47 + js/public/friend/ErrorNumbers.msg | 821 +++++++++++++++ js/public/friend/JSMEnvironment.h | 91 ++ js/public/friend/PerformanceHint.h | 29 + js/public/friend/StackLimits.h | 323 ++++++ js/public/friend/UsageStatistics.h | 100 ++ js/public/friend/WindowProxy.h | 105 ++ js/public/friend/XrayJitInfo.h | 57 + js/public/shadow/Function.h | 51 + js/public/shadow/Object.h | 67 ++ js/public/shadow/Realm.h | 38 + js/public/shadow/Shape.h | 65 ++ js/public/shadow/String.h | 120 +++ js/public/shadow/Symbol.h | 38 + js/public/shadow/Zone.h | 126 +++ 136 files changed, 35496 insertions(+) create mode 100644 js/public/AllocPolicy.h create mode 100644 js/public/AllocationLogging.h create mode 100644 js/public/AllocationRecording.h create mode 100644 js/public/Array.h create mode 100644 js/public/ArrayBuffer.h create mode 100644 js/public/ArrayBufferMaybeShared.h create mode 100644 js/public/BigInt.h create mode 100644 js/public/BuildId.h create mode 100644 js/public/CallAndConstruct.h create mode 100644 js/public/CallArgs.h create mode 100644 js/public/CallNonGenericMethod.h create mode 100644 js/public/CharacterEncoding.h create mode 100644 js/public/Class.h create mode 100644 js/public/ComparisonOperators.h create mode 100644 js/public/CompilationAndEvaluation.h create mode 100644 js/public/CompileOptions.h create mode 100644 js/public/Context.h create mode 100644 js/public/ContextOptions.h create mode 100644 js/public/Conversions.h create mode 100644 js/public/Date.h create mode 100644 js/public/Debug.h create mode 100644 js/public/Equality.h create mode 100644 js/public/ErrorInterceptor.h create mode 100644 js/public/ErrorReport.h create mode 100644 js/public/Exception.h create mode 100644 js/public/ForOfIterator.h create mode 100644 js/public/GCAPI.h create mode 100644 js/public/GCAnnotations.h create mode 100644 js/public/GCHashTable.h create mode 100644 js/public/GCPolicyAPI.h create mode 100644 js/public/GCTypeMacros.h create mode 100644 js/public/GCVariant.h create mode 100644 js/public/GCVector.h create mode 100644 js/public/GlobalObject.h create mode 100644 js/public/HashTable.h create mode 100644 js/public/HeapAPI.h create mode 100644 js/public/HelperThreadAPI.h create mode 100644 js/public/Id.h create mode 100644 js/public/Initialization.h create mode 100644 js/public/Interrupt.h create mode 100644 js/public/JSON.h create mode 100644 js/public/JitCodeAPI.h create mode 100644 js/public/LocaleSensitive.h create mode 100644 js/public/MapAndSet.h create mode 100644 js/public/MemoryCallbacks.h create mode 100644 js/public/MemoryFunctions.h create mode 100644 js/public/MemoryMetrics.h create mode 100644 js/public/Modules.h create mode 100644 js/public/Object.h create mode 100644 js/public/OffThreadScriptCompilation.h create mode 100644 js/public/Principals.h create mode 100644 js/public/Printer.h create mode 100644 js/public/Printf.h create mode 100644 js/public/ProfilingCategory.h create mode 100644 js/public/ProfilingFrameIterator.h create mode 100644 js/public/ProfilingStack.h create mode 100644 js/public/Promise.h create mode 100644 js/public/PropertyAndElement.h create mode 100644 js/public/PropertyDescriptor.h create mode 100644 js/public/PropertySpec.h create mode 100644 js/public/ProtoKey.h create mode 100644 js/public/Proxy.h create mode 100644 js/public/Realm.h create mode 100644 js/public/RealmIterators.h create mode 100644 js/public/RealmOptions.h create mode 100644 js/public/RefCounted.h create mode 100644 js/public/RegExp.h create mode 100644 js/public/RegExpFlags.h create mode 100644 js/public/Result.h create mode 100644 js/public/RootingAPI.h create mode 100644 js/public/SavedFrameAPI.h create mode 100644 js/public/ScalarType.h create mode 100644 js/public/ScriptPrivate.h create mode 100644 js/public/ShadowRealmCallbacks.h create mode 100644 js/public/SharedArrayBuffer.h create mode 100644 js/public/SliceBudget.h create mode 100644 js/public/SourceText.h create mode 100644 js/public/StableStringChars.h create mode 100644 js/public/Stack.h create mode 100644 js/public/StreamConsumer.h create mode 100644 js/public/String.h create mode 100644 js/public/StructuredClone.h create mode 100644 js/public/SweepingAPI.h create mode 100644 js/public/Symbol.h create mode 100644 js/public/TelemetryTimers.h create mode 100644 js/public/TraceKind.h create mode 100644 js/public/TracingAPI.h create mode 100644 js/public/Transcoding.h create mode 100644 js/public/TypeDecls.h create mode 100644 js/public/UbiNode.h create mode 100644 js/public/UbiNodeBreadthFirst.h create mode 100644 js/public/UbiNodeCensus.h create mode 100644 js/public/UbiNodeDominatorTree.h create mode 100644 js/public/UbiNodePostOrder.h create mode 100644 js/public/UbiNodeShortestPaths.h create mode 100644 js/public/UbiNodeUtils.h create mode 100644 js/public/UniquePtr.h create mode 100644 js/public/Utility.h create mode 100644 js/public/Value.h create mode 100644 js/public/ValueArray.h create mode 100644 js/public/Vector.h create mode 100644 js/public/WaitCallbacks.h create mode 100644 js/public/Warnings.h create mode 100644 js/public/WasmFeatures.h create mode 100644 js/public/WasmModule.h create mode 100644 js/public/WeakMap.h create mode 100644 js/public/WeakMapPtr.h create mode 100644 js/public/Wrapper.h create mode 100644 js/public/WrapperCallbacks.h create mode 100644 js/public/Zone.h create mode 100644 js/public/experimental/CTypes.h create mode 100644 js/public/experimental/CodeCoverage.h create mode 100644 js/public/experimental/CompileScript.h create mode 100644 js/public/experimental/Intl.h create mode 100644 js/public/experimental/JSStencil.h create mode 100644 js/public/experimental/JitInfo.h create mode 100644 js/public/experimental/PCCountProfiling.h create mode 100644 js/public/experimental/SourceHook.h create mode 100644 js/public/experimental/TypedData.h create mode 100644 js/public/friend/DOMProxy.h create mode 100644 js/public/friend/DumpFunctions.h create mode 100644 js/public/friend/ErrorMessages.h create mode 100644 js/public/friend/ErrorNumbers.msg create mode 100644 js/public/friend/JSMEnvironment.h create mode 100644 js/public/friend/PerformanceHint.h create mode 100644 js/public/friend/StackLimits.h create mode 100644 js/public/friend/UsageStatistics.h create mode 100644 js/public/friend/WindowProxy.h create mode 100644 js/public/friend/XrayJitInfo.h create mode 100644 js/public/shadow/Function.h create mode 100644 js/public/shadow/Object.h create mode 100644 js/public/shadow/Realm.h create mode 100644 js/public/shadow/Shape.h create mode 100644 js/public/shadow/String.h create mode 100644 js/public/shadow/Symbol.h create mode 100644 js/public/shadow/Zone.h (limited to 'js/public') 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 + T* maybe_pod_arena_malloc(arena_id_t arenaId, size_t numElems) { + return js_pod_arena_malloc(arenaId, numElems); + } + template + T* maybe_pod_arena_calloc(arena_id_t arenaId, size_t numElems) { + return js_pod_arena_calloc(arenaId, numElems); + } + template + T* maybe_pod_arena_realloc(arena_id_t arenaId, T* p, size_t oldSize, + size_t newSize) { + return js_pod_arena_realloc(arenaId, p, oldSize, newSize); + } + template + T* pod_arena_malloc(arena_id_t arenaId, size_t numElems) { + return maybe_pod_arena_malloc(arenaId, numElems); + } + template + T* pod_arena_calloc(arena_id_t arenaId, size_t numElems) { + return maybe_pod_arena_calloc(arenaId, numElems); + } + template + T* pod_arena_realloc(arena_id_t arenaId, T* p, size_t oldSize, + size_t newSize) { + return maybe_pod_arena_realloc(arenaId, p, oldSize, newSize); + } + + template + T* maybe_pod_malloc(size_t numElems) { + return maybe_pod_arena_malloc(js::MallocArena, numElems); + } + template + T* maybe_pod_calloc(size_t numElems) { + return maybe_pod_arena_calloc(js::MallocArena, numElems); + } + template + T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) { + return maybe_pod_arena_realloc(js::MallocArena, p, oldSize, newSize); + } + template + T* pod_malloc(size_t numElems) { + return pod_arena_malloc(js::MallocArena, numElems); + } + template + T* pod_calloc(size_t numElems) { + return pod_arena_calloc(js::MallocArena, numElems); + } + template + T* pod_realloc(T* p, size_t oldSize, size_t newSize) { + return pod_arena_realloc(js::MallocArena, p, oldSize, newSize); + } + + template + 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(context_bits_ ^ JsContextTag); + } + + MOZ_ALWAYS_INLINE FrontendContext* fc() const { + MOZ_ASSERT(!hasJSContext()); + return reinterpret_cast(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 + T* onOutOfMemoryTyped(arena_id_t arenaId, AllocFunction allocFunc, + size_t numElems, void* reallocPtr = nullptr) { + size_t bytes; + if (MOZ_UNLIKELY(!CalculateAllocSize(numElems, &bytes))) { + return nullptr; + } + return static_cast( + 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 + T* pod_arena_malloc(arena_id_t arenaId, size_t numElems) { + assertNotJSContextOnHelperThread(); + T* p = this->maybe_pod_arena_malloc(arenaId, numElems); + if (MOZ_UNLIKELY(!p)) { + p = onOutOfMemoryTyped(arenaId, AllocFunction::Malloc, numElems); + } + return p; + } + + template + T* pod_arena_calloc(arena_id_t arenaId, size_t numElems) { + assertNotJSContextOnHelperThread(); + T* p = this->maybe_pod_arena_calloc(arenaId, numElems); + if (MOZ_UNLIKELY(!p)) { + p = onOutOfMemoryTyped(arenaId, AllocFunction::Calloc, numElems); + } + return p; + } + + template + T* pod_arena_realloc(arena_id_t arenaId, T* prior, size_t oldSize, + size_t newSize) { + assertNotJSContextOnHelperThread(); + T* p2 = this->maybe_pod_arena_realloc(arenaId, prior, oldSize, newSize); + if (MOZ_UNLIKELY(!p2)) { + p2 = onOutOfMemoryTyped(arenaId, AllocFunction::Realloc, newSize, + prior); + } + return p2; + } + + template + T* pod_malloc(size_t numElems) { + return pod_arena_malloc(js::MallocArena, numElems); + } + + template + T* pod_calloc(size_t numElems) { + return pod_arena_calloc(js::MallocArena, numElems); + } + + template + T* pod_realloc(T* prior, size_t oldSize, size_t newSize) { + return pod_arena_realloc(js::MallocArena, prior, oldSize, newSize); + } + + template + 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 // 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(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(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 +#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 // size_t +#include // 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, + 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 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 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 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 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 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 // size_t +#include // 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 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 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 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 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 toBlock, size_t toIndex, + Handle 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 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 // 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 // std::numeric_limits +#include // int64_t, uint64_t +#include // 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 +class Range; +} + +namespace JS { + +class JS_PUBLIC_API BigInt; + +namespace detail { + +using Int64Limits = std::numeric_limits; +using Uint64Limits = std::numeric_limits; + +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 +struct NumberToBigIntConverter; + +template +struct NumberToBigIntConverter< + SignedIntT, + std::enable_if_t< + std::is_integral_v && std::is_signed_v && + Int64Limits::min() <= std::numeric_limits::min() && + std::numeric_limits::max() <= Int64Limits::max()>> { + static BigInt* convert(JSContext* cx, SignedIntT num) { + return BigIntFromInt64(cx, num); + } +}; + +template +struct NumberToBigIntConverter< + UnsignedIntT, + std::enable_if_t< + std::is_integral_v && std::is_unsigned_v && + std::numeric_limits::max() <= Uint64Limits::max()>> { + static BigInt* convert(JSContext* cx, UnsignedIntT num) { + return BigIntFromUint64(cx, num); + } +}; + +template <> +struct NumberToBigIntConverter { + 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 +struct BigIntToNumberChecker; + +template +struct BigIntToNumberChecker< + SignedIntT, + std::enable_if_t< + std::is_integral_v && std::is_signed_v && + Int64Limits::min() <= std::numeric_limits::min() && + std::numeric_limits::max() <= Int64Limits::max()>> { + using TypeLimits = std::numeric_limits; + + 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 +struct BigIntToNumberChecker< + UnsignedIntT, + std::enable_if_t< + std::is_integral_v && std::is_unsigned_v && + std::numeric_limits::max() <= Uint64Limits::max()>> { + static bool fits(BigInt* bi, UnsignedIntT* result) { + uint64_t innerResult; + if (!BigIntIsUint64(bi, &innerResult)) { + return false; + } + if (innerResult <= std::numeric_limits::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 +static inline BigInt* NumberToBigInt(JSContext* cx, NumericT val) { + return detail::NumberToBigIntConverter::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 chars); + +extern JS_PUBLIC_API BigInt* StringToBigInt( + JSContext* cx, mozilla::Range 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 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 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 +static inline bool BigIntFits(BigInt* bi, NumericT* out) { + return detail::BigIntToNumberChecker::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 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; + +/** + * 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 obj, JS::Handle fval, + const JS::HandleValueArray& args, JS::MutableHandle rval); + +extern JS_PUBLIC_API bool JS_CallFunction(JSContext* cx, + JS::Handle obj, + JS::Handle fun, + const JS::HandleValueArray& args, + JS::MutableHandle rval); + +/** + * Perform the method call `rval = obj[name](args)`. + */ +extern JS_PUBLIC_API bool JS_CallFunctionName( + JSContext* cx, JS::Handle obj, const char* name, + const JS::HandleValueArray& args, JS::MutableHandle rval); + +namespace JS { + +static inline bool Call(JSContext* cx, Handle thisObj, + Handle fun, const HandleValueArray& args, + MutableHandle rval) { + return !!JS_CallFunction(cx, thisObj, fun, args, rval); +} + +static inline bool Call(JSContext* cx, Handle thisObj, + Handle fun, const HandleValueArray& args, + MutableHandle rval) { + return !!JS_CallFunctionValue(cx, thisObj, fun, args, rval); +} + +static inline bool Call(JSContext* cx, Handle thisObj, + const char* name, const HandleValueArray& args, + MutableHandle rval) { + return !!JS_CallFunctionName(cx, thisObj, name, args, rval); +} + +extern JS_PUBLIC_API bool Call(JSContext* cx, Handle thisv, + Handle fun, const HandleValueArray& args, + MutableHandle rval); + +static inline bool Call(JSContext* cx, Handle thisv, + Handle funObj, const HandleValueArray& args, + MutableHandle rval) { + MOZ_ASSERT(funObj); + Rooted 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 fun, + Handle newTarget, + const HandleValueArray& args, + MutableHandle 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 fun, + const HandleValueArray& args, + MutableHandle 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 + +#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 MOZ_STACK_CLASS CallArgsBase { + static_assert(std::is_same_v || + std::is_same_v, + "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 { + 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 +JS_PUBLIC_API inline bool CallArgsBase::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(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 +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 { + typedef mozilla::Range Base; + + public: + using CharT = Latin1Char; + + Latin1Chars() = default; + Latin1Chars(char* aBytes, size_t aLength) + : Base(reinterpret_cast(aBytes), aLength) {} + Latin1Chars(const Latin1Char* aBytes, size_t aLength) + : Base(const_cast(aBytes), aLength) {} + Latin1Chars(const char* aBytes, size_t aLength) + : Base(reinterpret_cast(const_cast(aBytes)), + aLength) {} +}; + +/* + * Like Latin1Chars, but the chars are const. + */ +class ConstLatin1Chars : public mozilla::Range { + typedef mozilla::Range 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 { + typedef mozilla::RangedPtr Base; + + public: + using CharT = Latin1Char; + + Latin1CharsZ() : Base(nullptr, 0) {} // NOLINT + + Latin1CharsZ(char* aBytes, size_t aLength) + : Base(reinterpret_cast(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(get()); } +}; + +class UTF8Chars : public mozilla::Range { + typedef mozilla::Range Base; + + public: + using CharT = unsigned char; + + UTF8Chars() = default; + UTF8Chars(char* aBytes, size_t aLength) + : Base(reinterpret_cast(aBytes), aLength) {} + UTF8Chars(const char* aBytes, size_t aLength) + : Base(reinterpret_cast(const_cast(aBytes)), + aLength) {} + UTF8Chars(mozilla::Utf8Unit* aUnits, size_t aLength) + : UTF8Chars(reinterpret_cast(aUnits), aLength) {} + UTF8Chars(const mozilla::Utf8Unit* aUnits, size_t aLength) + : UTF8Chars(reinterpret_cast(aUnits), aLength) {} +}; + +/* + * SpiderMonkey also deals directly with UTF-8 encoded text in some places. + */ +class UTF8CharsZ : public mozilla::RangedPtr { + typedef mozilla::RangedPtr Base; + + public: + using CharT = unsigned char; + + UTF8CharsZ() : Base(nullptr, 0) {} // NOLINT + + UTF8CharsZ(char* aBytes, size_t aLength) + : Base(reinterpret_cast(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(aUnits), aLength) {} + + using Base::operator=; + + char* c_str() { return reinterpret_cast(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 { + typedef mozilla::Range 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(aChars), aLength) {} +}; + +/* + * A TwoByteChars, but \0 terminated for compatibility with JSFlatString. + */ +class TwoByteCharsZ : public mozilla::RangedPtr { + typedef mozilla::RangedPtr 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 ConstCharPtr; + +/* + * Like TwoByteChars, but the chars are const. + */ +class ConstTwoByteChars : public mozilla::Range { + typedef mozilla::Range 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 tbchars); + +inline Latin1CharsZ LossyTwoByteCharsToNewLatin1CharsZ(JSContext* cx, + const char16_t* begin, + size_t length) { + const mozilla::Range tbchars(begin, length); + return JS::LossyTwoByteCharsToNewLatin1CharsZ(cx, tbchars); +} + +template +extern UTF8CharsZ CharsToNewUTF8CharsZ(Allocator* alloc, + const mozilla::Range 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 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 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 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 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> 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|, + * |JS::Handle|, 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 // 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 +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 +inline bool WrapperEqualsWrapper(const W& wrapper, const OW& other) { + return JS::detail::DefineComparisonOps::get(wrapper) == + JS::detail::DefineComparisonOps::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 +inline bool WrapperEqualsUnwrapped(const W& wrapper, + const typename W::ElementType& value) { + return JS::detail::DefineComparisonOps::get(wrapper) == value; +} + +// Compare a wrapper containing a pointer against a pointer to const element +// type. Assumes its wrapper argument supports comparison operators. +template +inline bool WrapperEqualsPointer( + const W& wrapper, + const typename std::remove_pointer_t* ptr) { + return JS::detail::DefineComparisonOps::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 +inline typename std::enable_if_t::value && + JS::detail::DefineComparisonOps::value, + bool> +operator==(const W& wrapper, const OW& other) { + return JS::detail::WrapperEqualsWrapper(wrapper, other); +} + +template +inline typename std::enable_if_t::value && + JS::detail::DefineComparisonOps::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 +inline typename std::enable_if_t::value, bool> +operator==(const W& wrapper, const typename W::ElementType& value) { + return WrapperEqualsUnwrapped(wrapper, value); +} + +template +inline typename std::enable_if_t::value, bool> +operator!=(const W& wrapper, const typename W::ElementType& value) { + return !WrapperEqualsUnwrapped(wrapper, value); +} + +template +inline typename std::enable_if_t::value, bool> +operator==(const typename W::ElementType& value, const W& wrapper) { + return WrapperEqualsUnwrapped(wrapper, value); +} + +template +inline typename std::enable_if_t::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 +inline typename std::enable_if_t::value && + std::is_pointer_v, + bool> +operator==(const W& wrapper, + const typename std::remove_pointer_t* ptr) { + return WrapperEqualsPointer(wrapper, ptr); +} + +template +inline typename std::enable_if_t::value && + std::is_pointer_v, + bool> +operator!=(const W& wrapper, + const typename std::remove_pointer_t* ptr) { + return !WrapperEqualsPointer(wrapper, ptr); +} + +template +inline typename std::enable_if_t::value && + std::is_pointer_v, + bool> +operator==(const typename std::remove_pointer_t* ptr, + const W& wrapper) { + return WrapperEqualsPointer(wrapper, ptr); +} + +template +inline typename std::enable_if_t::value && + std::is_pointer_v, + bool> +operator!=(const typename std::remove_pointer_t* 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 +inline typename std::enable_if_t::value, bool> +operator==(const W& wrapper, std::nullptr_t) { + return WrapperEqualsUnwrapped(wrapper, nullptr); +} + +template +inline typename std::enable_if_t::value, bool> +operator!=(const W& wrapper, std::nullptr_t) { + return !WrapperEqualsUnwrapped(wrapper, nullptr); +} + +template +inline typename std::enable_if_t::value, bool> +operator==(std::nullptr_t, const W& wrapper) { + return WrapperEqualsUnwrapped(wrapper, nullptr); +} + +template +inline typename std::enable_if_t::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 // size_t +#include // 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 +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 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 script, + JS::MutableHandle rval); + +extern JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx, + JS::Handle 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 script, + JS::MutableHandle rval); + +extern JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx, + JS::HandleObjectVector envChain, + JS::Handle 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& srcBuf, + MutableHandle 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& srcBuf, + MutableHandle 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& srcBuf, + MutableHandle 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 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& 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& 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& 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& 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 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 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 // size_t +#include // 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 + 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 + 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 + 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 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 +#include // size_t +#include // {u,}int{8,16,32,64}_t +#include + +#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 + * + * but has been generalized to all integer widths. + */ +template +inline UnsignedInteger ToUnsignedInteger(double d) { + static_assert(std::is_unsigned_v, + "UnsignedInteger must be an unsigned type"); + + uint64_t bits = mozilla::BitwiseCast(d); + unsigned DoubleExponentShift = mozilla::FloatingPoint::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::kExponentBits) >> + DoubleExponentShift) - + int_fast16_t(mozilla::FloatingPoint::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(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{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::kSignBit) ? ~result + 1 + : result; +} + +template +inline SignedInteger ToSignedInteger(double d) { + static_assert(std::is_signed_v, + "SignedInteger must be a signed type"); + + using UnsignedInteger = std::make_unsigned_t; + UnsignedInteger u = ToUnsignedInteger(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(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 > +struct ToSignedOrUnsignedInteger; + +template +struct ToSignedOrUnsignedInteger { + static IntegerType compute(double d) { + return ToUnsignedInteger(d); + } +}; + +template +struct ToSignedOrUnsignedInteger { + static IntegerType compute(double d) { + return ToSignedInteger(d); + } +}; + +} // namespace detail + +template +inline IntegerType ToSignedOrUnsignedInteger(double d) { + return detail::ToSignedOrUnsignedInteger::compute(d); +} + +/* WEBIDL 4.2.4 */ +inline int8_t ToInt8(double d) { return ToSignedInteger(d); } + +/* ECMA-262 7.1.10 ToUInt8() specialized for doubles. */ +inline int8_t ToUint8(double d) { return ToUnsignedInteger(d); } + +/* WEBIDL 4.2.6 */ +inline int16_t ToInt16(double d) { return ToSignedInteger(d); } + +inline uint16_t ToUint16(double d) { return ToUnsignedInteger(d); } + +/* ES5 9.5 ToInt32 (specialized for doubles). */ +inline int32_t ToInt32(double d) { return ToSignedInteger(d); } + +/* ES5 9.6 (specialized for doubles). */ +inline uint32_t ToUint32(double d) { return ToUnsignedInteger(d); } + +/* WEBIDL 4.2.10 */ +inline int64_t ToInt64(double d) { return ToSignedInteger(d); } + +/* WEBIDL 4.2.11 */ +inline uint64_t ToUint64(double d) { return ToUnsignedInteger(d); } + +/** + * An amount of space large enough to store the null-terminated result of + * |ToString| on any Number. + * + * The + * |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 . + * (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(); + + 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()); + } + + // 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 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 + +#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: } +// +// where 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 + class BuiltThing { + friend class BuilderOrigin; + + protected: + // The Builder to which this trusted thing belongs. + Builder& owner; + + // A rooted reference to our value. + PersistentRooted value; + + BuiltThing(JSContext* cx, Builder& owner_, + T value_ = SafelyInitialized::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, 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 { + friend class Builder; // for construction + friend class BuilderOrigin; // for unwrapping + + typedef BuiltThing 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 + T unwrapAny(const BuiltThing& 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 v1, + JS::Handle 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 v1, + JS::Handle 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 v1, + JS::Handle 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 +#include // std::input_iterator_tag, std::iterator +#include +#include // size_t +#include // int16_t, uint16_t +#include // 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, 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 copy(JSContext* cx); + + class iterator final { + private: + js::UniquePtr* note_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = js::UniquePtr; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + explicit iterator(js::UniquePtr* 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 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> 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 + +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 exception_; + Rooted 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 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 // 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 + +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 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 iterator; + Rooted 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 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 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 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 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; + 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 +class Heap; +} + +extern JS_PUBLIC_API bool JS_UpdateWeakPointerAfterGC( + JSTracer* trc, JS::Heap* 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 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 +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 +struct DefaultMapEntryGCPolicy { + static bool traceWeak(JSTracer* trc, Key* key, Value* value) { + return GCPolicy::traceWeak(trc, key) && + GCPolicy::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::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::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 or PersistentRooted, or barriered and traced +// manually. +template , + typename AllocPolicy = js::TempAllocPolicy, + typename MapEntryGCPolicy = DefaultMapEntryGCPolicy> +class GCHashMap : public js::HashMap { + using Base = js::HashMap; + + 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::trace(trc, &e.front().value(), "hashmap value"); + GCPolicy::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 AllocPolicy = TempAllocPolicy, + typename MapEntryGCPolicy = JS::DefaultMapEntryGCPolicy> +class GCRekeyableHashMap : public JS::GCHashMap { + using Base = JS::GCHashMap; + + 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 +class WrappedPtrOperations, Wrapper> { + using Map = JS::GCHashMap; + using Lookup = typename Map::Lookup; + + const Map& map() const { return static_cast(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 +class MutableWrappedPtrOperations, Wrapper> + : public WrappedPtrOperations, Wrapper> { + using Map = JS::GCHashMap; + using Lookup = typename Map::Lookup; + + Map& map() { return static_cast(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 + bool add(AddPtr& p, KeyInput&& k, ValueInput&& v) { + return map().add(p, std::forward(k), std::forward(v)); + } + + template + bool add(AddPtr& p, KeyInput&& k) { + return map().add(p, std::forward(k), Map::Value()); + } + + template + bool relookupOrAdd(AddPtr& p, KeyInput&& k, ValueInput&& v) { + return map().relookupOrAdd(p, k, std::forward(k), + std::forward(v)); + } + + template + bool put(KeyInput&& k, ValueInput&& v) { + return map().put(std::forward(k), std::forward(v)); + } + + template + bool putNew(KeyInput&& k, ValueInput&& v) { + return map().putNew(std::forward(k), std::forward(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 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 AllocPolicy = js::TempAllocPolicy> +class GCHashSet : public js::HashSet { + using Base = js::HashSet; + + 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::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::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 +class WrappedPtrOperations, Wrapper> { + using Set = JS::GCHashSet; + + const Set& set() const { return static_cast(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 +class MutableWrappedPtrOperations, Wrapper> + : public WrappedPtrOperations, Wrapper> { + using Set = JS::GCHashSet; + using Lookup = typename Set::Lookup; + + Set& set() { return static_cast(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 + void replaceKey(Ptr p, const Lookup& l, TInput&& newValue) { + set().replaceKey(p, l, std::forward(newValue)); + } + + template + bool add(AddPtr& p, TInput&& t) { + return set().add(p, std::forward(t)); + } + + template + bool relookupOrAdd(AddPtr& p, const Lookup& l, TInput&& t) { + return set().relookupOrAdd(p, l, std::forward(t)); + } + + template + bool put(TInput&& t) { + return set().put(std::forward(t)); + } + + template + bool putNew(TInput&& t) { + return set().putNew(std::forward(t)); + } + + template + bool putNew(const Lookup& l, TInput&& t) { + return set().putNew(l, std::forward(t)); + } +}; + +} /* namespace js */ + +namespace JS { + +// Specialize WeakCache for GCHashMap to provide a barriered map that does not +// need to be swept immediately. +template +class WeakCache< + GCHashMap> + final : protected detail::WeakCacheBase { + using Map = GCHashMap; + using Self = WeakCache; + + Map map; + JSTracer* barrierTracer = nullptr; + + public: + template + explicit WeakCache(Zone* zone, Args&&... args) + : WeakCacheBase(zone), map(std::forward(args)...) {} + template + explicit WeakCache(JSRuntime* rt, Args&&... args) + : WeakCacheBase(rt), map(std::forward(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 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 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).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).remove(ptr); + return map.lookupForAdd(l); + } + return ptr; + } + + Range all() const { return Range(*const_cast(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 + bool add(AddPtr& p, KeyInput&& k, ValueInput&& v) { + return map.add(p, std::forward(k), std::forward(v)); + } + + template + bool relookupOrAdd(AddPtr& p, KeyInput&& k, ValueInput&& v) { + return map.relookupOrAdd(p, std::forward(k), + std::forward(v)); + } + + template + bool put(KeyInput&& k, ValueInput&& v) { + return map.put(std::forward(k), std::forward(v)); + } + + template + bool putNew(KeyInput&& k, ValueInput&& v) { + return map.putNew(std::forward(k), std::forward(v)); + } +} JS_HAZ_NON_GC_POINTER; + +// Specialize WeakCache for GCHashSet to provide a barriered set that does not +// need to be swept immediately. +template +class WeakCache> final + : protected detail::WeakCacheBase { + using Set = GCHashSet; + using Self = WeakCache; + + Set set; + JSTracer* barrierTracer = nullptr; + + public: + using Entry = typename Set::Entry; + + template + explicit WeakCache(Zone* zone, Args&&... args) + : WeakCacheBase(zone), set(std::forward(args)...) {} + template + explicit WeakCache(JSRuntime* rt, Args&&... args) + : WeakCacheBase(rt), set(std::forward(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 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 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::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).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).remove(ptr); + return set.lookupForAdd(l); + } + return ptr; + } + + Range all() const { return Range(*const_cast(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 + void replaceKey(Ptr p, const Lookup& l, TInput&& newValue) { + set.replaceKey(p, l, std::forward(newValue)); + } + + template + bool add(AddPtr& p, TInput&& t) { + return set.add(p, std::forward(t)); + } + + template + bool relookupOrAdd(AddPtr& p, const Lookup& l, TInput&& t) { + return set.relookupOrAdd(p, l, std::forward(t)); + } + + template + bool put(TInput&& t) { + return set.put(std::forward(t)); + } + + template + bool putNew(TInput&& t) { + return set.putNew(std::forward(t)); + } + + template + bool putNew(const Lookup& l, TInput&& t) { + return set.putNew(l, std::forward(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 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 and mozilla::UniquePtr. +// +// There are some stock structs your specializations can inherit from. +// IgnoreGCPolicy does nothing. StructGCPolicy forwards the methods to the +// referent type T. + +#ifndef GCPolicyAPI_h +#define GCPolicyAPI_h + +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" + +#include + +#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 +struct StructGCPolicy { + static_assert(!std::is_pointer_v, + "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 +struct GCPolicy : public StructGCPolicy {}; + +// This policy ignores any GC interaction, e.g. for non-GC types. +template +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 : public IgnoreGCPolicy {}; +template <> +struct GCPolicy : public IgnoreGCPolicy {}; +template <> +struct GCPolicy : public IgnoreGCPolicy {}; + +template +struct GCPointerPolicy { + static_assert(std::is_pointer_v, + "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 : public GCPointerPolicy {}; \ + template <> \ + struct GCPolicy : public GCPointerPolicy {}; +JS_FOR_EACH_PUBLIC_GC_POINTER_TYPE(EXPAND_SPECIALIZE_GCPOLICY) +#undef EXPAND_SPECIALIZE_GCPOLICY + +template +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 +struct GCPolicy> { + static void trace(JSTracer* trc, JS::Heap* thingp, const char* name) { + TraceEdge(trc, thingp, name); + } + static bool traceWeak(JSTracer* trc, JS::Heap* thingp) { + return !*thingp || js::gc::TraceWeakEdge(trc, thingp); + } +}; + +// GCPolicy> forwards the contained pointer to GCPolicy. +template +struct GCPolicy> { + static void trace(JSTracer* trc, mozilla::UniquePtr* tp, + const char* name) { + if (tp->get()) { + GCPolicy::trace(trc, tp->get(), name); + } + } + static bool traceWeak(JSTracer* trc, mozilla::UniquePtr* tp) { + if (tp->get()) { + return GCPolicy::traceWeak(trc, tp->get()); + } + return true; + } + static bool isValid(const mozilla::UniquePtr& t) { + if (t.get()) { + return GCPolicy::isValid(*t.get()); + } + return true; + } +}; + +template <> +struct GCPolicy : public IgnoreGCPolicy {}; + +// GCPolicy> forwards tracing/sweeping to GCPolicy if +// the Maybe is filled and T* can be traced via GCPolicy. +template +struct GCPolicy> { + static void trace(JSTracer* trc, mozilla::Maybe* tp, const char* name) { + if (tp->isSome()) { + GCPolicy::trace(trc, tp->ptr(), name); + } + } + static bool traceWeak(JSTracer* trc, mozilla::Maybe* tp) { + if (tp->isSome()) { + return GCPolicy::traceWeak(trc, tp->ptr()); + } + return true; + } + static bool isValid(const mozilla::Maybe& t) { + if (t.isSome()) { + return GCPolicy::isValid(t.ref()); + } + return true; + } +}; + +template +struct GCPolicy> { + static void trace(JSTracer* trc, std::pair* tp, const char* name) { + GCPolicy::trace(trc, &tp->first, name); + GCPolicy::trace(trc, &tp->second, name); + } + static bool traceWeak(JSTracer* trc, std::pair* tp) { + return GCPolicy::traceWeak(trc, &tp->first) && + GCPolicy::traceWeak(trc, &tp->second); + } + static bool isValid(const std::pair& t) { + return GCPolicy::isValid(t.first) && GCPolicy::isValid(t.second); + } +}; + +template <> +struct GCPolicy; // see Realm.h + +template <> +struct GCPolicy : public IgnoreGCPolicy {}; + +template +struct GCPolicy> { + static void trace(JSTracer* trc, mozilla::Result* tp, + const char* name) { + if (tp->isOk()) { + V tmp = tp->unwrap(); + JS::GCPolicy::trace(trc, &tmp, "Result value"); + tp->updateAfterTracing(std::move(tmp)); + } + + if (tp->isErr()) { + E tmp = tp->unwrapErr(); + JS::GCPolicy::trace(trc, &tmp, "Result error"); + tp->updateErrorAfterTracing(std::move(tmp)); + } + } + + static bool isValid(const mozilla::Result& 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 + +#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> 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 +struct GCVariantImplementation; + +// The base case. +template +struct GCVariantImplementation { + template + static void trace(JSTracer* trc, ConcreteVariant* v, const char* name) { + T& thing = v->template as(); + GCPolicy::trace(trc, &thing, name); + } + + template + static typename Matcher::ReturnType match(Matcher& matcher, + Handle v) { + const T& thing = v.get().template as(); + return matcher.match(Handle::fromMarkedLocation(&thing)); + } + + template + static typename Matcher::ReturnType match(Matcher& matcher, + MutableHandle v) { + T& thing = v.get().template as(); + return matcher.match(MutableHandle::fromMarkedLocation(&thing)); + } +}; + +// The inductive case. +template +struct GCVariantImplementation { + using Next = GCVariantImplementation; + + template + static void trace(JSTracer* trc, ConcreteVariant* v, const char* name) { + if (v->template is()) { + T& thing = v->template as(); + GCPolicy::trace(trc, &thing, name); + } else { + Next::trace(trc, v, name); + } + } + + template + static typename Matcher::ReturnType match(Matcher& matcher, + Handle v) { + if (v.get().template is()) { + const T& thing = v.get().template as(); + return matcher.match(Handle::fromMarkedLocation(&thing)); + } + return Next::match(matcher, v); + } + + template + static typename Matcher::ReturnType match(Matcher& matcher, + MutableHandle v) { + if (v.get().template is()) { + T& thing = v.get().template as(); + return matcher.match(MutableHandle::fromMarkedLocation(&thing)); + } + return Next::match(matcher, v); + } +}; + +} // namespace detail + +template +struct GCPolicy> { + using Impl = detail::GCVariantImplementation; + + static void trace(JSTracer* trc, mozilla::Variant* v, + const char* name) { + Impl::trace(trc, v, name); + } + + static bool isValid(const mozilla::Variant& v) { + return v.match([](auto& v) { + return GCPolicy>::isValid(v); + }); + } +}; + +} // namespace JS + +namespace js { + +template +class WrappedPtrOperations, Wrapper> { + using Impl = JS::detail::GCVariantImplementation; + using Variant = mozilla::Variant; + + const Variant& variant() const { + return static_cast(this)->get(); + } + + public: + template + bool is() const { + return variant().template is(); + } + + template + JS::Handle as() const { + return JS::Handle::fromMarkedLocation(&variant().template as()); + } + + template + typename Matcher::ReturnType match(Matcher& matcher) const { + return Impl::match(matcher, + JS::Handle::fromMarkedLocation(&variant())); + } +}; + +template +class MutableWrappedPtrOperations, Wrapper> + : public WrappedPtrOperations, Wrapper> { + using Impl = JS::detail::GCVariantImplementation; + using Variant = mozilla::Variant; + + const Variant& variant() const { + return static_cast(this)->get(); + } + Variant& variant() { return static_cast(this)->get(); } + + public: + template + JS::MutableHandle as() { + return JS::MutableHandle::fromMarkedLocation( + &variant().template as()); + } + + template + typename Matcher::ReturnType match(Matcher& matcher) { + return Impl::match( + matcher, JS::MutableHandle::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 // size_t +#include // 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 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 +class GCVector { + mozilla::Vector 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() { return vector; } + operator mozilla::Span() 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 + bool append(U&& item) { + return vector.append(std::forward(item)); + } + + void erase(T* it) { vector.erase(it); } + void erase(T* begin, T* end) { vector.erase(begin, end); } + template + void eraseIf(Pred pred) { + vector.eraseIf(pred); + } + template + void eraseIfEqual(const U& u) { + vector.eraseIfEqual(u); + } + + template + [[nodiscard]] bool emplaceBack(Args&&... args) { + return vector.emplaceBack(std::forward(args)...); + } + + template + void infallibleEmplaceBack(Args&&... args) { + vector.infallibleEmplaceBack(std::forward(args)...); + } + + template + void infallibleAppend(U&& aU) { + return vector.infallibleAppend(std::forward(aU)); + } + void infallibleAppendN(const T& aT, size_t aN) { + return vector.infallibleAppendN(aT, aN); + } + template + void infallibleAppend(const U* aBegin, const U* aEnd) { + return vector.infallibleAppend(aBegin, aEnd); + } + template + void infallibleAppend(const U* aBegin, size_t aLength) { + return vector.infallibleAppend(aBegin, aLength); + } + + template + [[nodiscard]] bool appendAll(const U& aU) { + return vector.append(aU.begin(), aU.end()); + } + template + [[nodiscard]] bool appendAll( + GCVector&& aU) { + return vector.appendAll(aU.begin(), aU.end()); + } + + [[nodiscard]] bool appendN(const T& val, size_t count) { + return vector.appendN(val, count); + } + + template + [[nodiscard]] bool append(const U* aBegin, const U* aEnd) { + return vector.append(aBegin, aEnd); + } + template + [[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::trace(trc, &elem, "vector element"); + } + } + + bool traceWeak(JSTracer* trc) { + mutableEraseIf( + [trc](T& elem) { return !GCPolicy::traceWeak(trc, &elem); }); + return !empty(); + } + + // Like eraseIf, but may mutate the contents of the vector. + template + 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 +class MOZ_STACK_CLASS StackGCVector : public GCVector { + public: + using Base = GCVector; + + private: + // Inherit constructor from GCVector. + using Base::Base; +}; + +} // namespace JS + +namespace js { + +template +class WrappedPtrOperations, Wrapper> { + using Vec = JS::GCVector; + const Vec& vec() const { return static_cast(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 operator[](size_t aIndex) const { + return JS::Handle::fromMarkedLocation(&vec().operator[](aIndex)); + } +}; + +template +class MutableWrappedPtrOperations, + Wrapper> + : public WrappedPtrOperations, + Wrapper> { + using Vec = JS::GCVector; + const Vec& vec() const { return static_cast(this)->get(); } + Vec& vec() { return static_cast(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 operator[](size_t aIndex) const { + return JS::Handle::fromMarkedLocation(&vec().operator[](aIndex)); + } + JS::MutableHandle operator[](size_t aIndex) { + return JS::MutableHandle::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 + [[nodiscard]] bool append(U&& aU) { + return vec().append(std::forward(aU)); + } + template + [[nodiscard]] bool emplaceBack(Args&&... aArgs) { + return vec().emplaceBack(std::forward(aArgs)...); + } + template + void infallibleEmplaceBack(Args&&... args) { + vec().infallibleEmplaceBack(std::forward(args)...); + } + template + [[nodiscard]] bool appendAll(U&& aU) { + return vec().appendAll(aU); + } + [[nodiscard]] bool appendN(const T& aT, size_t aN) { + return vec().appendN(aT, aN); + } + template + [[nodiscard]] bool append(const U* aBegin, const U* aEnd) { + return vec().append(aBegin, aEnd); + } + template + [[nodiscard]] bool append(const U* aBegin, size_t aLength) { + return vec().append(aBegin, aLength); + } + template + void infallibleAppend(U&& aU) { + vec().infallibleAppend(std::forward(aU)); + } + void infallibleAppendN(const T& aT, size_t aN) { + vec().infallibleAppendN(aT, aN); + } + template + void infallibleAppend(const U* aBegin, const U* aEnd) { + vec().infallibleAppend(aBegin, aEnd); + } + template + 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 + void eraseIf(Pred pred) { + vec().eraseIf(pred); + } + template + void eraseIfEqual(const U& u) { + vec().eraseIfEqual(u); + } +}; + +template +class WrappedPtrOperations, Wrapper> + : public WrappedPtrOperations< + typename JS::StackGCVector::Base, Wrapper> {}; + +template +class MutableWrappedPtrOperations, Wrapper> + : public MutableWrappedPtrOperations< + typename JS::StackGCVector::Base, Wrapper> {}; + +} // namespace js + +namespace JS { + +// An automatically rooted GCVector for stack use. +template +class RootedVector : public Rooted> { + using Vec = StackGCVector; + using Base = Rooted; + + 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 +class PersistentRootedVector : public PersistentRooted> { + using Vec = StackGCVector; + using Base = PersistentRooted; + + 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 +using DefaultHasher = mozilla::DefaultHasher; + +template +using PointerHasher = mozilla::PointerHasher; + +template , + class AllocPolicy = TempAllocPolicy> +using HashSet = mozilla::HashSet; + +template , + class AllocPolicy = TempAllocPolicy> +using HashMap = mozilla::HashMap; + +} // 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 +#include + +#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; + +/* + * 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; + +// Bitmap with one bit per arena used for free committed arena set. +using ChunkArenaBitmap = mozilla::BitSet; + +// 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 + explicit GCCellPtr(T* p) + : ptr(checkedCast(p, JS::MapTypeToTraceKind::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 >> + bool is() const { + return kind() == JS::MapTypeToTraceKind::kind; + } + + // Conversions to more specific types must match the kind. Access to + // further refined types is not allowed directly from a GCCellPtr. + template >> + T& as() const { + MOZ_ASSERT(kind() == JS::MapTypeToTraceKind::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(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(ptr & ~OutOfLineTraceKindMask); + } + + // The CC's trace logger needs an identity that is XPIDL serializable. + uint64_t unsafeAsInteger() const { + return static_cast(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(asCell()); + } + + MOZ_ALWAYS_INLINE bool mayBeOwnedByOtherRuntime() const { + if (!is() && !is()) { + return false; + } + if (is()) { + return JS::shadow::String::isPermanentAtom(asCell()); + } + MOZ_ASSERT(is()); + return JS::shadow::Symbol::isWellKnownSymbol(asCell()); + } + + private: + static uintptr_t checkedCast(void* p, JS::TraceKind traceKind) { + auto* cell = static_cast(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 +auto MapGCThingTyped(GCCellPtr thing, F&& f) { + switch (thing.kind()) { +#define JS_EXPAND_DEF(name, type, _, _1) \ + case JS::TraceKind::name: \ + return f(&thing.as()); + 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 +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(uintptr_t(cell) & ~ChunkMask); +} + +static MOZ_ALWAYS_INLINE TenuredChunkBase* GetCellChunkBase( + const TenuredCell* cell) { + MOZ_ASSERT(cell); + return reinterpret_cast(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(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(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(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(obj)); +} +MOZ_ALWAYS_INLINE bool IsInsideNursery(const JSString* str) { + return IsInsideNursery(reinterpret_cast(str)); +} +MOZ_ALWAYS_INLINE bool IsInsideNursery(const JS::BigInt* bi) { + return IsInsideNursery(reinterpret_cast(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(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(str)); + } + return GetNurseryCellZone(reinterpret_cast(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(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(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(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(thing.asCell()); + if (zone->needsIncrementalBarrier() && + !detail::TenuredCellIsMarkedBlack(cell)) { + // GC things owned by other runtimes are always black. + MOZ_ASSERT(!thing.mayBeOwnedByOtherRuntime()); + PerformIncrementalReadBarrier(thing); + } +} + +template +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 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 // 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(asBits_) >> 1; + return static_cast(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(asBits_ ^ StringTypeTag); + } + + MOZ_ALWAYS_INLINE JS::Symbol* toSymbol() const { + MOZ_ASSERT(isSymbol()); + return reinterpret_cast(asBits_ ^ SymbolTypeTag); + } + + js::gc::Cell* toGCThing() const { + MOZ_ASSERT(isGCThing()); + return reinterpret_cast(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(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(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(toString()); + } + MOZ_ALWAYS_INLINE JSLinearString* toLinearString() const { + return reinterpret_cast(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 version of PropertyKey::Void(). +extern JS_PUBLIC_DATA const JS::HandleId VoidHandlePropertyKey; + +template <> +struct GCPolicy { + 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 id, + JS::MutableHandle getterId); +extern JS_PUBLIC_API bool ToSetterId( + JSContext* cx, JS::Handle id, + JS::MutableHandle setterId); + +} // namespace JS + +namespace js { + +template <> +struct BarrierMethods { + 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 +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(nullptr))); + return mozilla::Maybe(); +} + +// If the jsid is a GC pointer type, convert to that type and call |f| with the +// pointer. Return whether this happened. +template +bool ApplyGCThingTyped(const jsid& id, F&& f) { + return MapGCThingTyped(id, + [&f](auto t) { + f(t); + return true; + }) + .isSome(); +} + +template +class WrappedPtrOperations { + const JS::PropertyKey& id() const { + return static_cast(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 (Bug 1709135) +using SelfHostedCache = mozilla::Span; + +// 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 // 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 value, + JS::Handle replacer, + JS::Handle 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 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, + Handle replacer, + Handle 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 vp); + +/** + * Performs the JSON.parse operation as specified by ECMAScript. + */ +extern JS_PUBLIC_API bool JS_ParseJSON(JSContext* cx, JS::Handle str, + JS::MutableHandle 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 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 reviver, JS::MutableHandle 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 str, JS::Handle reviver, + JS::MutableHandle 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 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 + SourceInfoVector; +typedef js::Vector 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 src, + JS::MutableHandle rval); + +using JSLocaleToLowerCase = bool (*)(JSContext* cx, JS::Handle src, + JS::MutableHandle rval); + +using JSLocaleCompare = bool (*)(JSContext* cx, JS::Handle src1, + JS::Handle src2, + JS::MutableHandle rval); + +using JSLocaleToUnicode = bool (*)(JSContext* cx, const char* src, + JS::MutableHandle 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 // 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 +#include + +#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) \ + ? 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 + 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 allScriptSources; + js::Vector + 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 + 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 allStrings; + js::Vector 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 + ClassesHashMap; + + // These are similar to |allStrings| and |notableStrings| in ZoneStats. + mozilla::Maybe allClasses; + js::Vector notableClasses; + bool isTotals = true; + +#undef FOR_EACH_SIZE +}; + +typedef js::Vector RealmStatsVector; +typedef js::Vector 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 // 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 +class SourceText; +} // namespace JS + +namespace mozilla { +union Utf8Unit; +} + +namespace JS { + +enum class ImportAssertion { Type }; + +using ImportAssertionVector = + js::Vector; + +/** + * 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 referencingPrivate, + Handle 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 privateValue, + Handle 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 referencingPrivate, + Handle moduleRequest, + Handle 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 evaluationPromise, + Handle referencingPrivate, Handle moduleRequest, + Handle 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& 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& 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 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 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 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 moduleRecord); + +extern JS_PUBLIC_API JSString* GetRequestedModuleSpecifier( + JSContext* cx, Handle moduleRecord, uint32_t index); + +extern JS_PUBLIC_API void GetRequestedModuleSourcePos( + JSContext* cx, Handle 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 moduleRecord); + +extern JS_PUBLIC_API JSObject* CreateModuleRequest( + JSContext* cx, Handle specifierArg); +extern JS_PUBLIC_API JSString* GetModuleRequestSpecifier( + JSContext* cx, Handle moduleRequestArg); + +/* + * Get the module record for a module script. + */ +extern JS_PUBLIC_API JSObject* GetModuleObject(Handle moduleScript); + +/* + * Get the namespace object for a module. + */ +extern JS_PUBLIC_API JSObject* GetModuleNamespace( + JSContext* cx, Handle moduleRecord); + +extern JS_PUBLIC_API JSObject* GetModuleForNamespace( + JSContext* cx, Handle moduleNamespace); + +extern JS_PUBLIC_API JSObject* GetModuleEnvironment( + JSContext* cx, Handle 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 // size_t +#include // 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 obj, + js::ESClass* cls); + +/** Get the |JSClass| of an object. */ +inline const JSClass* GetClass(const JSObject* obj) { + return reinterpret_cast(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(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(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(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 +inline T* GetMaybePtrFromReservedSlot(JSObject* obj, size_t slot) { + Value v = GetReservedSlot(obj, slot); + return v.isUndefined() ? nullptr : static_cast(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 +inline T* GetObjectISupports(JSObject* obj) { + MOZ_ASSERT(GetClass(obj)->slot0IsISupports()); + return GetMaybePtrFromReservedSlot(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 +// compiles. + +namespace mozilla { +namespace detail { +template <> +struct HasFreeLSB { + 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 // 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 + +#include "jstypes.h" + +#include "js/TypeDecls.h" + +struct JSStructuredCloneReader; +struct JSStructuredCloneWriter; + +struct JSPrincipals { + /* Don't call "destroy"; use reference counting macros below. */ + mozilla::Atomic 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 +#include +#include +#include + +#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(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 +bool JS_PUBLIC_API QuoteString(Sprinter* sp, + const mozilla::Range 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 + +#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 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(storage()); + } + const js::wasm::ProfilingFrameIterator& wasmIter() const { + MOZ_ASSERT(!done()); + MOZ_ASSERT(isWasm()); + return *static_cast(storage()); + } + + js::jit::JSJitProfilingFrameIterator& jsJitIter() { + MOZ_ASSERT(!done()); + MOZ_ASSERT(isJSJit()); + return *static_cast(storage()); + } + + const js::jit::JSJitProfilingFrameIterator& jsJitIter() const { + MOZ_ASSERT(!done()); + MOZ_ASSERT(isJSJit()); + return *static_cast(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& 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 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 getCppEntryRegisters() const; + + private: + mozilla::Maybe 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 + +#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 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 dynamicString_; + + // Stack pointer for non-JS stack frames, the script pointer otherwise. + mozilla::Atomic 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 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 pcOffsetIfJS_; + + // Bits 0...8 hold the Flags. Bits 9...31 hold the category pair. + mozilla::Atomic 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 "