From 8f3badf88243c8c4db6eb389a619635dbdd01797 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sun, 29 Jan 2023 19:16:16 +0100 Subject: [PATCH 01/21] Issue #1382 - Remove incorrect assertion. Tag #2090 #2095 --- js/src/vm/NativeObject.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index 7418d81edf..91b7cacb4d 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -1696,7 +1696,6 @@ js::NativeDefineDataProperty(JSContext* cx, Handle obj, HandleId // Off-thread callers should not get here: they must call this // function only with known-valid arguments. Populating a new // PlainObject with configurable properties is fine. - MOZ_ASSERT(!cx->isHelperThreadContext()); result.reportError(cx, obj, id); return false; } From d89f4e14548311e522643680d6cdf44dfebe72bc Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sun, 29 Jan 2023 19:17:00 +0100 Subject: [PATCH 02/21] No issue - bump default platform UA compatibility mode to 102.0 --- modules/libpref/init/all.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 6e4cff6092..535ca1c93b 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -21,13 +21,15 @@ pref("keyword.enabled", false); pref("general.useragent.locale", "chrome://global/locale/intl.properties"); + +// Platform User-agent compatibility mode default settings pref("general.useragent.compatMode.gecko", false); pref("general.useragent.compatMode.firefox", false); -pref("general.useragent.compatMode.version", "68.0"); +pref("general.useragent.compatMode.version", "102.0"); pref("general.useragent.appVersionIsBuildID", false); -// This pref exists only for testing purposes. In order to disable all -// overrides by default, don't initialize UserAgentOverrides.jsm. +// In order to disable all overrides by default, don't initialize +// UserAgentOverrides.jsm. pref("general.useragent.site_specific_overrides", true); pref("general.config.obscure_value", 13); // for MCD .cfg files From 8bf892d728db1348956ca18d97d5a71adde33867 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sun, 29 Jan 2023 19:19:48 +0100 Subject: [PATCH 03/21] [xpcom] remove incorrect assertion. See https://repo.palemoon.org/MoonchildProductions/UXP/pulls/2095#issuecomment-33796 --- xpcom/threads/TimerThread.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xpcom/threads/TimerThread.cpp b/xpcom/threads/TimerThread.cpp index a2e40c3880..ca0cd3cb9c 100644 --- a/xpcom/threads/TimerThread.cpp +++ b/xpcom/threads/TimerThread.cpp @@ -623,13 +623,11 @@ TimerThread::AddTimerInternal(nsTimerImpl* aTimer) return insertSlot - mTimers.Elements(); } -// This function must be called from within a lock. -// Also: we hold the mutex for the nsTimerImpl. +// Note: this function must be called from within a lock. bool TimerThread::RemoveTimerInternal(nsTimerImpl* aTimer) { mMonitor.AssertCurrentThreadOwns(); - aTimer->mMutex.AssertCurrentThreadOwns(); if (!mTimers.RemoveElement(aTimer)) { return false; } From 3796c7c1e110159cead58d84cc99288c66512f79 Mon Sep 17 00:00:00 2001 From: Job Bautista Date: Thu, 26 Jan 2023 15:02:09 +0800 Subject: [PATCH 04/21] Issue #2083 - Part 1: Make RegExpShared a GC thing. Based on Mozilla bug 1345177. Changes from the original bug's patch: - We don't have JS::CurrentThreadIsHeapCollecting, so let's use trc->runtime()->isHeapCollecting() instead. - Mozilla bug 1337117 renamed runtimeFromMainThread to runtimeFromActiveCooperatingThread for Firefox 54, so let's use the former --- js/public/MemoryMetrics.h | 26 +-- js/public/Proxy.h | 3 +- js/public/TraceKind.h | 8 +- js/public/TracingAPI.h | 4 + js/src/gc/GCInternals.h | 5 + js/src/gc/GCRuntime.h | 2 + js/src/gc/Heap.h | 65 ++++---- js/src/gc/Marking.cpp | 10 ++ js/src/gc/Policy.h | 1 + js/src/gc/Statistics.cpp | 1 + js/src/gc/Statistics.h | 1 + js/src/gc/Tracer.h | 4 + js/src/jsapi.cpp | 4 +- js/src/jscompartment.cpp | 12 +- js/src/jscompartment.h | 2 +- js/src/jsfriendapi.h | 2 +- js/src/jsgc.cpp | 78 +++------ js/src/jsgc.h | 100 ++---------- js/src/jsgcinlines.h | 108 +++++++++++++ js/src/proxy/CrossCompartmentWrapper.cpp | 5 +- js/src/proxy/Proxy.h | 2 - js/src/vm/MemoryMetrics.cpp | 7 + js/src/vm/RegExpObject.cpp | 196 +++++++---------------- js/src/vm/RegExpObject.h | 123 ++++++-------- js/src/vm/SPSProfiler.cpp | 2 + js/src/vm/Shape-inl.h | 1 + js/src/vm/UbiNode.cpp | 3 + js/xpconnect/src/XPCJSContext.cpp | 16 ++ xpcom/base/CycleCollectedJSContext.h | 5 +- 29 files changed, 384 insertions(+), 412 deletions(-) diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index 2d7f06fb59..72764aec0e 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -581,7 +581,8 @@ struct UnusedGCThingSizes macro(Other, GCHeapUnused, string) \ macro(Other, GCHeapUnused, symbol) \ macro(Other, GCHeapUnused, jitcode) \ - macro(Other, GCHeapUnused, scope) + macro(Other, GCHeapUnused, scope) \ + macro(Other, GCHeapUnused, regExpShared) UnusedGCThingSizes() : FOR_EACH_SIZE(ZERO_SIZE) @@ -595,16 +596,17 @@ struct UnusedGCThingSizes 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::Script: script += n; break; - case JS::TraceKind::Shape: shape += n; break; - case JS::TraceKind::BaseShape: baseShape += n; break; - case JS::TraceKind::JitCode: jitcode += n; break; - case JS::TraceKind::LazyScript: lazyScript += n; break; - case JS::TraceKind::ObjectGroup: objectGroup += n; break; - case JS::TraceKind::Scope: scope += n; break; + case JS::TraceKind::Object: object += n; break; + case JS::TraceKind::String: string += n; break; + case JS::TraceKind::Symbol: symbol += 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::JitCode: jitcode += n; break; + case JS::TraceKind::LazyScript: lazyScript += n; break; + case JS::TraceKind::ObjectGroup: objectGroup += n; break; + case JS::TraceKind::Scope: scope += n; break; + case JS::TraceKind::RegExpShared: regExpShared += n; break; default: MOZ_CRASH("Bad trace kind for UnusedGCThingSizes"); } @@ -646,6 +648,8 @@ struct ZoneStats macro(Other, MallocHeap, objectGroupsMallocHeap) \ macro(Other, GCHeapUsed, scopesGCHeap) \ macro(Other, MallocHeap, scopesMallocHeap) \ + macro(Other, GCHeapUsed, regExpSharedsGCHeap) \ + macro(Other, MallocHeap, regExpSharedsMallocHeap) \ macro(Other, MallocHeap, typePool) \ macro(Other, MallocHeap, baselineStubsOptimized) \ macro(Other, MallocHeap, uniqueIdMap) \ diff --git a/js/public/Proxy.h b/js/public/Proxy.h index 2453e2e140..42973bdc37 100644 --- a/js/public/Proxy.h +++ b/js/public/Proxy.h @@ -31,7 +31,8 @@ using JS::PrivateValue; using JS::PropertyDescriptor; using JS::Value; -class RegExpGuard; +using RegExpGuard = JS::Rooted; + class JS_FRIEND_API(Wrapper); /* diff --git a/js/public/TraceKind.h b/js/public/TraceKind.h index d3109d3606..13228a9612 100644 --- a/js/public/TraceKind.h +++ b/js/public/TraceKind.h @@ -15,6 +15,7 @@ namespace js { class BaseShape; class LazyScript; class ObjectGroup; +class RegExpShared; class Shape; class Scope; namespace jit { @@ -58,13 +59,15 @@ enum class TraceKind BaseShape = 0x0F, JitCode = 0x1F, LazyScript = 0x2F, - Scope = 0x3F + Scope = 0x3F, + RegExpShared = 0x4F }; const static uintptr_t OutOfLineTraceKindMask = 0x07; static_assert(uintptr_t(JS::TraceKind::BaseShape) & OutOfLineTraceKindMask, "mask bits are set"); static_assert(uintptr_t(JS::TraceKind::JitCode) & OutOfLineTraceKindMask, "mask bits are set"); static_assert(uintptr_t(JS::TraceKind::LazyScript) & OutOfLineTraceKindMask, "mask bits are set"); static_assert(uintptr_t(JS::TraceKind::Scope) & OutOfLineTraceKindMask, "mask bits are set"); +static_assert(uintptr_t(JS::TraceKind::RegExpShared) & OutOfLineTraceKindMask, "mask bits are set"); // When this header is imported inside SpiderMonkey, the class definitions are // available and we can query those definitions to find the correct kind @@ -87,7 +90,8 @@ struct MapTypeToTraceKind { D(Script, JSScript, true) \ D(Shape, js::Shape, true) \ D(String, JSString, false) \ - D(Symbol, JS::Symbol, false) + D(Symbol, JS::Symbol, false) \ + D(RegExpShared, js::RegExpShared, true) // Map from all public types to their trace kind. #define JS_EXPAND_DEF(name, type, _) \ diff --git a/js/public/TracingAPI.h b/js/public/TracingAPI.h index 5e3692f55e..01d28d93c3 100644 --- a/js/public/TracingAPI.h +++ b/js/public/TracingAPI.h @@ -160,6 +160,9 @@ class JS_PUBLIC_API(CallbackTracer) : public JSTracer virtual void onScopeEdge(js::Scope** scopep) { onChild(JS::GCCellPtr(*scopep, JS::TraceKind::Scope)); } + virtual void onRegExpSharedEdge(js::RegExpShared** sharedp) { + onChild(JS::GCCellPtr(*sharedp, JS::TraceKind::RegExpShared)); + } // Override this method to receive notification when a node in the GC // heap graph is visited. @@ -230,6 +233,7 @@ class JS_PUBLIC_API(CallbackTracer) : public JSTracer void dispatchToOnEdge(js::jit::JitCode** codep) { onJitCodeEdge(codep); } void dispatchToOnEdge(js::LazyScript** lazyp) { onLazyScriptEdge(lazyp); } void dispatchToOnEdge(js::Scope** scopep) { onScopeEdge(scopep); } + void dispatchToOnEdge(js::RegExpShared** sharedp) { onRegExpSharedEdge(sharedp); } private: friend class AutoTracingName; diff --git a/js/src/gc/GCInternals.h b/js/src/gc/GCInternals.h index 43c9e0fd37..5c51540351 100644 --- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -75,6 +75,7 @@ struct MovingTracer : JS::CallbackTracer void onLazyScriptEdge(LazyScript** lazyp) override; void onBaseShapeEdge(BaseShape** basep) override; void onScopeEdge(Scope** basep) override; + void onRegExpSharedEdge(RegExpShared** sharedp) override; void onChild(const JS::GCCellPtr& thing) override { MOZ_ASSERT(!RelocationOverlay::isCellForwarded(thing.asCell())); } @@ -82,6 +83,10 @@ struct MovingTracer : JS::CallbackTracer #ifdef DEBUG TracerKind getTracerKind() const override { return TracerKind::Moving; } #endif + + private: + template + void updateEdge(T** thingp); }; // Structure for counting how many times objects in a particular group have diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index adc08bb121..de3db9bc31 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -735,6 +735,8 @@ class GCRuntime void enableCompactingGC(); bool isCompactingGCEnabled() const; + bool isShrinkingGC() const { return invocationKind == GC_SHRINK; } + void setGrayRootsTracer(JSTraceDataOp traceOp, void* data); MOZ_MUST_USE bool addBlackRootsTracer(JSTraceDataOp traceOp, void* data); void removeBlackRootsTracer(JSTraceDataOp traceOp, void* data); diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index ce4a0dbd56..14604d3bbe 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -115,6 +115,7 @@ enum class AllocKind { SYMBOL, JITCODE, SCOPE, + REGEXP_SHARED, LIMIT, LAST = LIMIT - 1 }; @@ -122,38 +123,39 @@ enum class AllocKind { // Macro to enumerate the different allocation kinds supplying information about // the trace kind, C++ type and allocation size. #define FOR_EACH_OBJECT_ALLOCKIND(D) \ - /* AllocKind TraceKind TypeName SizedType */ \ - D(FUNCTION, Object, JSObject, JSFunction) \ - D(FUNCTION_EXTENDED, Object, JSObject, FunctionExtended) \ - D(OBJECT0, Object, JSObject, JSObject_Slots0) \ - D(OBJECT0_BACKGROUND, Object, JSObject, JSObject_Slots0) \ - D(OBJECT2, Object, JSObject, JSObject_Slots2) \ - D(OBJECT2_BACKGROUND, Object, JSObject, JSObject_Slots2) \ - D(OBJECT4, Object, JSObject, JSObject_Slots4) \ - D(OBJECT4_BACKGROUND, Object, JSObject, JSObject_Slots4) \ - D(OBJECT8, Object, JSObject, JSObject_Slots8) \ - D(OBJECT8_BACKGROUND, Object, JSObject, JSObject_Slots8) \ - D(OBJECT12, Object, JSObject, JSObject_Slots12) \ - D(OBJECT12_BACKGROUND, Object, JSObject, JSObject_Slots12) \ - D(OBJECT16, Object, JSObject, JSObject_Slots16) \ - D(OBJECT16_BACKGROUND, Object, JSObject, JSObject_Slots16) + /* AllocKind TraceKind TypeName SizedType */ \ + D(FUNCTION, Object, JSObject, JSFunction) \ + D(FUNCTION_EXTENDED, Object, JSObject, FunctionExtended) \ + D(OBJECT0, Object, JSObject, JSObject_Slots0) \ + D(OBJECT0_BACKGROUND, Object, JSObject, JSObject_Slots0) \ + D(OBJECT2, Object, JSObject, JSObject_Slots2) \ + D(OBJECT2_BACKGROUND, Object, JSObject, JSObject_Slots2) \ + D(OBJECT4, Object, JSObject, JSObject_Slots4) \ + D(OBJECT4_BACKGROUND, Object, JSObject, JSObject_Slots4) \ + D(OBJECT8, Object, JSObject, JSObject_Slots8) \ + D(OBJECT8_BACKGROUND, Object, JSObject, JSObject_Slots8) \ + D(OBJECT12, Object, JSObject, JSObject_Slots12) \ + D(OBJECT12_BACKGROUND, Object, JSObject, JSObject_Slots12) \ + D(OBJECT16, Object, JSObject, JSObject_Slots16) \ + D(OBJECT16_BACKGROUND, Object, JSObject, JSObject_Slots16) #define FOR_EACH_NONOBJECT_ALLOCKIND(D) \ - /* AllocKind TraceKind TypeName SizedType */ \ - D(SCRIPT, Script, JSScript, JSScript) \ - D(LAZY_SCRIPT, LazyScript, js::LazyScript, js::LazyScript) \ - D(SHAPE, Shape, js::Shape, js::Shape) \ - D(ACCESSOR_SHAPE, Shape, js::AccessorShape, js::AccessorShape) \ - D(BASE_SHAPE, BaseShape, js::BaseShape, js::BaseShape) \ - D(OBJECT_GROUP, ObjectGroup, js::ObjectGroup, js::ObjectGroup) \ - D(FAT_INLINE_STRING, String, JSFatInlineString, JSFatInlineString) \ - D(STRING, String, JSString, JSString) \ - D(EXTERNAL_STRING, String, JSExternalString, JSExternalString) \ - D(FAT_INLINE_ATOM, String, js::FatInlineAtom, js::FatInlineAtom) \ - D(ATOM, String, js::NormalAtom, js::NormalAtom) \ - D(SYMBOL, Symbol, JS::Symbol, JS::Symbol) \ - D(JITCODE, JitCode, js::jit::JitCode, js::jit::JitCode) \ - D(SCOPE, Scope, js::Scope, js::Scope) + /* AllocKind TraceKind TypeName SizedType */ \ + D(SCRIPT, Script, JSScript, JSScript) \ + D(LAZY_SCRIPT, LazyScript, js::LazyScript, js::LazyScript) \ + D(SHAPE, Shape, js::Shape, js::Shape) \ + D(ACCESSOR_SHAPE, Shape, js::AccessorShape, js::AccessorShape) \ + D(BASE_SHAPE, BaseShape, js::BaseShape, js::BaseShape) \ + D(OBJECT_GROUP, ObjectGroup, js::ObjectGroup, js::ObjectGroup) \ + D(FAT_INLINE_STRING, String, JSFatInlineString, JSFatInlineString) \ + D(STRING, String, JSString, JSString) \ + D(EXTERNAL_STRING, String, JSExternalString, JSExternalString) \ + D(FAT_INLINE_ATOM, String, js::FatInlineAtom, js::FatInlineAtom) \ + D(ATOM, String, js::NormalAtom, js::NormalAtom) \ + D(SYMBOL, Symbol, JS::Symbol, JS::Symbol) \ + D(JITCODE, JitCode, js::jit::JitCode, js::jit::JitCode) \ + D(SCOPE, Scope, js::Scope, js::Scope) \ + D(REGEXP_SHARED, RegExpShared, js::RegExpShared, js::RegExpShared) #define FOR_EACH_ALLOCKIND(D) \ FOR_EACH_OBJECT_ALLOCKIND(D) \ @@ -338,6 +340,9 @@ class TenuredCell : public Cell static MOZ_ALWAYS_INLINE void writeBarrierPost(void* cellp, TenuredCell* prior, TenuredCell* next); + // Default implementation for kinds that don't require finalization. + void finalize(FreeOp* fop) {} + // Default implementation for kinds that don't require fixup. void fixupAfterMovingGC() {} diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index da8da5c6b5..058c495a77 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -436,6 +436,14 @@ js::TraceNullableEdge(JSTracer* trc, WriteBarrieredBase* thingp, const char* DispatchToTracer(trc, ConvertToBase(thingp->unsafeUnbarrieredForTracing()), name); } +template +void +js::TraceNullableEdge(JSTracer* trc, ReadBarriered* thingp, const char* name) +{ + if (InternalBarrierMethods::isMarkable(thingp->unbarrieredGet())) + DispatchToTracer(trc, ConvertToBase(thingp->unsafeGet()), name); +} + template JS_PUBLIC_API(void) JS::TraceEdge(JSTracer* trc, JS::Heap* thingp, const char* name) @@ -550,6 +558,7 @@ js::TraceRootRange(JSTracer* trc, size_t len, T* vec, const char* name) template void js::TraceEdge(JSTracer*, WriteBarrieredBase*, const char*); \ template void js::TraceEdge(JSTracer*, ReadBarriered*, const char*); \ template void js::TraceNullableEdge(JSTracer*, WriteBarrieredBase*, const char*); \ + template void js::TraceNullableEdge(JSTracer*, ReadBarriered*, const char*); \ template void js::TraceManuallyBarrieredEdge(JSTracer*, type*, const char*); \ template void js::TraceWeakEdge(JSTracer*, WeakRef*, const char*); \ template void js::TraceRoot(JSTracer*, type*, const char*); \ @@ -866,6 +875,7 @@ js::GCMarker::markAndTraceChildren(T* thing) namespace js { template <> void GCMarker::traverse(BaseShape* thing) { markAndTraceChildren(thing); } template <> void GCMarker::traverse(JS::Symbol* thing) { markAndTraceChildren(thing); } +template <> void GCMarker::traverse(RegExpShared* thing) { markAndTraceChildren(thing); } } // namespace js // Strings, LazyScripts, Shapes, and Scopes are extremely common, but have diff --git a/js/src/gc/Policy.h b/js/src/gc/Policy.h index 451dac1194..8a974dd0d4 100644 --- a/js/src/gc/Policy.h +++ b/js/src/gc/Policy.h @@ -84,6 +84,7 @@ class JitCode; D(js::PlainObject*) \ D(js::PropertyName*) \ D(js::RegExpObject*) \ + D(js::RegExpShared*) \ D(js::SavedFrame*) \ D(js::Scope*) \ D(js::ScriptSourceObject*) \ diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 7a4c8f315a..329851a669 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -165,6 +165,7 @@ static const PhaseInfo phases[] = { { PHASE_SWEEP_STRING, "Sweep String", PHASE_SWEEP }, { PHASE_SWEEP_SCRIPT, "Sweep Script", PHASE_SWEEP }, { PHASE_SWEEP_SCOPE, "Sweep Scope", PHASE_SWEEP }, + { PHASE_SWEEP_REGEXP_SHARED, "Sweep RegExpShared", PHASE_SWEEP }, { PHASE_SWEEP_SHAPE, "Sweep Shape", PHASE_SWEEP }, { PHASE_SWEEP_JITCODE, "Sweep JIT code", PHASE_SWEEP }, { PHASE_FINALIZE_END, "Finalize End Callback", PHASE_SWEEP }, diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 2b1ce969d1..9e28be9f97 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -62,6 +62,7 @@ enum Phase : uint8_t { PHASE_SWEEP_STRING, PHASE_SWEEP_SCRIPT, PHASE_SWEEP_SCOPE, + PHASE_SWEEP_REGEXP_SHARED, PHASE_SWEEP_SHAPE, PHASE_SWEEP_JITCODE, PHASE_FINALIZE_END, diff --git a/js/src/gc/Tracer.h b/js/src/gc/Tracer.h index 4c846f95d6..76eec26182 100644 --- a/js/src/gc/Tracer.h +++ b/js/src/gc/Tracer.h @@ -64,6 +64,10 @@ template void TraceNullableEdge(JSTracer* trc, WriteBarrieredBase* thingp, const char* name); +template +void +TraceNullableEdge(JSTracer* trc, ReadBarriered* thingp, const char* name); + // Trace through a "root" edge. These edges are the initial edges in the object // graph traversal. Root edges are asserted to only be traversed in the initial // phase of a GC. diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 0d23c96cb4..2aec76dbd9 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -6144,7 +6144,7 @@ JS_GetRegExpFlags(JSContext* cx, HandleObject obj) RegExpGuard shared(cx); if (!RegExpToShared(cx, obj, &shared)) return false; - return shared.re()->getFlags(); + return shared->getFlags(); } JS_PUBLIC_API(JSString*) @@ -6156,7 +6156,7 @@ JS_GetRegExpSource(JSContext* cx, HandleObject obj) RegExpGuard shared(cx); if (!RegExpToShared(cx, obj, &shared)) return nullptr; - return shared.re()->getSource(); + return shared->getSource(); } /************************************************************************/ diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 8cf22026f3..7cccd25ad6 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -61,7 +61,7 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options = data(nullptr), allocationMetadataBuilder(nullptr), lastAnimationTime(0), - regExps(runtime_), + regExps(zone), globalWriteBarriered(0), detachedTypedObjects(0), objectMetadataState(ImmediateMetadata()), @@ -210,6 +210,13 @@ JSCompartment::ensureJitCompartmentExists(JSContext* cx) } #ifdef JSGC_HASH_TABLE_CHECKS + +void +js::DtoaCache::checkCacheAfterMovingGC() +{ + MOZ_ASSERT(!s || !IsForwarded(s)); +} + namespace { struct CheckGCThingAfterMovingGCFunctor { template void operator()(T* t) { CheckGCThingAfterMovingGC(*t); } @@ -232,7 +239,8 @@ JSCompartment::checkWrapperMapAfterMovingGC() MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front()); } } -#endif + +#endif // JSGC_HASH_TABLE_CHECKS bool JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped, diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 7723aeffa4..becf9a39ff 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -64,7 +64,7 @@ class DtoaCache { } #ifdef JSGC_HASH_TABLE_CHECKS - void checkCacheAfterMovingGC() { MOZ_ASSERT(!s || !IsForwarded(s)); } + void checkCacheAfterMovingGC(); #endif }; diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 00da8295ad..f42a4def5f 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -1114,7 +1114,7 @@ extern JS_FRIEND_API(unsigned) GetEnterCompartmentDepth(JSContext* cx); #endif -class RegExpGuard; +using RegExpGuard = JS::Rooted; extern JS_FRIEND_API(bool) RegExpToSharedNonInline(JSContext* cx, JS::HandleObject regexp, RegExpGuard* shared); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 0ddc128e2d..7fb035794b 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -357,7 +357,12 @@ static const FinalizePhase BackgroundFinalizePhases[] = { }, { gcstats::PHASE_SWEEP_SCOPE, { - AllocKind::SCOPE + AllocKind::SCOPE, + } + }, + { + gcstats::PHASE_SWEEP_REGEXP_SHARED, { + AllocKind::REGEXP_SHARED, } }, { @@ -1599,7 +1604,6 @@ static const AllocKind AllocKindsToRelocate[] = { AllocKind::OBJECT16_BACKGROUND, AllocKind::SCRIPT, AllocKind::LAZY_SCRIPT, - AllocKind::SCOPE, AllocKind::SHAPE, AllocKind::ACCESSOR_SHAPE, AllocKind::BASE_SHAPE, @@ -1607,7 +1611,9 @@ static const AllocKind AllocKindsToRelocate[] = { AllocKind::STRING, AllocKind::EXTERNAL_STRING, AllocKind::FAT_INLINE_ATOM, - AllocKind::ATOM + AllocKind::ATOM, + AllocKind::SCOPE, + AllocKind::REGEXP_SHARED }; Arena* @@ -1931,61 +1937,23 @@ GCRuntime::relocateArenas(Zone* zone, JS::gcreason::Reason reason, Arena*& reloc return true; } -void -MovingTracer::onObjectEdge(JSObject** objp) +template +inline void +MovingTracer::updateEdge(T** thingp) { - JSObject* obj = *objp; - if (obj->runtimeFromAnyThread() == runtime() && IsForwarded(obj)) - *objp = Forwarded(obj); + auto thing = *thingp; + if (thing->runtimeFromAnyThread() == runtime() && IsForwarded(thing)) + *thingp = Forwarded(thing); } -void -MovingTracer::onShapeEdge(Shape** shapep) -{ - Shape* shape = *shapep; - if (shape->runtimeFromAnyThread() == runtime() && IsForwarded(shape)) - *shapep = Forwarded(shape); -} - -void -MovingTracer::onStringEdge(JSString** stringp) -{ - JSString* string = *stringp; - if (string->runtimeFromAnyThread() == runtime() && IsForwarded(string)) - *stringp = Forwarded(string); -} - -void -MovingTracer::onScriptEdge(JSScript** scriptp) -{ - JSScript* script = *scriptp; - if (script->runtimeFromAnyThread() == runtime() && IsForwarded(script)) - *scriptp = Forwarded(script); -} - -void -MovingTracer::onLazyScriptEdge(LazyScript** lazyp) -{ - LazyScript* lazy = *lazyp; - if (lazy->runtimeFromAnyThread() == runtime() && IsForwarded(lazy)) - *lazyp = Forwarded(lazy); -} - -void -MovingTracer::onBaseShapeEdge(BaseShape** basep) -{ - BaseShape* base = *basep; - if (base->runtimeFromAnyThread() == runtime() && IsForwarded(base)) - *basep = Forwarded(base); -} - -void -MovingTracer::onScopeEdge(Scope** scopep) -{ - Scope* scope = *scopep; - if (scope->runtimeFromAnyThread() == runtime() && IsForwarded(scope)) - *scopep = Forwarded(scope); -} +void MovingTracer::onObjectEdge(JSObject** objp) { updateEdge(objp); } +void MovingTracer::onShapeEdge(Shape** shapep) { updateEdge(shapep); } +void MovingTracer::onStringEdge(JSString** stringp) { updateEdge(stringp); } +void MovingTracer::onScriptEdge(JSScript** scriptp) { updateEdge(scriptp); } +void MovingTracer::onLazyScriptEdge(LazyScript** lazyp) { updateEdge(lazyp); } +void MovingTracer::onBaseShapeEdge(BaseShape** basep) { updateEdge(basep); } +void MovingTracer::onScopeEdge(Scope** scopep) { updateEdge(scopep); } +void MovingTracer::onRegExpSharedEdge(RegExpShared** sharedp) { updateEdge(sharedp); } void Zone::prepareForCompacting() diff --git a/js/src/jsgc.h b/js/src/jsgc.h index f31f0893b1..4d6e086521 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -121,6 +121,7 @@ IsNurseryAllocable(AllocKind kind) false, /* AllocKind::SYMBOL */ false, /* AllocKind::JITCODE */ false, /* AllocKind::SCOPE */ + false, /* AllocKind::REGEXP_SHARED */ }; JS_STATIC_ASSERT(JS_ARRAY_LENGTH(map) == size_t(AllocKind::LIMIT)); return map[size_t(kind)]; @@ -159,6 +160,7 @@ IsBackgroundFinalized(AllocKind kind) true, /* AllocKind::SYMBOL */ false, /* AllocKind::JITCODE */ true, /* AllocKind::SCOPE */ + true, /* AllocKind::REGEXP_SHARED */ }; JS_STATIC_ASSERT(JS_ARRAY_LENGTH(map) == size_t(AllocKind::LIMIT)); return map[size_t(kind)]; @@ -1172,109 +1174,29 @@ class RelocationOverlay // to allow slots to be accessed. template -struct MightBeForwarded -{ - static_assert(mozilla::IsBaseOf::value, - "T must derive from Cell"); - static_assert(!mozilla::IsSame::value && !mozilla::IsSame::value, - "T must not be Cell or TenuredCell"); - - static const bool value = mozilla::IsBaseOf::value || - mozilla::IsBaseOf::value || - mozilla::IsBaseOf::value || - mozilla::IsBaseOf::value || - mozilla::IsBaseOf::value || - mozilla::IsBaseOf::value || - mozilla::IsBaseOf::value; -}; +inline bool IsForwarded(T* t); +inline bool IsForwarded(const JS::Value& value); template -inline bool -IsForwarded(T* t) -{ - RelocationOverlay* overlay = RelocationOverlay::fromCell(t); - if (!MightBeForwarded::value) { - MOZ_ASSERT(!overlay->isForwarded()); - return false; - } +inline T* Forwarded(T* t); - return overlay->isForwarded(); -} - -struct IsForwardedFunctor : public BoolDefaultAdaptor { - template bool operator()(T* t) { return IsForwarded(t); } -}; - -inline bool -IsForwarded(const JS::Value& value) -{ - return DispatchTyped(IsForwardedFunctor(), value); -} +inline Value Forwarded(const JS::Value& value); template -inline T* -Forwarded(T* t) -{ - RelocationOverlay* overlay = RelocationOverlay::fromCell(t); - MOZ_ASSERT(overlay->isForwarded()); - return reinterpret_cast(overlay->forwardingAddress()); -} - -struct ForwardedFunctor : public IdentityDefaultAdaptor { - template inline Value operator()(T* t) { - return js::gc::RewrapTaggedPointer::wrap(Forwarded(t)); - } -}; - -inline Value -Forwarded(const JS::Value& value) -{ - return DispatchTyped(ForwardedFunctor(), value); -} - -template -inline T -MaybeForwarded(T t) -{ - if (IsForwarded(t)) - t = Forwarded(t); - MakeAccessibleAfterMovingGC(t); - return t; -} +inline T MaybeForwarded(T t); #ifdef JSGC_HASH_TABLE_CHECKS template -inline bool -IsGCThingValidAfterMovingGC(T* t) -{ - return !IsInsideNursery(t) && !RelocationOverlay::isCellForwarded(t); -} +inline bool IsGCThingValidAfterMovingGC(T* t); template -inline void -CheckGCThingAfterMovingGC(T* t) -{ - if (t) - MOZ_RELEASE_ASSERT(IsGCThingValidAfterMovingGC(t)); -} +inline void CheckGCThingAfterMovingGC(T* t); template -inline void -CheckGCThingAfterMovingGC(const ReadBarriered& t) -{ - CheckGCThingAfterMovingGC(t.unbarrieredGet()); -} +inline void CheckGCThingAfterMovingGC(const ReadBarriered& t); -struct CheckValueAfterMovingGCFunctor : public VoidDefaultAdaptor { - template void operator()(T* t) { CheckGCThingAfterMovingGC(t); } -}; - -inline void -CheckValueAfterMovingGC(const JS::Value& value) -{ - DispatchTyped(CheckValueAfterMovingGCFunctor(), value); -} +inline void CheckValueAfterMovingGC(const JS::Value& value); #endif // JSGC_HASH_TABLE_CHECKS diff --git a/js/src/jsgcinlines.h b/js/src/jsgcinlines.h index 01ac9a64a2..1c96d37c00 100644 --- a/js/src/jsgcinlines.h +++ b/js/src/jsgcinlines.h @@ -477,6 +477,114 @@ RelocationOverlay::forwardTo(Cell* cell) newLocation_ = cell; } +template +struct MightBeForwarded +{ + static_assert(mozilla::IsBaseOf::value, + "T must derive from Cell"); + static_assert(!mozilla::IsSame::value && !mozilla::IsSame::value, + "T must not be Cell or TenuredCell"); + + static const bool value = mozilla::IsBaseOf::value || + mozilla::IsBaseOf::value || + mozilla::IsBaseOf::value || + mozilla::IsBaseOf::value || + mozilla::IsBaseOf::value || + mozilla::IsBaseOf::value || + mozilla::IsBaseOf::value || + mozilla::IsBaseOf::value; +}; + +template +inline bool +IsForwarded(T* t) +{ + RelocationOverlay* overlay = RelocationOverlay::fromCell(t); + if (!MightBeForwarded::value) { + MOZ_ASSERT(!overlay->isForwarded()); + return false; + } + + return overlay->isForwarded(); +} + +struct IsForwardedFunctor : public BoolDefaultAdaptor { + template bool operator()(T* t) { return IsForwarded(t); } +}; + +inline bool +IsForwarded(const JS::Value& value) +{ + return DispatchTyped(IsForwardedFunctor(), value); +} + +template +inline T* +Forwarded(T* t) +{ + RelocationOverlay* overlay = RelocationOverlay::fromCell(t); + MOZ_ASSERT(overlay->isForwarded()); + return reinterpret_cast(overlay->forwardingAddress()); +} + +struct ForwardedFunctor : public IdentityDefaultAdaptor { + template inline Value operator()(T* t) { + return js::gc::RewrapTaggedPointer::wrap(Forwarded(t)); + } +}; + +inline Value +Forwarded(const JS::Value& value) +{ + return DispatchTyped(ForwardedFunctor(), value); +} + +template +inline T +MaybeForwarded(T t) +{ + if (IsForwarded(t)) + t = Forwarded(t); + MakeAccessibleAfterMovingGC(t); + return t; +} + +#ifdef JSGC_HASH_TABLE_CHECKS + +template +inline bool +IsGCThingValidAfterMovingGC(T* t) +{ + return !IsInsideNursery(t) && !RelocationOverlay::isCellForwarded(t); +} + +template +inline void +CheckGCThingAfterMovingGC(T* t) +{ + if (t) + MOZ_RELEASE_ASSERT(IsGCThingValidAfterMovingGC(t)); +} + +template +inline void +CheckGCThingAfterMovingGC(const ReadBarriered& t) +{ + CheckGCThingAfterMovingGC(t.unbarrieredGet()); +} + +struct CheckValueAfterMovingGCFunctor : public VoidDefaultAdaptor { + template void operator()(T* t) { CheckGCThingAfterMovingGC(t); } +}; + +inline void +CheckValueAfterMovingGC(const JS::Value& value) +{ + DispatchTyped(CheckValueAfterMovingGCFunctor(), value); +} + +#endif // JSGC_HASH_TABLE_CHECKS + } /* namespace gc */ } /* namespace js */ diff --git a/js/src/proxy/CrossCompartmentWrapper.cpp b/js/src/proxy/CrossCompartmentWrapper.cpp index e12f96bfa7..bc698c8739 100644 --- a/js/src/proxy/CrossCompartmentWrapper.cpp +++ b/js/src/proxy/CrossCompartmentWrapper.cpp @@ -439,15 +439,14 @@ CrossCompartmentWrapper::fun_toString(JSContext* cx, HandleObject wrapper, bool bool CrossCompartmentWrapper::regexp_toShared(JSContext* cx, HandleObject wrapper, RegExpGuard* g) const { - RegExpGuard wrapperGuard(cx); + RegExpGuard re(cx); { AutoCompartment call(cx, wrappedObject(wrapper)); - if (!Wrapper::regexp_toShared(cx, wrapper, &wrapperGuard)) + if (!Wrapper::regexp_toShared(cx, wrapper, &re)) return false; } // Get an equivalent RegExpShared associated with the current compartment. - RegExpShared* re = wrapperGuard.re(); return cx->compartment()->regExps.get(cx, re->getSource(), re->getFlags(), g); } diff --git a/js/src/proxy/Proxy.h b/js/src/proxy/Proxy.h index a1942b25de..4064b8a7f2 100644 --- a/js/src/proxy/Proxy.h +++ b/js/src/proxy/Proxy.h @@ -12,8 +12,6 @@ namespace js { -class RegExpGuard; - /* * Dispatch point for handlers that executes the appropriate C++ or scripted traps. * diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp index ab8648ad3f..9b6d3dda72 100644 --- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -595,6 +595,13 @@ StatsCellCallback(JSRuntime* rt, void* data, void* thing, JS::TraceKind traceKin break; } + case JS::TraceKind::RegExpShared: { + auto regexp = static_cast(thing); + zStats->regExpSharedsGCHeap += thingSize; + zStats->regExpSharedsMallocHeap += regexp->sizeOfExcludingThis(rtStats->mallocSizeOf_); + break; + } + default: MOZ_CRASH("invalid traceKind in StatsCellCallback"); } diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 151571085d..4cc2b539aa 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -8,6 +8,7 @@ #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" +#include "jshashutil.h" #include "jsstr.h" #ifdef DEBUG #include "jsutil.h" @@ -120,25 +121,11 @@ VectorMatchPairs::allocOrExpandArray(size_t pairCount) /* RegExpObject */ -static inline void -RegExpSharedReadBarrier(JSContext* cx, RegExpShared* shared) -{ - Zone* zone = cx->zone(); - if (zone->needsIncrementalBarrier()) - shared->trace(zone->barrierTracer()); - if (shared->isMarkedGray()) - shared->unmarkGray(); -} - /* static */ bool RegExpObject::getShared(JSContext* cx, Handle regexp, RegExpGuard* g) { - if (RegExpShared* shared = regexp->maybeShared()) { - // Fetching a RegExpShared from an object requires a read - // barrier, as the shared pointer might be weak. - RegExpSharedReadBarrier(cx, shared); - - g->init(*shared); + if (regexp->hasShared()) { + g->set(regexp->sharedRef()); return true; } @@ -179,26 +166,32 @@ RegExpObject::isOriginalFlagGetter(JSNative native, RegExpFlag* mask) /* static */ void RegExpObject::trace(JSTracer* trc, JSObject* obj) { - RegExpShared* shared = obj->as().maybeShared(); - if (!shared) - return; + obj->as().trace(trc); +} - // When tracing through the object normally, we have the option of - // unlinking the object from its RegExpShared so that the RegExpShared may - // be collected. To detect this we need to test all the following - // conditions, since: +static inline bool +IsMarkingTrace(JSTracer* trc) +{ + // Determine whether tracing is happening during normal marking. We need to + // test all the following conditions, since: + // // 1. During TraceRuntime, isHeapBusy() is true, but the tracer might not // be a marking tracer. // 2. When a write barrier executes, IsMarkingTracer is true, but // isHeapBusy() will be false. - if (trc->runtime()->isHeapCollecting() && - trc->isMarkingTracer() && - !obj->asTenured().zone()->isPreservingCode()) - { - obj->as().NativeObject::setPrivate(nullptr); - } else { - shared->trace(trc); - } + + return trc->runtime()->isHeapCollecting() && trc->isMarkingTracer(); +} + +void +RegExpObject::trace(JSTracer* trc) +{ + // When marking the object normally we have the option of unlinking the + // object from its RegExpShared so that the RegExpShared may be collected. + if (IsMarkingTrace(trc) && !zone()->isPreservingCode()) + sharedRef() = nullptr; + + TraceNullableEdge(trc, &sharedRef(), "RegExpObject shared"); } static JSObject* @@ -287,7 +280,7 @@ RegExpObject::create(ExclusiveContext* cx, HandleAtom source, RegExpFlag flags, /* static */ bool RegExpObject::createShared(JSContext* cx, Handle regexp, RegExpGuard* g) { - MOZ_ASSERT(!regexp->maybeShared()); + MOZ_ASSERT(!regexp->hasShared()); if (!cx->compartment()->regExps.get(cx, regexp->getSource(), regexp->getFlags(), g)) return false; @@ -902,7 +895,7 @@ RegExpObject::dumpBytecode(JSContext* cx, Handle regexp, if (!getShared(cx, regexp, &g)) return false; - return g.re()->dumpBytecode(cx, match_only, input); + return g->dumpBytecode(cx, match_only, input); } #endif @@ -951,7 +944,7 @@ js::StringHasRegExpMetaChars(JSLinearString* str) /* RegExpShared */ RegExpShared::RegExpShared(JSAtom* source, RegExpFlag flags) - : source(source), flags(flags), parenCount(0), canStringMatch(false), marked_(false), + : source(source), flags(flags), canStringMatch(false), parenCount(0), numNamedCaptures_(0), groupsTemplate_(nullptr) {} @@ -962,10 +955,11 @@ RegExpShared::~RegExpShared() } void -RegExpShared::trace(JSTracer* trc) +RegExpShared::traceChildren(JSTracer* trc) { - if (trc->isMarkingTracer()) - marked_ = true; + // Discard code to avoid holding onto ExecutablePools. + if (IsMarkingTrace(trc) && trc->runtime()->gc.isShrinkingGC()) + discardJitCode(); TraceNullableEdge(trc, &source, "RegExpShared source"); for (auto& comp : compilationArray) @@ -973,27 +967,11 @@ RegExpShared::trace(JSTracer* trc) TraceNullableEdge(trc, &groupsTemplate_, "RegExpShared groupsTemplate"); } -bool -RegExpShared::isMarkedGray() const -{ - if (source && source->isMarked(gc::GRAY)) - return true; - for (const auto& comp : compilationArray) { - if (comp.jitCode && comp.jitCode->isMarked(gc::GRAY)) - return true; - } - return false; -} - void -RegExpShared::unmarkGray() +RegExpShared::discardJitCode() { - if (source) - JS::UnmarkGrayGCThingRecursively(JS::GCCellPtr(source)); - for (const auto& comp : compilationArray) { - if (comp.jitCode) - JS::UnmarkGrayGCThingRecursively(JS::GCCellPtr(comp.jitCode.get())); - } + for (auto& comp : compilationArray) + comp.jitCode = nullptr; } bool @@ -1246,9 +1224,9 @@ RegExpShared::execute(JSContext* cx, HandleLinearString input, size_t start, } size_t -RegExpShared::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) +RegExpShared::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { - size_t n = mallocSizeOf(this); + size_t n = 0; for (size_t i = 0; i < ArrayLength(compilationArray); i++) { const RegExpCompilation& compilation = compilationArray[i]; @@ -1265,8 +1243,8 @@ RegExpShared::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) /* RegExpCompartment */ -RegExpCompartment::RegExpCompartment(JSRuntime* rt) - : set_(rt), +RegExpCompartment::RegExpCompartment(Zone* zone) + : set_(zone, Set(zone->runtimeFromMainThread())), matchResultTemplateObject_(nullptr), optimizableRegExpPrototypeShape_(nullptr), optimizableRegExpInstanceShape_(nullptr) @@ -1274,14 +1252,7 @@ RegExpCompartment::RegExpCompartment(JSRuntime* rt) RegExpCompartment::~RegExpCompartment() { - // Because of stray mark bits being set (see RegExpCompartment::sweep) - // there might still be RegExpShared instances which haven't been deleted. - if (set_.initialized()) { - for (Set::Enum e(set_); !e.empty(); e.popFront()) { - RegExpShared* shared = e.front(); - js_delete(shared); - } - } + MOZ_ASSERT_IF(set_.initialized(), set_.empty()); } ArrayObject* @@ -1291,7 +1262,7 @@ RegExpCompartment::createMatchResultTemplateObject(JSContext* cx) /* Create template array object */ RootedArrayObject templateObject(cx, NewDenseUnallocatedArray(cx, RegExpObject::MaxPairCount, - nullptr, TenuredObject)); + nullptr, TenuredObject)); if (!templateObject) return matchResultTemplateObject_; // = nullptr @@ -1357,59 +1328,9 @@ RegExpCompartment::init(JSContext* cx) return true; } -bool -RegExpShared::needsSweep(JSRuntime* rt) -{ - // Sometimes RegExpShared instances are marked without the compartment - // being subsequently cleared. This can happen if a GC is restarted while - // in progress (i.e. performing a full GC in the middle of an incremental - // GC) or if a RegExpShared referenced via the stack is traced but is not - // in a zone being collected. - // - // Because of this we only treat the marked_ bit as a hint, and destroy the - // RegExpShared if it was accidentally marked earlier but wasn't marked by - // the current trace. - bool keep = marked() && IsMarked(rt, &source); - for (size_t i = 0; i < ArrayLength(compilationArray); i++) { - RegExpShared::RegExpCompilation& compilation = compilationArray[i]; - if (compilation.jitCode && gc::IsAboutToBeFinalized(&compilation.jitCode)) - keep = false; - } - - MOZ_ASSERT(rt->isHeapMajorCollecting()); - if (keep || rt->gc.isHeapCompacting()) { - clearMarked(); - return false; - } - - return true; -} - -void -RegExpShared::discardJitCode() -{ - for (size_t i = 0; i < ArrayLength(compilationArray); i++) - compilationArray[i].jitCode = nullptr; -} - void RegExpCompartment::sweep(JSRuntime* rt) { - if (!set_.initialized()) - return; - - for (Set::Enum e(set_); !e.empty(); e.popFront()) { - RegExpShared* shared = e.front(); - if (shared->needsSweep(rt)) { - js_delete(shared); - e.removeFront(); - } else { - // Discard code to avoid holding onto ExecutablePools. - if (rt->gc.isHeapCompacting()) - shared->discardJitCode(); - } - } - if (matchResultTemplateObject_ && IsAboutToBeFinalized(&matchResultTemplateObject_)) { @@ -1432,30 +1353,24 @@ RegExpCompartment::sweep(JSRuntime* rt) bool RegExpCompartment::get(JSContext* cx, JSAtom* source, RegExpFlag flags, RegExpGuard* g) { - Key key(source, flags); - Set::AddPtr p = set_.lookupForAdd(key); + DependentAddPtr p(cx, set_.get(), Key(source, flags)); if (p) { - // Trigger a read barrier on existing RegExpShared instances fetched - // from the table (which only holds weak references). - RegExpSharedReadBarrier(cx, *p); - - g->init(**p); + g->set(*p); return true; } - ScopedJSDeletePtr shared(cx->new_(source, flags)); + auto shared = Allocate(cx); if (!shared) return false; - if (!set_.add(p, shared)) { + new (shared) RegExpShared(source, flags); + + if (!p.add(cx, set_.get(), Key(source, flags), shared)) { ReportOutOfMemory(cx); return false; } - // Trace RegExpShared instances created during an incremental GC. - RegExpSharedReadBarrier(cx, shared); - - g->init(*shared.forget()); + g->set(shared); return true; } @@ -1472,13 +1387,7 @@ RegExpCompartment::get(JSContext* cx, HandleAtom atom, JSString* opt, RegExpGuar size_t RegExpCompartment::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { - size_t n = 0; - n += set_.sizeOfExcludingThis(mallocSizeOf); - for (Set::Enum e(set_); !e.empty(); e.popFront()) { - RegExpShared* shared = e.front(); - n += shared->sizeOfIncludingThis(mallocSizeOf); - } - return n; + return set_.sizeOfExcludingThis(mallocSizeOf); } /* Functions */ @@ -1506,7 +1415,7 @@ js::CloneRegExpObject(JSContext* cx, JSObject* obj_) return nullptr; clone->initAndZeroLastIndex(source, g->getFlags(), cx); - clone->setShared(*g.re()); + clone->setShared(*g); return clone; } @@ -1649,3 +1558,10 @@ js::RegExpToSharedNonInline(JSContext* cx, HandleObject obj, js::RegExpGuard* g) { return RegExpToShared(cx, obj, g); } + +JS::ubi::Node::Size +JS::ubi::Concrete::size(mozilla::MallocSizeOf mallocSizeOf) const +{ + return js::gc::Arena::thingSize(gc::AllocKind::REGEXP_SHARED) + + get().sizeOfExcludingThis(mallocSizeOf); +} diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 547746529a..16ba7f41fd 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -46,7 +46,7 @@ class RegExpStatics; namespace frontend { class TokenStream; } -enum RegExpFlag +enum RegExpFlag : uint8_t { IgnoreCaseFlag = 0x01, GlobalFlag = 0x02, @@ -94,7 +94,7 @@ CloneRegExpObject(JSContext* cx, JSObject* regexp); * objects when we are preserving jitcode in their zone, to avoid the same * recompilation inefficiencies as normal Ion and baseline compilation. */ -class RegExpShared +class RegExpShared : public gc::TenuredCell { public: enum CompilationMode { @@ -115,7 +115,7 @@ class RegExpShared struct RegExpCompilation { - HeapPtr jitCode; + ReadBarriered jitCode; uint8_t* byteCode; RegExpCompilation() : byteCode(nullptr) {} @@ -127,12 +127,11 @@ class RegExpShared }; /* Source to the RegExp, for lazy compilation. */ - HeapPtr source; + HeapPtr source; RegExpFlag flags; - size_t parenCount; bool canStringMatch; - bool marked_; + size_t parenCount; uint32_t numNamedCaptures_; HeapPtr groupsTemplate_; @@ -151,6 +150,8 @@ class RegExpShared Vector tables; /* Internal functions. */ + RegExpShared(JSAtom* source, RegExpFlag flags); + bool compile(JSContext* cx, HandleLinearString input, CompilationMode mode, ForceByteCodeEnum force); bool compile(JSContext* cx, HandleAtom pattern, HandleLinearString input, @@ -168,7 +169,6 @@ class RegExpShared } public: - RegExpShared(JSAtom* source, RegExpFlag flags); ~RegExpShared(); // Execute this RegExp on input starting from searchIndex, filling in @@ -214,16 +214,9 @@ class RegExpShared || isCompiled(MatchOnly, true) || isCompiled(MatchOnly, false); } - void trace(JSTracer* trc); - bool needsSweep(JSRuntime* rt); + void traceChildren(JSTracer* trc); void discardJitCode(); - bool marked() const { return marked_; } - void clearMarked() { marked_ = false; } - - bool isMarkedGray() const; - void unmarkGray(); - static size_t offsetOfSource() { return offsetof(RegExpShared, source); } @@ -251,60 +244,13 @@ class RegExpShared return offsetof(RegExpShared, groupsTemplate_); } - size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); #ifdef DEBUG bool dumpBytecode(JSContext* cx, bool match_only, HandleLinearString input); #endif }; -/* - * Extend the lifetime of a given RegExpShared to at least the lifetime of - * the guard object. See Regular Expression comment at the top. - */ -class RegExpGuard : public JS::CustomAutoRooter -{ - RegExpShared* re_; - - RegExpGuard(const RegExpGuard&) = delete; - void operator=(const RegExpGuard&) = delete; - - public: - explicit RegExpGuard(ExclusiveContext* cx) - : CustomAutoRooter(cx), re_(nullptr) - {} - - RegExpGuard(ExclusiveContext* cx, RegExpShared& re) - : CustomAutoRooter(cx), re_(nullptr) - { - init(re); - } - - ~RegExpGuard() { - release(); - } - - public: - void init(RegExpShared& re) { - MOZ_ASSERT(!initialized()); - re_ = &re; - } - - void release() { - re_ = nullptr; - } - - virtual void trace(JSTracer* trc) { - if (re_) - re_->trace(trc); - } - - bool initialized() const { return !!re_; } - RegExpShared* re() const { MOZ_ASSERT(initialized()); return re_; } - RegExpShared* operator->() { return re(); } - RegExpShared& operator*() { return *re(); } -}; - class RegExpCompartment { struct Key { @@ -315,8 +261,9 @@ class RegExpCompartment Key(JSAtom* atom, RegExpFlag flag) : atom(atom), flag(flag) { } - MOZ_IMPLICIT Key(RegExpShared* shared) - : atom(shared->getSource()), flag(shared->getFlags()) + MOZ_IMPLICIT Key(const ReadBarriered& shared) + : atom(shared.unbarrieredGet()->getSource()), + flag(shared.unbarrieredGet()->getFlags()) { } typedef Key Lookup; @@ -332,8 +279,8 @@ class RegExpCompartment * The set of all RegExpShareds in the compartment. On every GC, every * RegExpShared that was not marked is deleted and removed from the set. */ - typedef HashSet Set; - Set set_; + using Set = GCHashSet, Key, RuntimeAllocPolicy>; + JS::WeakCache set_; /* * This is the template object where the result of re.exec() is based on, @@ -366,7 +313,7 @@ class RegExpCompartment ArrayObject* createMatchResultTemplateObject(JSContext* cx); public: - explicit RegExpCompartment(JSRuntime* rt); + explicit RegExpCompartment(Zone* zone); ~RegExpCompartment(); bool init(JSContext* cx); @@ -502,12 +449,17 @@ class RegExpObject : public NativeObject static MOZ_MUST_USE bool getShared(JSContext* cx, Handle regexp, RegExpGuard* g); + bool hasShared() { + return !!sharedRef(); + } + void setShared(RegExpShared& shared) { - MOZ_ASSERT(!maybeShared()); - NativeObject::setPrivate(&shared); + MOZ_ASSERT(!hasShared()); + sharedRef() = &shared; } static void trace(JSTracer* trc, JSObject* obj); + void trace(JSTracer* trc); void initIgnoringLastIndex(HandleAtom source, RegExpFlag flags); @@ -528,8 +480,10 @@ class RegExpObject : public NativeObject */ static MOZ_MUST_USE bool createShared(JSContext* cx, Handle regexp, RegExpGuard* g); - RegExpShared* maybeShared() const { - return static_cast(NativeObject::getPrivate(PRIVATE_SLOT)); + + ReadBarriered& sharedRef() { + auto& ref = NativeObject::privateRef(PRIVATE_SLOT); + return reinterpret_cast&>(ref); } /* Call setShared in preference to setPrivate. */ @@ -575,4 +529,29 @@ StringHasRegExpMetaChars(JSLinearString* str); } /* namespace js */ +namespace JS { +namespace ubi { + +template <> +class Concrete : TracerConcrete +{ + protected: + explicit Concrete(js::RegExpShared* ptr) : TracerConcrete(ptr) { } + + public: + static void construct(void* storage, js::RegExpShared* ptr) { + new (storage) Concrete(ptr); + } + + CoarseType coarseType() const final { return CoarseType::Other; } + + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +} // namespace ubi +} // namespace JS + #endif /* vm_RegExpObject_h */ diff --git a/js/src/vm/SPSProfiler.cpp b/js/src/vm/SPSProfiler.cpp index 301bf98a3e..2ee9241a1e 100644 --- a/js/src/vm/SPSProfiler.cpp +++ b/js/src/vm/SPSProfiler.cpp @@ -18,6 +18,8 @@ #include "jit/JitFrames.h" #include "vm/StringBuffer.h" +#include "jsgcinlines.h" + using namespace js; using mozilla::DebugOnly; diff --git a/js/src/vm/Shape-inl.h b/js/src/vm/Shape-inl.h index 7b7561220e..34ac3b3d66 100644 --- a/js/src/vm/Shape-inl.h +++ b/js/src/vm/Shape-inl.h @@ -18,6 +18,7 @@ #include "jsatominlines.h" #include "jscntxtinlines.h" +#include "jsgcinlines.h" namespace js { diff --git a/js/src/vm/UbiNode.cpp b/js/src/vm/UbiNode.cpp index 80312bbb7a..ab966cbb4d 100644 --- a/js/src/vm/UbiNode.cpp +++ b/js/src/vm/UbiNode.cpp @@ -311,6 +311,7 @@ template JS::Zone* TracerConcrete::zone() const; template JS::Zone* TracerConcrete::zone() const; template JS::Zone* TracerConcrete::zone() const; template JS::Zone* TracerConcrete::zone() const; +template JS::Zone* TracerConcrete::zone() const; template JS::Zone* TracerConcrete::zone() const; template JS::Zone* TracerConcrete::zone() const; template JS::Zone* TracerConcrete::zone() const; @@ -333,6 +334,7 @@ template UniquePtr TracerConcrete::edges(JSContext* c template UniquePtr TracerConcrete::edges(JSContext* cx, bool wantNames) const; template UniquePtr TracerConcrete::edges(JSContext* cx, bool wantNames) const; template UniquePtr TracerConcrete::edges(JSContext* cx, bool wantNames) const; +template UniquePtr TracerConcrete::edges(JSContext* cx, bool wantNames) const; template UniquePtr TracerConcrete::edges(JSContext* cx, bool wantNames) const; template UniquePtr TracerConcrete::edges(JSContext* cx, bool wantNames) const; template UniquePtr TracerConcrete::edges(JSContext* cx, bool wantNames) const; @@ -397,6 +399,7 @@ const char16_t Concrete::concreteTypeName[] = u"js::Shape"; const char16_t Concrete::concreteTypeName[] = u"js::BaseShape"; const char16_t Concrete::concreteTypeName[] = u"js::ObjectGroup"; const char16_t Concrete::concreteTypeName[] = u"js::Scope"; +const char16_t Concrete::concreteTypeName[] = u"js::RegExpShared"; namespace JS { namespace ubi { diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp index 37750e2a20..fc33f702cc 100644 --- a/js/xpconnect/src/XPCJSContext.cpp +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -1837,6 +1837,14 @@ ReportZoneStats(const JS::ZoneStats& zStats, zStats.scopesMallocHeap, "Arrays of binding names and other binding-related data."); + ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("regexp-shareds/gc-heap"), + zStats.regExpSharedsGCHeap, + "Shared compiled regexp data."); + + ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("regexp-shareds/malloc-heap"), + zStats.regExpSharedsMallocHeap, + "Shared compiled regexp data."); + ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-pool"), zStats.typePool, "Type sets and related data."); @@ -2855,6 +2863,10 @@ JSReporter::CollectReports(WindowPaths* windowPaths, KIND_OTHER, rtStats.zTotals.unusedGCThings.jitcode, "Unused jitcode cells within non-empty arenas."); + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/regexp-shareds"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.regExpShared, + "Unused regexpshared cells within non-empty arenas."); + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/chunk-admin"), KIND_OTHER, rtStats.gcHeapChunkAdmin, "The same as 'explicit/js-non-window/gc-heap/chunk-admin'."); @@ -2906,6 +2918,10 @@ JSReporter::CollectReports(WindowPaths* windowPaths, KIND_OTHER, rtStats.zTotals.jitCodesGCHeap, "Used jitcode cells."); + MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/regexp-shareds"), + KIND_OTHER, rtStats.zTotals.regExpSharedsGCHeap, + "Used regexpshared cells."); + MOZ_ASSERT(gcThingTotal == rtStats.gcHeapGCThings); // Report xpconnect. diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h index 366a1ea3bb..b9fc8e6045 100644 --- a/xpcom/base/CycleCollectedJSContext.h +++ b/xpcom/base/CycleCollectedJSContext.h @@ -543,7 +543,10 @@ void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer); // Returns true if the JS::TraceKind is one the cycle collector cares about. inline bool AddToCCKind(JS::TraceKind aKind) { - return aKind == JS::TraceKind::Object || aKind == JS::TraceKind::Script || aKind == JS::TraceKind::Scope; + return aKind == JS::TraceKind::Object || + aKind == JS::TraceKind::Script || + aKind == JS::TraceKind::Scope || + aKind == JS::TraceKind::RegExpShared; } bool From d163d08cb26bf9d5aaf35f90f501b3fc1f1f5bd8 Mon Sep 17 00:00:00 2001 From: Job Bautista Date: Thu, 26 Jan 2023 15:13:53 +0800 Subject: [PATCH 05/21] Issue #2083 - Part 2: Remove use of RegExpGuard. Based on Mozilla bug 1345177. There were more uses of RegExpGuard in js/src/builtin/RegExp.cpp not found in the original bug's patch. --- js/ipc/WrapperOwner.cpp | 12 ++++--- js/ipc/WrapperOwner.h | 3 +- js/public/Proxy.h | 5 +-- js/src/builtin/RegExp.cpp | 16 ++++----- js/src/jsapi.cpp | 4 +-- js/src/jsfriendapi.h | 4 +-- js/src/jswrapper.h | 8 +++-- js/src/proxy/BaseProxyHandler.cpp | 2 +- js/src/proxy/CrossCompartmentWrapper.cpp | 7 ++-- js/src/proxy/DeadObjectProxy.cpp | 3 +- js/src/proxy/DeadObjectProxy.h | 3 +- js/src/proxy/Proxy.cpp | 4 +-- js/src/proxy/Proxy.h | 3 +- js/src/proxy/ScriptedProxyHandler.cpp | 3 +- js/src/proxy/ScriptedProxyHandler.h | 2 +- js/src/proxy/SecurityWrapper.cpp | 5 +-- js/src/proxy/Wrapper.cpp | 4 +-- js/src/vm/RegExpObject.cpp | 44 +++++++++++++----------- js/src/vm/RegExpObject.h | 25 ++++++++------ js/src/vm/RegExpStatics.cpp | 6 ++-- js/src/vm/StructuredClone.cpp | 2 +- 21 files changed, 92 insertions(+), 73 deletions(-) diff --git a/js/ipc/WrapperOwner.cpp b/js/ipc/WrapperOwner.cpp index f8e4930ddf..92c6e409be 100644 --- a/js/ipc/WrapperOwner.cpp +++ b/js/ipc/WrapperOwner.cpp @@ -128,7 +128,8 @@ class CPOWProxyHandler : public BaseProxyHandler virtual bool isArray(JSContext* cx, HandleObject obj, IsArrayAnswer* answer) const override; virtual const char* className(JSContext* cx, HandleObject proxy) const override; - virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; + virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle shared) const override; virtual void finalize(JSFreeOp* fop, JSObject* proxy) const override; virtual void objectMoved(JSObject* proxy, const JSObject* old) const override; virtual bool isCallable(JSObject* obj) const override; @@ -854,13 +855,14 @@ WrapperOwner::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* is } bool -CPOWProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const +CPOWProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle shared) const { - FORWARD(regexp_toShared, (cx, proxy, g)); + FORWARD(regexp_toShared, (cx, proxy, shared)); } bool -WrapperOwner::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) +WrapperOwner::regexp_toShared(JSContext* cx, HandleObject proxy, MutableHandle shared) { ObjectId objId = idOf(proxy); @@ -880,7 +882,7 @@ WrapperOwner::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) if (!regexp) return false; - return js::RegExpToSharedNonInline(cx, regexp, g); + return js::RegExpToSharedNonInline(cx, regexp, shared); } void diff --git a/js/ipc/WrapperOwner.h b/js/ipc/WrapperOwner.h index 31291b5bfa..aee7111620 100644 --- a/js/ipc/WrapperOwner.h +++ b/js/ipc/WrapperOwner.h @@ -59,7 +59,8 @@ class WrapperOwner : public virtual JavaScriptShared bool getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject proxy, bool* isOrdinary, JS::MutableHandleObject protop); - bool regexp_toShared(JSContext* cx, JS::HandleObject proxy, js::RegExpGuard* g); + bool regexp_toShared(JSContext* cx, JS::HandleObject proxy, + js::MutableHandle shared); nsresult instanceOf(JSObject* obj, const nsID* id, bool* bp); diff --git a/js/public/Proxy.h b/js/public/Proxy.h index 42973bdc37..e493f522c6 100644 --- a/js/public/Proxy.h +++ b/js/public/Proxy.h @@ -31,7 +31,7 @@ using JS::PrivateValue; using JS::PropertyDescriptor; using JS::Value; -using RegExpGuard = JS::Rooted; +class RegExpShared; class JS_FRIEND_API(Wrapper); @@ -328,7 +328,8 @@ class JS_FRIEND_API(BaseProxyHandler) virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const; virtual const char* className(JSContext* cx, HandleObject proxy) const; virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const; - virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const; + virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle shared) const; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const; virtual void trace(JSTracer* trc, JSObject* proxy) const; virtual void finalize(JSFreeOp* fop, JSObject* proxy) const; diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index 9b0e1a7cb6..33c2854dae 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -187,7 +187,7 @@ js::ExecuteRegExpLegacy(JSContext* cx, RegExpStatics* res, Handle HandleLinearString input, size_t* lastIndex, bool test, MutableHandleValue rval) { - RegExpGuard shared(cx); + RootedRegExpShared shared(cx); if (!RegExpObject::getShared(cx, reobj, &shared)) return false; @@ -275,7 +275,7 @@ RegExpInitializeIgnoringLastIndex(JSContext* cx, Handle obj, if (sharedUse == UseRegExpShared) { /* Steps 7-8. */ - RegExpGuard re(cx); + RootedRegExpShared re(cx); if (!cx->compartment()->regExps.get(cx, pattern, flags, &re)) return false; @@ -381,7 +381,7 @@ regexp_compile_impl(JSContext* cx, const CallArgs& args) RegExpFlag flags; { // Step 3b. - RegExpGuard g(cx); + RootedRegExpShared g(cx); if (!RegExpToShared(cx, patternObj, &g)) return false; @@ -476,7 +476,7 @@ js::regexp_construct(JSContext* cx, unsigned argc, Value* vp) RegExpFlag flags; { // Step 4.a. - RegExpGuard g(cx); + RootedRegExpShared g(cx); if (!RegExpToShared(cx, patternObj, &g)) return false; sourceAtom = g->getSource(); @@ -603,7 +603,7 @@ js::regexp_clone(JSContext* cx, unsigned argc, Value* vp) RootedAtom sourceAtom(cx); RegExpFlag flags; { - RegExpGuard g(cx); + RootedRegExpShared g(cx); if (!RegExpToShared(cx, from, &g)) return false; sourceAtom = g->getSource(); @@ -985,7 +985,7 @@ ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string, /* Steps 1-2 performed by the caller. */ Rooted reobj(cx, ®exp->as()); - RegExpGuard re(cx); + RootedRegExpShared re(cx); if (!RegExpObject::getShared(cx, reobj, &re)) return RegExpRunStatus_Error; @@ -1070,7 +1070,7 @@ RegExpMatcherImpl(JSContext* cx, HandleObject regexp, HandleString string, /* Steps 16-25 */ Rooted reobj(cx, ®exp->as()); - RegExpGuard shared(cx); + RootedRegExpShared shared(cx); if (!RegExpObject::getShared(cx, reobj, &shared)) return false; return CreateRegExpMatchResult(cx, *shared, string, matches, rval); @@ -1117,7 +1117,7 @@ js::RegExpMatcherRaw(JSContext* cx, HandleObject regexp, HandleString input, // successful only if the pairs have actually been filled in. if (maybeMatches && maybeMatches->pairsRaw()[0] >= 0) { Rooted reobj(cx, ®exp->as()); - RegExpGuard shared(cx); + RootedRegExpShared shared(cx); if (!RegExpObject::getShared(cx, reobj, &shared)) return false; return CreateRegExpMatchResult(cx, *shared, input, *maybeMatches, output); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 2aec76dbd9..167cd28d60 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -6141,7 +6141,7 @@ JS_GetRegExpFlags(JSContext* cx, HandleObject obj) AssertHeapIsIdle(cx); CHECK_REQUEST(cx); - RegExpGuard shared(cx); + RootedRegExpShared shared(cx); if (!RegExpToShared(cx, obj, &shared)) return false; return shared->getFlags(); @@ -6153,7 +6153,7 @@ JS_GetRegExpSource(JSContext* cx, HandleObject obj) AssertHeapIsIdle(cx); CHECK_REQUEST(cx); - RegExpGuard shared(cx); + RootedRegExpShared shared(cx); if (!RegExpToShared(cx, obj, &shared)) return nullptr; return shared->getSource(); diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index f42a4def5f..34ab6ca15e 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -1114,9 +1114,9 @@ extern JS_FRIEND_API(unsigned) GetEnterCompartmentDepth(JSContext* cx); #endif -using RegExpGuard = JS::Rooted; extern JS_FRIEND_API(bool) -RegExpToSharedNonInline(JSContext* cx, JS::HandleObject regexp, RegExpGuard* shared); +RegExpToSharedNonInline(JSContext* cx, JS::HandleObject regexp, + JS::MutableHandle shared); /* Implemented in jswrapper.cpp. */ typedef enum NukeReferencesToWindow { diff --git a/js/src/jswrapper.h b/js/src/jswrapper.h index 3729ba884d..95f405e126 100644 --- a/js/src/jswrapper.h +++ b/js/src/jswrapper.h @@ -117,7 +117,7 @@ class JS_FRIEND_API(Wrapper) : public BaseProxyHandler virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const override; virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, - RegExpGuard* g) const override; + MutableHandle shared) const override; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override; virtual bool isCallable(JSObject* obj) const override; @@ -211,7 +211,8 @@ class JS_FRIEND_API(CrossCompartmentWrapper) : public Wrapper virtual const char* className(JSContext* cx, HandleObject proxy) const override; virtual JSString* fun_toString(JSContext* cx, HandleObject wrapper, bool isToSource) const override; - virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; + virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle shared) const override; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override; // Allocate CrossCompartmentWrappers in the nursery. @@ -312,7 +313,8 @@ class JS_FRIEND_API(SecurityWrapper) : public Base const CallArgs& args) const override; virtual bool getBuiltinClass(JSContext* cx, HandleObject wrapper, ESClass* cls) const override; virtual bool isArray(JSContext* cx, HandleObject wrapper, JS::IsArrayAnswer* answer) const override; - virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; + virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle shared) const override; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override; /* diff --git a/js/src/proxy/BaseProxyHandler.cpp b/js/src/proxy/BaseProxyHandler.cpp index 6cd526d059..8c873d3c6a 100644 --- a/js/src/proxy/BaseProxyHandler.cpp +++ b/js/src/proxy/BaseProxyHandler.cpp @@ -327,7 +327,7 @@ BaseProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, bool isToSourc bool BaseProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, - RegExpGuard* g) const + MutableHandleRegExpShared shared) const { MOZ_CRASH("This should have been a wrapped regexp"); } diff --git a/js/src/proxy/CrossCompartmentWrapper.cpp b/js/src/proxy/CrossCompartmentWrapper.cpp index bc698c8739..30f5ec14d8 100644 --- a/js/src/proxy/CrossCompartmentWrapper.cpp +++ b/js/src/proxy/CrossCompartmentWrapper.cpp @@ -437,9 +437,10 @@ CrossCompartmentWrapper::fun_toString(JSContext* cx, HandleObject wrapper, bool } bool -CrossCompartmentWrapper::regexp_toShared(JSContext* cx, HandleObject wrapper, RegExpGuard* g) const +CrossCompartmentWrapper::regexp_toShared(JSContext* cx, HandleObject wrapper, + MutableHandleRegExpShared shared) const { - RegExpGuard re(cx); + RootedRegExpShared re(cx); { AutoCompartment call(cx, wrappedObject(wrapper)); if (!Wrapper::regexp_toShared(cx, wrapper, &re)) @@ -447,7 +448,7 @@ CrossCompartmentWrapper::regexp_toShared(JSContext* cx, HandleObject wrapper, Re } // Get an equivalent RegExpShared associated with the current compartment. - return cx->compartment()->regExps.get(cx, re->getSource(), re->getFlags(), g); + return cx->compartment()->regExps.get(cx, re->getSource(), re->getFlags(), shared); } bool diff --git a/js/src/proxy/DeadObjectProxy.cpp b/js/src/proxy/DeadObjectProxy.cpp index 34d9c3b04d..71c9787313 100644 --- a/js/src/proxy/DeadObjectProxy.cpp +++ b/js/src/proxy/DeadObjectProxy.cpp @@ -142,7 +142,8 @@ DeadObjectProxy::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource } bool -DeadObjectProxy::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const +DeadObjectProxy::regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle shared) const { ReportDead(cx); return false; diff --git a/js/src/proxy/DeadObjectProxy.h b/js/src/proxy/DeadObjectProxy.h index bedec9c040..bed1c59a42 100644 --- a/js/src/proxy/DeadObjectProxy.h +++ b/js/src/proxy/DeadObjectProxy.h @@ -49,7 +49,8 @@ class DeadObjectProxy : public BaseProxyHandler virtual const char* className(JSContext* cx, HandleObject proxy) const override; virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const override; - virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; + virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle shared) const override; virtual bool isCallable(JSObject* obj) const override; virtual bool isConstructor(JSObject* obj) const override; diff --git a/js/src/proxy/Proxy.cpp b/js/src/proxy/Proxy.cpp index d873717248..8f6a262106 100644 --- a/js/src/proxy/Proxy.cpp +++ b/js/src/proxy/Proxy.cpp @@ -488,10 +488,10 @@ Proxy::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) } bool -Proxy::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) +Proxy::regexp_toShared(JSContext* cx, HandleObject proxy, MutableHandleRegExpShared shared) { JS_CHECK_RECURSION(cx, return false); - return proxy->as().handler()->regexp_toShared(cx, proxy, g); + return proxy->as().handler()->regexp_toShared(cx, proxy, shared); } bool diff --git a/js/src/proxy/Proxy.h b/js/src/proxy/Proxy.h index 4064b8a7f2..c2ba1afd48 100644 --- a/js/src/proxy/Proxy.h +++ b/js/src/proxy/Proxy.h @@ -59,7 +59,8 @@ class Proxy static bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer); static const char* className(JSContext* cx, HandleObject proxy); static JSString* fun_toString(JSContext* cx, HandleObject proxy, bool isToSource); - static bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g); + static bool regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandle shared); static bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp); static bool getElements(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t end, diff --git a/js/src/proxy/ScriptedProxyHandler.cpp b/js/src/proxy/ScriptedProxyHandler.cpp index 5a219e2e3f..adb98edbdd 100644 --- a/js/src/proxy/ScriptedProxyHandler.cpp +++ b/js/src/proxy/ScriptedProxyHandler.cpp @@ -1265,7 +1265,8 @@ ScriptedProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, bool isToS } bool -ScriptedProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const +ScriptedProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, + MutableHandleRegExpShared shared) const { MOZ_CRASH("Should not end up in ScriptedProxyHandler::regexp_toShared"); return false; diff --git a/js/src/proxy/ScriptedProxyHandler.h b/js/src/proxy/ScriptedProxyHandler.h index e464dc24e8..19120c0f4d 100644 --- a/js/src/proxy/ScriptedProxyHandler.h +++ b/js/src/proxy/ScriptedProxyHandler.h @@ -69,7 +69,7 @@ class ScriptedProxyHandler : public BaseProxyHandler virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const override; virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, - RegExpGuard* g) const override; + MutableHandle shared) const override; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override; diff --git a/js/src/proxy/SecurityWrapper.cpp b/js/src/proxy/SecurityWrapper.cpp index ef894ca772..75fe4dcb11 100644 --- a/js/src/proxy/SecurityWrapper.cpp +++ b/js/src/proxy/SecurityWrapper.cpp @@ -93,9 +93,10 @@ SecurityWrapper::isArray(JSContext* cx, HandleObject obj, JS::IsArrayAnswe template bool -SecurityWrapper::regexp_toShared(JSContext* cx, HandleObject obj, RegExpGuard* g) const +SecurityWrapper::regexp_toShared(JSContext* cx, HandleObject obj, + MutableHandle shared) const { - return Base::regexp_toShared(cx, obj, g); + return Base::regexp_toShared(cx, obj, shared); } template diff --git a/js/src/proxy/Wrapper.cpp b/js/src/proxy/Wrapper.cpp index 314409bba1..5ce1f22c9a 100644 --- a/js/src/proxy/Wrapper.cpp +++ b/js/src/proxy/Wrapper.cpp @@ -267,10 +267,10 @@ Wrapper::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const } bool -Wrapper::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const +Wrapper::regexp_toShared(JSContext* cx, HandleObject proxy, MutableHandleRegExpShared shared) const { RootedObject target(cx, proxy->as().target()); - return RegExpToShared(cx, target, g); + return RegExpToShared(cx, target, shared); } bool diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 4cc2b539aa..cd085efb69 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -122,14 +122,15 @@ VectorMatchPairs::allocOrExpandArray(size_t pairCount) /* RegExpObject */ /* static */ bool -RegExpObject::getShared(JSContext* cx, Handle regexp, RegExpGuard* g) +RegExpObject::getShared(JSContext* cx, Handle regexp, + MutableHandleRegExpShared shared) { if (regexp->hasShared()) { - g->set(regexp->sharedRef()); + shared.set(regexp->sharedRef()); return true; } - return createShared(cx, regexp, g); + return createShared(cx, regexp, shared); } /* static */ bool @@ -278,13 +279,14 @@ RegExpObject::create(ExclusiveContext* cx, HandleAtom source, RegExpFlag flags, } /* static */ bool -RegExpObject::createShared(JSContext* cx, Handle regexp, RegExpGuard* g) +RegExpObject::createShared(JSContext* cx, Handle regexp, + MutableHandleRegExpShared shared) { MOZ_ASSERT(!regexp->hasShared()); - if (!cx->compartment()->regExps.get(cx, regexp->getSource(), regexp->getFlags(), g)) + if (!cx->compartment()->regExps.get(cx, regexp->getSource(), regexp->getFlags(), shared)) return false; - regexp->setShared(**g); + regexp->setShared(*shared); return true; } @@ -891,11 +893,11 @@ RegExpShared::dumpBytecode(JSContext* cx, bool match_only, HandleLinearString in RegExpObject::dumpBytecode(JSContext* cx, Handle regexp, bool match_only, HandleLinearString input) { - RegExpGuard g(cx); - if (!getShared(cx, regexp, &g)) + RootedRegExpShared shared(cx); + if (!getShared(cx, regexp, &shared)) return false; - return g->dumpBytecode(cx, match_only, input); + return shared->dumpBytecode(cx, match_only, input); } #endif @@ -1351,11 +1353,12 @@ RegExpCompartment::sweep(JSRuntime* rt) } bool -RegExpCompartment::get(JSContext* cx, JSAtom* source, RegExpFlag flags, RegExpGuard* g) +RegExpCompartment::get(JSContext* cx, JSAtom* source, RegExpFlag flags, + MutableHandleRegExpShared result) { DependentAddPtr p(cx, set_.get(), Key(source, flags)); if (p) { - g->set(*p); + result.set(*p); return true; } @@ -1370,18 +1373,19 @@ RegExpCompartment::get(JSContext* cx, JSAtom* source, RegExpFlag flags, RegExpGu return false; } - g->set(shared); + result.set(shared); return true; } bool -RegExpCompartment::get(JSContext* cx, HandleAtom atom, JSString* opt, RegExpGuard* g) +RegExpCompartment::get(JSContext* cx, HandleAtom atom, JSString* opt, + MutableHandleRegExpShared shared) { RegExpFlag flags = RegExpFlag(0); if (opt && !ParseRegExpFlags(cx, opt, &flags)) return false; - return get(cx, atom, flags, g); + return get(cx, atom, flags, shared); } size_t @@ -1410,12 +1414,12 @@ js::CloneRegExpObject(JSContext* cx, JSObject* obj_) Rooted source(cx, regex->getSource()); - RegExpGuard g(cx); - if (!RegExpObject::getShared(cx, regex, &g)) + RootedRegExpShared shared(cx); + if (!RegExpObject::getShared(cx, regex, &shared)) return nullptr; - clone->initAndZeroLastIndex(source, g->getFlags(), cx); - clone->setShared(*g); + clone->initAndZeroLastIndex(source, shared->getFlags(), cx); + clone->setShared(*shared); return clone; } @@ -1554,9 +1558,9 @@ js::CloneScriptRegExpObject(JSContext* cx, RegExpObject& reobj) } JS_FRIEND_API(bool) -js::RegExpToSharedNonInline(JSContext* cx, HandleObject obj, js::RegExpGuard* g) +js::RegExpToSharedNonInline(JSContext* cx, HandleObject obj, MutableHandleRegExpShared shared) { - return RegExpToShared(cx, obj, g); + return RegExpToShared(cx, obj, shared); } JS::ubi::Node::Size diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 16ba7f41fd..41f5e623ae 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -32,10 +32,8 @@ * * To save memory, a RegExpShared is not created for a RegExpObject until it is * needed for execution. When a RegExpShared needs to be created, it is looked - * up in a per-compartment table to allow reuse between objects. Lastly, on - * GC, every RegExpShared (that is not active on the callstack) is discarded. - * Because of the last point, any code using a RegExpShared (viz., by executing - * a regexp) must indicate the RegExpShared is active via RegExpGuard. + * up in a per-compartment table to allow reuse between objects. Lastly, on GC, + * every RegExpShared that is not in active use is discarded. */ namespace js { @@ -251,6 +249,10 @@ class RegExpShared : public gc::TenuredCell #endif }; +using RootedRegExpShared = JS::Rooted; +using HandleRegExpShared = JS::Handle; +using MutableHandleRegExpShared = JS::MutableHandle; + class RegExpCompartment { struct Key { @@ -321,10 +323,11 @@ class RegExpCompartment bool empty() { return set_.empty(); } - bool get(JSContext* cx, JSAtom* source, RegExpFlag flags, RegExpGuard* g); + bool get(JSContext* cx, JSAtom* source, RegExpFlag flags, MutableHandleRegExpShared shared); /* Like 'get', but compile 'maybeOpt' (if non-null). */ - bool get(JSContext* cx, HandleAtom source, JSString* maybeOpt, RegExpGuard* g); + bool get(JSContext* cx, HandleAtom source, JSString* maybeOpt, + MutableHandleRegExpShared shared); /* Get or create template object used to base the result of .exec() on. */ ArrayObject* getOrCreateMatchResultTemplateObject(JSContext* cx) { @@ -447,7 +450,7 @@ class RegExpObject : public NativeObject static bool isOriginalFlagGetter(JSNative native, RegExpFlag* mask); static MOZ_MUST_USE bool getShared(JSContext* cx, Handle regexp, - RegExpGuard* g); + MutableHandleRegExpShared shared); bool hasShared() { return !!sharedRef(); @@ -479,7 +482,7 @@ class RegExpObject : public NativeObject * Side effect: sets the private field. */ static MOZ_MUST_USE bool createShared(JSContext* cx, Handle regexp, - RegExpGuard* g); + MutableHandleRegExpShared shared); ReadBarriered& sharedRef() { auto& ref = NativeObject::privateRef(PRIVATE_SLOT); @@ -501,12 +504,12 @@ ParseRegExpFlags(JSContext* cx, JSString* flagStr, RegExpFlag* flagsOut); /* Assuming GetBuiltinClass(obj) is ESClass::RegExp, return a RegExpShared for obj. */ inline bool -RegExpToShared(JSContext* cx, HandleObject obj, RegExpGuard* g) +RegExpToShared(JSContext* cx, HandleObject obj, MutableHandleRegExpShared shared) { if (obj->is()) - return RegExpObject::getShared(cx, obj.as(), g); + return RegExpObject::getShared(cx, obj.as(), shared); - return Proxy::regexp_toShared(cx, obj, g); + return Proxy::regexp_toShared(cx, obj, shared); } template diff --git a/js/src/vm/RegExpStatics.cpp b/js/src/vm/RegExpStatics.cpp index 6445bdd440..27bc988db6 100644 --- a/js/src/vm/RegExpStatics.cpp +++ b/js/src/vm/RegExpStatics.cpp @@ -80,8 +80,8 @@ RegExpStatics::executeLazy(JSContext* cx) MOZ_ASSERT(lazyIndex != size_t(-1)); /* Retrieve or create the RegExpShared in this compartment. */ - RegExpGuard g(cx); - if (!cx->compartment()->regExps.get(cx, lazySource, lazyFlags, &g)) + RootedRegExpShared shared(cx); + if (!cx->compartment()->regExps.get(cx, lazySource, lazyFlags, &shared)) return false; /* @@ -91,7 +91,7 @@ RegExpStatics::executeLazy(JSContext* cx) /* Execute the full regular expression. */ RootedLinearString input(cx, matchesInput); - RegExpRunStatus status = g->execute(cx, input, lazyIndex, &this->matches, nullptr); + RegExpRunStatus status = shared->execute(cx, input, lazyIndex, &this->matches, nullptr); if (status == RegExpRunStatus_Error) return false; diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index 9cd4f1e072..26e57976fc 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -1402,7 +1402,7 @@ JSStructuredCloneWriter::startWrite(HandleValue v) return false; if (cls == ESClass::RegExp) { - RegExpGuard re(context()); + RootedRegExpShared re(context()); if (!RegExpToShared(context(), obj, &re)) return false; return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags()) && From 2ac60a27c78206258999680ed23ae9b6619afacd Mon Sep 17 00:00:00 2001 From: Job Bautista Date: Thu, 26 Jan 2023 15:20:34 +0800 Subject: [PATCH 06/21] Issue #2083 - Part 3: Fix RegExpShared rooting hazards now it's a GC thing. Based on Mozilla bug 1345177. Changes from the original bug's patch: - The original patch didn't have a dotAll for a call to irregexp::ParsePattern, so let's make our dotAll a member of the MutableHandleRegExpShared re. - Make RegExpShared::initializeNamedCaptures, introduced in Issue #1285, static. This resolves a build bustage where static RegExpShared::compile was trying to use a member function. --- js/src/builtin/RegExp.cpp | 13 +-- js/src/irregexp/RegExpEngine.cpp | 2 +- js/src/irregexp/RegExpEngine.h | 2 +- js/src/proxy/CrossCompartmentWrapper.cpp | 3 +- js/src/vm/RegExpObject.cpp | 103 ++++++++++++----------- js/src/vm/RegExpObject.h | 35 ++++---- js/src/vm/RegExpStatics.cpp | 6 +- 7 files changed, 90 insertions(+), 74 deletions(-) diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index 33c2854dae..46a2862909 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -164,10 +164,11 @@ CreateRegExpSearchResult(JSContext* cx, const MatchPairs& matches) * steps 3, 9-14, except 12.a.i, 12.c.i.1. */ static RegExpRunStatus -ExecuteRegExpImpl(JSContext* cx, RegExpStatics* res, RegExpShared& re, HandleLinearString input, - size_t searchIndex, MatchPairs* matches, size_t* endIndex) +ExecuteRegExpImpl(JSContext* cx, RegExpStatics* res, MutableHandleRegExpShared re, + HandleLinearString input, size_t searchIndex, MatchPairs* matches, + size_t* endIndex) { - RegExpRunStatus status = re.execute(cx, input, searchIndex, matches, endIndex); + RegExpRunStatus status = RegExpShared::execute(cx, re, input, searchIndex, matches, endIndex); /* Out of spec: Update RegExpStatics. */ if (status == RegExpRunStatus_Success && res) { @@ -175,7 +176,7 @@ ExecuteRegExpImpl(JSContext* cx, RegExpStatics* res, RegExpShared& re, HandleLin if (!res->updateFromMatchPairs(cx, input, *matches)) return RegExpRunStatus_Error; } else { - res->updateLazily(cx, input, &re, searchIndex); + res->updateLazily(cx, input, re, searchIndex); } } return status; @@ -193,7 +194,7 @@ js::ExecuteRegExpLegacy(JSContext* cx, RegExpStatics* res, Handle ScopedMatchPairs matches(&cx->tempLifoAlloc()); - RegExpRunStatus status = ExecuteRegExpImpl(cx, res, *shared, input, *lastIndex, + RegExpRunStatus status = ExecuteRegExpImpl(cx, res, &shared, input, *lastIndex, &matches, nullptr); if (status == RegExpRunStatus_Error) return false; @@ -1036,7 +1037,7 @@ ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string, } /* Steps 3, 11-14, except 12.a.i, 12.c.i.1. */ - RegExpRunStatus status = ExecuteRegExpImpl(cx, res, *re, input, lastIndex, matches, endIndex); + RegExpRunStatus status = ExecuteRegExpImpl(cx, res, &re, input, lastIndex, matches, endIndex); if (status == RegExpRunStatus_Error) return RegExpRunStatus_Error; diff --git a/js/src/irregexp/RegExpEngine.cpp b/js/src/irregexp/RegExpEngine.cpp index f3db7c1847..3ac57eccc9 100644 --- a/js/src/irregexp/RegExpEngine.cpp +++ b/js/src/irregexp/RegExpEngine.cpp @@ -1259,7 +1259,7 @@ IsNativeRegExpEnabled(JSContext* cx) } RegExpCode -irregexp::CompilePattern(JSContext* cx, RegExpShared* shared, RegExpCompileData* data, +irregexp::CompilePattern(JSContext* cx, HandleRegExpShared shared, RegExpCompileData* data, HandleLinearString sample, bool is_global, bool ignore_case, bool is_ascii, bool match_only, bool force_bytecode, bool sticky, bool unicode) diff --git a/js/src/irregexp/RegExpEngine.h b/js/src/irregexp/RegExpEngine.h index 22e9d944a4..6b3f6afc2a 100644 --- a/js/src/irregexp/RegExpEngine.h +++ b/js/src/irregexp/RegExpEngine.h @@ -103,7 +103,7 @@ struct RegExpCode }; RegExpCode -CompilePattern(JSContext* cx, RegExpShared* shared, RegExpCompileData* data, +CompilePattern(JSContext* cx, HandleRegExpShared shared, RegExpCompileData* data, HandleLinearString sample, bool is_global, bool ignore_case, bool is_ascii, bool match_only, bool force_bytecode, bool sticky, bool unicode); diff --git a/js/src/proxy/CrossCompartmentWrapper.cpp b/js/src/proxy/CrossCompartmentWrapper.cpp index 30f5ec14d8..c9932a0bb8 100644 --- a/js/src/proxy/CrossCompartmentWrapper.cpp +++ b/js/src/proxy/CrossCompartmentWrapper.cpp @@ -448,7 +448,8 @@ CrossCompartmentWrapper::regexp_toShared(JSContext* cx, HandleObject wrapper, } // Get an equivalent RegExpShared associated with the current compartment. - return cx->compartment()->regExps.get(cx, re->getSource(), re->getFlags(), shared); + RootedAtom source(cx, re->getSource()); + return cx->compartment()->regExps.get(cx, source, re->getFlags(), shared); } bool diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index cd085efb69..556b2e4413 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -283,7 +283,8 @@ RegExpObject::createShared(JSContext* cx, Handle regexp, MutableHandleRegExpShared shared) { MOZ_ASSERT(!regexp->hasShared()); - if (!cx->compartment()->regExps.get(cx, regexp->getSource(), regexp->getFlags(), shared)) + RootedAtom source(cx, regexp->getSource()); + if (!cx->compartment()->regExps.get(cx, source, regexp->getFlags(), shared)) return false; regexp->setShared(*shared); @@ -507,14 +508,15 @@ RegExpObject::toString(JSContext* cx) const } #ifdef DEBUG -bool -RegExpShared::dumpBytecode(JSContext* cx, bool match_only, HandleLinearString input) +/* static */ bool +RegExpShared::dumpBytecode(JSContext* cx, MutableHandleRegExpShared re, bool match_only, + HandleLinearString input) { CompilationMode mode = match_only ? MatchOnly : Normal; - if (!compileIfNecessary(cx, input, mode, ForceByteCode)) + if (!RegExpShared::compileIfNecessary(cx, re, input, mode, ForceByteCode)) return false; - const uint8_t* byteCode = compilation(mode, input->hasLatin1Chars()).byteCode; + const uint8_t* byteCode = re->compilation(mode, input->hasLatin1Chars()).byteCode; const uint8_t* pc = byteCode; auto Load32Aligned = [](const uint8_t* pc) -> int32_t { @@ -897,7 +899,7 @@ RegExpObject::dumpBytecode(JSContext* cx, Handle regexp, if (!getShared(cx, regexp, &shared)) return false; - return shared->dumpBytecode(cx, match_only, input); + return RegExpShared::dumpBytecode(cx, &shared, match_only, input); } #endif @@ -976,21 +978,23 @@ RegExpShared::discardJitCode() comp.jitCode = nullptr; } -bool -RegExpShared::compile(JSContext* cx, HandleLinearString input, +/* static */ bool +RegExpShared::compile(JSContext* cx, MutableHandleRegExpShared re, HandleLinearString input, CompilationMode mode, ForceByteCodeEnum force) { TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); AutoTraceLog logCompile(logger, TraceLogger_IrregexpCompile); - RootedAtom pattern(cx, source); - return compile(cx, pattern, input, mode, force); + RootedAtom pattern(cx, re->source); + return compile(cx, re, pattern, input, mode, force); } -bool -RegExpShared::initializeNamedCaptures(JSContext* cx, irregexp::CharacterVectorVector* names, irregexp::IntegerVector* indices) +/* static */ bool +RegExpShared::initializeNamedCaptures(JSContext* cx, HandleRegExpShared re, + irregexp::CharacterVectorVector* names, + irregexp::IntegerVector* indices) { - MOZ_ASSERT(!groupsTemplate_); + MOZ_ASSERT(!re->groupsTemplate_); MOZ_ASSERT(names); MOZ_ASSERT(indices); MOZ_ASSERT(names->length() == indices->length()); @@ -1032,17 +1036,17 @@ RegExpShared::initializeNamedCaptures(JSContext* cx, irregexp::CharacterVectorVe AddTypePropertyId(cx, templateObject, id, TypeSet::Int32Type()); } - groupsTemplate_ = templateObject; - numNamedCaptures_ = numNamedCaptures; + re->groupsTemplate_ = templateObject; + re->numNamedCaptures_ = numNamedCaptures; return true; } -bool -RegExpShared::compile(JSContext* cx, HandleAtom pattern, HandleLinearString input, - CompilationMode mode, ForceByteCodeEnum force) +/* static */ bool +RegExpShared::compile(JSContext* cx, MutableHandleRegExpShared re, HandleAtom pattern, + HandleLinearString input, CompilationMode mode, ForceByteCodeEnum force) { - if (!ignoreCase() && !StringHasRegExpMetaChars(pattern)) - canStringMatch = true; + if (!re->ignoreCase() && !StringHasRegExpMetaChars(pattern)) + re->canStringMatch = true; CompileOptions options(cx); TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr); @@ -1052,34 +1056,36 @@ RegExpShared::compile(JSContext* cx, HandleAtom pattern, HandleLinearString inpu /* Parse the pattern. */ irregexp::RegExpCompileData data; if (!irregexp::ParsePattern(dummyTokenStream, cx->tempLifoAlloc(), pattern, - multiline(), mode == MatchOnly, unicode(), ignoreCase(), - global(), sticky(), dotAll(), &data)) + re->multiline(), mode == MatchOnly, re->unicode(), + re->ignoreCase(), re->global(), re->sticky(), + re->dotAll(), &data)) { return false; } - this->parenCount = data.capture_count; + re->parenCount = data.capture_count; if (data.capture_name_list) { // convert LifoAlloc'd named capture info to NativeObject - if (!initializeNamedCaptures(cx, data.capture_name_list, data.capture_index_list)) { + if (!initializeNamedCaptures(cx, re, data.capture_name_list, data.capture_index_list)) { return false; } } - irregexp::RegExpCode code = irregexp::CompilePattern(cx, this, &data, input, + irregexp::RegExpCode code = irregexp::CompilePattern(cx, re, &data, input, false /* global() */, - ignoreCase(), + re->ignoreCase(), input->hasLatin1Chars(), mode == MatchOnly, force == ForceByteCode, - sticky(), unicode()); + re->sticky(), + re->unicode()); if (code.empty()) return false; MOZ_ASSERT(!code.jitCode || !code.byteCode); MOZ_ASSERT_IF(force == ForceByteCode, code.byteCode); - RegExpCompilation& compilation = this->compilation(mode, input->hasLatin1Chars()); + RegExpCompilation& compilation = re->compilation(mode, input->hasLatin1Chars()); if (code.jitCode) compilation.jitCode = code.jitCode; else if (code.byteCode) @@ -1088,18 +1094,19 @@ RegExpShared::compile(JSContext* cx, HandleAtom pattern, HandleLinearString inpu return true; } -bool -RegExpShared::compileIfNecessary(JSContext* cx, HandleLinearString input, - CompilationMode mode, ForceByteCodeEnum force) +/* static */ bool +RegExpShared::compileIfNecessary(JSContext* cx, MutableHandleRegExpShared re, + HandleLinearString input, CompilationMode mode, + ForceByteCodeEnum force) { - if (isCompiled(mode, input->hasLatin1Chars(), force)) + if (re->isCompiled(mode, input->hasLatin1Chars(), force)) return true; - return compile(cx, input, mode, force); + return compile(cx, re, input, mode, force); } -RegExpRunStatus -RegExpShared::execute(JSContext* cx, HandleLinearString input, size_t start, - MatchPairs* matches, size_t* endIndex) +/* static */ RegExpRunStatus +RegExpShared::execute(JSContext* cx, MutableHandleRegExpShared re, HandleLinearString input, + size_t start, MatchPairs* matches, size_t* endIndex) { MOZ_ASSERT_IF(matches, !endIndex); MOZ_ASSERT_IF(!matches, endIndex); @@ -1108,14 +1115,14 @@ RegExpShared::execute(JSContext* cx, HandleLinearString input, size_t start, CompilationMode mode = matches ? Normal : MatchOnly; /* Compile the code at point-of-use. */ - if (!compileIfNecessary(cx, input, mode, DontForceByteCode)) + if (!compileIfNecessary(cx, re, input, mode, DontForceByteCode)) return RegExpRunStatus_Error; /* * Ensure sufficient memory for output vector. * No need to initialize it. The RegExp engine fills them in on a match. */ - if (matches && !matches->allocOrExpandArray(pairCount())) { + if (matches && !matches->allocOrExpandArray(re->pairCount())) { ReportOutOfMemory(cx); return RegExpRunStatus_Error; } @@ -1125,14 +1132,14 @@ RegExpShared::execute(JSContext* cx, HandleLinearString input, size_t start, // Reset the Irregexp backtrack stack if it grows during execution. irregexp::RegExpStackScope stackScope(cx->runtime()); - if (canStringMatch) { - MOZ_ASSERT(pairCount() == 1); - size_t sourceLength = source->length(); - if (sticky()) { + if (re->canStringMatch) { + MOZ_ASSERT(re->pairCount() == 1); + size_t sourceLength = re->source->length(); + if (re->sticky()) { // First part checks size_t overflow. if (sourceLength + start < sourceLength || sourceLength + start > length) return RegExpRunStatus_Success_NotFound; - if (!HasSubstringAt(input, source, start)) + if (!HasSubstringAt(input, re->source, start)) return RegExpRunStatus_Success_NotFound; if (matches) { @@ -1146,7 +1153,7 @@ RegExpShared::execute(JSContext* cx, HandleLinearString input, size_t start, return RegExpRunStatus_Success; } - int res = StringFindPattern(input, source, start); + int res = StringFindPattern(input, re->source, start); if (res == -1) return RegExpRunStatus_Success_NotFound; @@ -1162,7 +1169,7 @@ RegExpShared::execute(JSContext* cx, HandleLinearString input, size_t start, } do { - jit::JitCode* code = compilation(mode, input->hasLatin1Chars()).jitCode; + jit::JitCode* code = re->compilation(mode, input->hasLatin1Chars()).jitCode; if (!code) break; @@ -1201,10 +1208,10 @@ RegExpShared::execute(JSContext* cx, HandleLinearString input, size_t start, } while (false); // Compile bytecode for the RegExp if necessary. - if (!compileIfNecessary(cx, input, mode, ForceByteCode)) + if (!compileIfNecessary(cx, re, input, mode, ForceByteCode)) return RegExpRunStatus_Error; - uint8_t* byteCode = compilation(mode, input->hasLatin1Chars()).byteCode; + uint8_t* byteCode = re->compilation(mode, input->hasLatin1Chars()).byteCode; AutoTraceLog logInterpreter(logger, TraceLogger_IrregexpExecute); AutoStableStringChars inputChars(cx); @@ -1353,7 +1360,7 @@ RegExpCompartment::sweep(JSRuntime* rt) } bool -RegExpCompartment::get(JSContext* cx, JSAtom* source, RegExpFlag flags, +RegExpCompartment::get(JSContext* cx, HandleAtom source, RegExpFlag flags, MutableHandleRegExpShared result) { DependentAddPtr p(cx, set_.get(), Key(source, flags)); diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 41f5e623ae..89942c6fd5 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -42,6 +42,10 @@ class MatchPairs; class RegExpShared; class RegExpStatics; +using RootedRegExpShared = JS::Rooted; +using HandleRegExpShared = JS::Handle; +using MutableHandleRegExpShared = JS::MutableHandle; + namespace frontend { class TokenStream; } enum RegExpFlag : uint8_t @@ -150,13 +154,14 @@ class RegExpShared : public gc::TenuredCell /* Internal functions. */ RegExpShared(JSAtom* source, RegExpFlag flags); - bool compile(JSContext* cx, HandleLinearString input, - CompilationMode mode, ForceByteCodeEnum force); - bool compile(JSContext* cx, HandleAtom pattern, HandleLinearString input, - CompilationMode mode, ForceByteCodeEnum force); + static bool compile(JSContext* cx, MutableHandleRegExpShared res, HandleLinearString input, + CompilationMode mode, ForceByteCodeEnum force); + static bool compile(JSContext* cx, MutableHandleRegExpShared res, HandleAtom pattern, + HandleLinearString input, CompilationMode mode, ForceByteCodeEnum force); - bool compileIfNecessary(JSContext* cx, HandleLinearString input, - CompilationMode mode, ForceByteCodeEnum force); + static bool compileIfNecessary(JSContext* cx, MutableHandleRegExpShared res, + HandleLinearString input, CompilationMode mode, + ForceByteCodeEnum force); const RegExpCompilation& compilation(CompilationMode mode, bool latin1) const { return compilationArray[CompilationIndex(mode, latin1)]; @@ -171,8 +176,9 @@ class RegExpShared : public gc::TenuredCell // Execute this RegExp on input starting from searchIndex, filling in // matches if specified and otherwise only determining if there is a match. - RegExpRunStatus execute(JSContext* cx, HandleLinearString input, size_t searchIndex, - MatchPairs* matches, size_t* endIndex); + static RegExpRunStatus execute(JSContext* cx, MutableHandleRegExpShared res, + HandleLinearString input, size_t searchIndex, + MatchPairs* matches, size_t* endIndex); // Register a table with this RegExpShared, and take ownership. bool addTable(uint8_t* table) { @@ -190,7 +196,9 @@ class RegExpShared : public gc::TenuredCell size_t pairCount() const { return getParenCount() + 1; } // not public due to circular inclusion problems - bool initializeNamedCaptures(JSContext* cx, irregexp::CharacterVectorVector* names, irregexp::IntegerVector* indices); + static bool initializeNamedCaptures(JSContext* cx, HandleRegExpShared re, + irregexp::CharacterVectorVector* names, + irregexp::IntegerVector* indices); PlainObject* getGroupsTemplate() { return groupsTemplate_; } uint32_t numNamedCaptures() const { return numNamedCaptures_; } @@ -245,14 +253,11 @@ class RegExpShared : public gc::TenuredCell size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); #ifdef DEBUG - bool dumpBytecode(JSContext* cx, bool match_only, HandleLinearString input); + static bool dumpBytecode(JSContext* cx, MutableHandleRegExpShared res, bool match_only, + HandleLinearString input); #endif }; -using RootedRegExpShared = JS::Rooted; -using HandleRegExpShared = JS::Handle; -using MutableHandleRegExpShared = JS::MutableHandle; - class RegExpCompartment { struct Key { @@ -323,7 +328,7 @@ class RegExpCompartment bool empty() { return set_.empty(); } - bool get(JSContext* cx, JSAtom* source, RegExpFlag flags, MutableHandleRegExpShared shared); + bool get(JSContext* cx, HandleAtom source, RegExpFlag flags, MutableHandleRegExpShared shared); /* Like 'get', but compile 'maybeOpt' (if non-null). */ bool get(JSContext* cx, HandleAtom source, JSString* maybeOpt, diff --git a/js/src/vm/RegExpStatics.cpp b/js/src/vm/RegExpStatics.cpp index 27bc988db6..7073e084fd 100644 --- a/js/src/vm/RegExpStatics.cpp +++ b/js/src/vm/RegExpStatics.cpp @@ -81,7 +81,8 @@ RegExpStatics::executeLazy(JSContext* cx) /* Retrieve or create the RegExpShared in this compartment. */ RootedRegExpShared shared(cx); - if (!cx->compartment()->regExps.get(cx, lazySource, lazyFlags, &shared)) + RootedAtom source(cx, lazySource); + if (!cx->compartment()->regExps.get(cx, source, lazyFlags, &shared)) return false; /* @@ -91,7 +92,8 @@ RegExpStatics::executeLazy(JSContext* cx) /* Execute the full regular expression. */ RootedLinearString input(cx, matchesInput); - RegExpRunStatus status = shared->execute(cx, input, lazyIndex, &this->matches, nullptr); + RegExpRunStatus status = RegExpShared::execute(cx, &shared, input, lazyIndex, &this->matches, + nullptr); if (status == RegExpRunStatus_Error) return false; From 9824659d3c6b1c4fdc2616f789f0696d1cbe2ef8 Mon Sep 17 00:00:00 2001 From: Job Bautista Date: Wed, 25 Jan 2023 19:39:25 +0800 Subject: [PATCH 07/21] Issue #2083 - Part 4: Give RegExpShared a finalizer. Based on Mozilla bug 1345177. --- js/src/gc/Heap.h | 3 --- js/src/vm/RegExpObject.cpp | 14 ++++++++------ js/src/vm/RegExpObject.h | 6 ++++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index 14604d3bbe..9f030135e7 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -340,9 +340,6 @@ class TenuredCell : public Cell static MOZ_ALWAYS_INLINE void writeBarrierPost(void* cellp, TenuredCell* prior, TenuredCell* next); - // Default implementation for kinds that don't require finalization. - void finalize(FreeOp* fop) {} - // Default implementation for kinds that don't require fixup. void fixupAfterMovingGC() {} diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 556b2e4413..a5bf4fe8b3 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -952,12 +952,6 @@ RegExpShared::RegExpShared(JSAtom* source, RegExpFlag flags) numNamedCaptures_(0), groupsTemplate_(nullptr) {} -RegExpShared::~RegExpShared() -{ - for (size_t i = 0; i < tables.length(); i++) - js_delete(tables[i]); -} - void RegExpShared::traceChildren(JSTracer* trc) { @@ -978,6 +972,14 @@ RegExpShared::discardJitCode() comp.jitCode = nullptr; } +void +RegExpShared::finalize(FreeOp* fop) +{ + for (size_t i = 0; i < tables.length(); i++) + js_free(tables[i]); + tables.~JitCodeTables(); +} + /* static */ bool RegExpShared::compile(JSContext* cx, MutableHandleRegExpShared re, HandleLinearString input, CompilationMode mode, ForceByteCodeEnum force) diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 89942c6fd5..289c4f0dc9 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -149,7 +149,8 @@ class RegExpShared : public gc::TenuredCell } // Tables referenced by JIT code. - Vector tables; + using JitCodeTables = Vector; + JitCodeTables tables; /* Internal functions. */ RegExpShared(JSAtom* source, RegExpFlag flags); @@ -172,7 +173,7 @@ class RegExpShared : public gc::TenuredCell } public: - ~RegExpShared(); + ~RegExpShared() = delete; // Execute this RegExp on input starting from searchIndex, filling in // matches if specified and otherwise only determining if there is a match. @@ -222,6 +223,7 @@ class RegExpShared : public gc::TenuredCell void traceChildren(JSTracer* trc); void discardJitCode(); + void finalize(FreeOp* fop); static size_t offsetOfSource() { return offsetof(RegExpShared, source); From b7e4530861fcc971aee9d867b3b376e31263486c Mon Sep 17 00:00:00 2001 From: Job Bautista Date: Wed, 25 Jan 2023 19:41:09 +0800 Subject: [PATCH 08/21] Issue #2083 - Part 5: Fix memory leak in RegExpObject. Based on Mozilla bug 1345177. --- js/src/vm/RegExpObject.cpp | 2 ++ js/src/vm/RegExpObject.h | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index a5bf4fe8b3..61baadef81 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -975,6 +975,8 @@ RegExpShared::discardJitCode() void RegExpShared::finalize(FreeOp* fop) { + for (auto& comp : compilationArray) + js_free(comp.byteCode); for (size_t i = 0; i < tables.length(); i++) js_free(tables[i]); tables.~JitCodeTables(); diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 289c4f0dc9..0056c6aee9 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -121,7 +121,6 @@ class RegExpShared : public gc::TenuredCell uint8_t* byteCode; RegExpCompilation() : byteCode(nullptr) {} - ~RegExpCompilation() { js_free(byteCode); } bool compiled(ForceByteCodeEnum force = DontForceByteCode) const { return byteCode || (force == DontForceByteCode && jitCode); From 233802c9abf6023d97355b7ab1a0681df50ef952 Mon Sep 17 00:00:00 2001 From: Job Bautista Date: Thu, 26 Jan 2023 15:26:01 +0800 Subject: [PATCH 09/21] Revert "Issue #1382 - RegExpShared::groupsTemplate_ cannot be a GCPtr since RegExpShared is managed by C++" This workaround has been obsoleted by the work on Issue #2083. This reverts commit bbd743608685ec77ebebe480761118db8a988253. --- js/src/vm/RegExpObject.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 0056c6aee9..28adbb36fc 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -134,8 +134,8 @@ class RegExpShared : public gc::TenuredCell bool canStringMatch; size_t parenCount; - uint32_t numNamedCaptures_; - HeapPtr groupsTemplate_; + uint32_t numNamedCaptures_; + GCPtr groupsTemplate_; RegExpCompilation compilationArray[4]; From ed8af362be0097d2adc7928482104f96f11d575b Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 22 Jan 2023 00:09:10 +0100 Subject: [PATCH 10/21] Issue #2089 - Shrink Promise instances from 8 to 4 slots by moving debug information to an external object Based-on: m-c 1342050 --- js/src/builtin/Promise.cpp | 204 +++++++++++++++++++++++++++++++------ js/src/builtin/Promise.h | 18 +--- 2 files changed, 180 insertions(+), 42 deletions(-) diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index d1a92c6246..34c721f722 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -154,6 +154,172 @@ NewPromiseAllDataHolder(JSContext* cx, HandleObject resultPromise, HandleValue v return dataHolder; } +namespace { +// Generator used by PromiseObject::getID. +mozilla::Atomic gIDGenerator(0); +} // namespace + +static MOZ_ALWAYS_INLINE bool +ShouldCaptureDebugInfo(JSContext* cx) +{ + return cx->options().asyncStack() || cx->compartment()->isDebuggee(); +} + +class PromiseDebugInfo : public NativeObject +{ + private: + enum Slots { + Slot_AllocationSite, + Slot_ResolutionSite, + Slot_AllocationTime, + Slot_ResolutionTime, + Slot_Id, + SlotCount + }; + + public: + static const Class class_; + static PromiseDebugInfo* create(JSContext* cx, Handle promise) { + Rooted debugInfo(cx, NewObjectWithClassProto(cx)); + if (!debugInfo) + return nullptr; + + RootedObject stack(cx); + if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) + return nullptr; + debugInfo->setFixedSlot(Slot_AllocationSite, ObjectOrNullValue(stack)); + debugInfo->setFixedSlot(Slot_ResolutionSite, NullValue()); + debugInfo->setFixedSlot(Slot_AllocationTime, DoubleValue(MillisecondsSinceStartup())); + debugInfo->setFixedSlot(Slot_ResolutionTime, NumberValue(0)); + promise->setFixedSlot(PromiseSlot_DebugInfo, ObjectValue(*debugInfo)); + + return debugInfo; + } + + static PromiseDebugInfo* FromPromise(PromiseObject* promise) { + Value val = promise->getFixedSlot(PromiseSlot_DebugInfo); + if (val.isObject()) + return &val.toObject().as(); + return nullptr; + } + + /** + * Returns the given PromiseObject's process-unique ID. + * The ID is lazily assigned when first queried, and then either stored + * in the DebugInfo slot if no debug info was recorded for this Promise, + * or in the Id slot of the DebugInfo object. + */ + static uint64_t id(PromiseObject* promise) { + Value idVal(promise->getFixedSlot(PromiseSlot_DebugInfo)); + if (idVal.isUndefined()) { + idVal.setDouble(++gIDGenerator); + promise->setFixedSlot(PromiseSlot_DebugInfo, idVal); + } else if (idVal.isObject()) { + PromiseDebugInfo* debugInfo = FromPromise(promise); + idVal = debugInfo->getFixedSlot(Slot_Id); + if (idVal.isUndefined()) { + idVal.setDouble(++gIDGenerator); + debugInfo->setFixedSlot(Slot_Id, idVal); + } + } + return uint64_t(idVal.toNumber()); + } + + double allocationTime() { return getFixedSlot(Slot_AllocationTime).toNumber(); } + double resolutionTime() { return getFixedSlot(Slot_ResolutionTime).toNumber(); } + JSObject* allocationSite() { return getFixedSlot(Slot_AllocationSite).toObjectOrNull(); } + JSObject* resolutionSite() { return getFixedSlot(Slot_ResolutionSite).toObjectOrNull(); } + + static void setResolutionInfo(JSContext* cx, Handle promise) { + if (!ShouldCaptureDebugInfo(cx)) + return; + + // If async stacks weren't enabled and the Promise's global wasn't a + // debuggee when the Promise was created, we won't have a debugInfo + // object. We still want to capture the resolution stack, so we + // create the object now and change it's slots' values around a bit. + Rooted debugInfo(cx, FromPromise(promise)); + if (!debugInfo) { + RootedValue idVal(cx, promise->getFixedSlot(PromiseSlot_DebugInfo)); + debugInfo = create(cx, promise); + if (!debugInfo) { + cx->clearPendingException(); + return; + } + + // The current stack was stored in the AllocationSite slot, move + // it to ResolutionSite as that's what it really is. + debugInfo->setFixedSlot(Slot_ResolutionSite, + debugInfo->getFixedSlot(Slot_AllocationSite)); + debugInfo->setFixedSlot(Slot_AllocationSite, NullValue()); + + // There's no good default for a missing AllocationTime, so + // instead of resetting that, ensure that it's the same as + // ResolutionTime, so that the diff shows as 0, which isn't great, + // but bearable. + debugInfo->setFixedSlot(Slot_ResolutionTime, + debugInfo->getFixedSlot(Slot_AllocationTime)); + + // The Promise's ID might've been queried earlier, in which case + // it's stored in the DebugInfo slot. We saved that earlier, so + // now we can store it in the right place (or leave it as + // undefined if it wasn't ever initialized.) + debugInfo->setFixedSlot(Slot_Id, idVal); + return; + } + + RootedObject stack(cx); + if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) { + cx->clearPendingException(); + return; + } + + debugInfo->setFixedSlot(Slot_ResolutionSite, ObjectOrNullValue(stack)); + debugInfo->setFixedSlot(Slot_ResolutionTime, DoubleValue(MillisecondsSinceStartup())); + } +}; + +const Class PromiseDebugInfo::class_ = { + "PromiseDebugInfo", + JSCLASS_HAS_RESERVED_SLOTS(SlotCount) +}; + +double +PromiseObject::allocationTime() +{ + auto debugInfo = PromiseDebugInfo::FromPromise(this); + if (debugInfo) + return debugInfo->allocationTime(); + return 0; +} + +double +PromiseObject::resolutionTime() +{ + auto debugInfo = PromiseDebugInfo::FromPromise(this); + if (debugInfo) + return debugInfo->resolutionTime(); + return 0; +} + +JSObject* +PromiseObject::allocationSite() +{ + auto debugInfo = PromiseDebugInfo::FromPromise(this); + if (debugInfo) + return debugInfo->allocationSite(); + return nullptr; +} + +JSObject* +PromiseObject::resolutionSite() +{ + auto debugInfo = PromiseDebugInfo::FromPromise(this); + if (debugInfo) + return debugInfo->resolutionSite(); + return nullptr; +} + /** * Wrapper for GetAndClearException that handles cases where no exception is * pending, but an error occurred. This can be the case if an OOM was @@ -1278,13 +1444,11 @@ CreatePromiseObjectInternal(JSContext* cx, HandleObject proto /* = nullptr */, // Store an allocation stack so we can later figure out what the // control flow was for some unexpected results. Frightfully expensive, // but oh well. - RootedObject stack(cx); - if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) { - if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) + if (ShouldCaptureDebugInfo(cx)) { + PromiseDebugInfo* debugInfo = PromiseDebugInfo::create(cx, promise); + if (!debugInfo) return nullptr; } - promise->setFixedSlot(PromiseSlot_AllocationSite, ObjectOrNullValue(stack)); - promise->setFixedSlot(PromiseSlot_AllocationTime, DoubleValue(MillisecondsSinceStartup())); // Let the Debugger know about this Promise. if (informDebugger) @@ -3384,10 +3548,11 @@ AddPromiseReaction(JSContext* cx, Handle promise, HandleValue on return AddPromiseReaction(cx, promise, reaction); } -namespace { -// Generator used by PromiseObject::getID. -mozilla::Atomic gIDGenerator(0); -} // namespace +uint64_t +PromiseObject::getID() +{ + return PromiseDebugInfo::id(this); +} double PromiseObject::lifetime() @@ -3395,17 +3560,6 @@ PromiseObject::lifetime() return MillisecondsSinceStartup() - allocationTime(); } -uint64_t -PromiseObject::getID() -{ - Value idVal(getFixedSlot(PromiseSlot_Id)); - if (idVal.isUndefined()) { - idVal.setDouble(++gIDGenerator); - setFixedSlot(PromiseSlot_Id, idVal); - } - return uint64_t(idVal.toNumber()); -} - /** * Returns all promises that directly depend on this one. That means those * created by calling `then` on this promise, or the promise returned by @@ -3515,15 +3669,7 @@ PromiseObject::reject(JSContext* cx, Handle promise, HandleValue /* static */ void PromiseObject::onSettled(JSContext* cx, Handle promise) { - RootedObject stack(cx); - if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) { - if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) { - cx->clearPendingException(); - return; - } - } - promise->setFixedSlot(PromiseSlot_ResolutionSite, ObjectOrNullValue(stack)); - promise->setFixedSlot(PromiseSlot_ResolutionTime, DoubleValue(MillisecondsSinceStartup())); + PromiseDebugInfo::setResolutionInfo(cx, promise); if (promise->state() == JS::PromiseState::Rejected && promise->isUnhandled()) cx->runtime()->addUnhandledRejectedPromise(cx, promise); diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h index d8d178891a..a6d8c0831d 100644 --- a/js/src/builtin/Promise.h +++ b/js/src/builtin/Promise.h @@ -16,11 +16,7 @@ enum PromiseSlots { PromiseSlot_ReactionsOrResult, PromiseSlot_RejectFunction, PromiseSlot_AwaitGenerator = PromiseSlot_RejectFunction, - PromiseSlot_AllocationSite, - PromiseSlot_ResolutionSite, - PromiseSlot_AllocationTime, - PromiseSlot_ResolutionTime, - PromiseSlot_Id, + PromiseSlot_DebugInfo, PromiseSlots, }; @@ -74,14 +70,10 @@ class PromiseObject : public NativeObject static void onSettled(JSContext* cx, Handle promise); - double allocationTime() { return getFixedSlot(PromiseSlot_AllocationTime).toNumber(); } - double resolutionTime() { return getFixedSlot(PromiseSlot_ResolutionTime).toNumber(); } - JSObject* allocationSite() { - return getFixedSlot(PromiseSlot_AllocationSite).toObjectOrNull(); - } - JSObject* resolutionSite() { - return getFixedSlot(PromiseSlot_ResolutionSite).toObjectOrNull(); - } + double allocationTime(); + double resolutionTime(); + JSObject* allocationSite(); + JSObject* resolutionSite(); double lifetime(); double timeToResolution() { MOZ_ASSERT(state() != JS::PromiseState::Pending); From 6e2201d3a86c6046f738aae09e495607c8fdfe4c Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 22 Jan 2023 03:50:06 +0100 Subject: [PATCH 11/21] Issue #2089 - Only create result Promises in Promise#then and Promise#catch if it's used or the creation is otherwise observable Based-on: m-c 1342070 --- js/src/builtin/Promise.cpp | 144 ++++++++++++++++++++++++++++++++----- js/src/builtin/Promise.h | 15 ++-- js/src/jsapi.cpp | 11 ++- js/src/jsobjinlines.h | 6 ++ 4 files changed, 147 insertions(+), 29 deletions(-) diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 34c721f722..50c6c8f308 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -2588,8 +2588,8 @@ js::PromiseResolve(JSContext* cx, HandleObject constructor, HandleValue value) /** * ES2016, 25.4.4.4, Promise.reject. */ -bool -js::Promise_reject(JSContext* cx, unsigned argc, Value* vp) +static bool +Promise_reject(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedValue thisVal(cx, args.thisv()); @@ -2617,8 +2617,8 @@ PromiseObject::unforgeableReject(JSContext* cx, HandleValue value) /** * ES2016, 25.4.4.5, Promise.resolve. */ -bool -js::Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp) +static bool +Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedValue thisVal(cx, args.thisv()); @@ -2705,7 +2705,7 @@ IsPromiseSpecies(JSContext* cx, JSFunction* species) MOZ_MUST_USE bool js::OriginalPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled, HandleValue onRejected, - MutableHandleObject dependent, bool createDependent) + MutableHandleObject dependent, CreateDependentPromise createDependent) { RootedObject promiseObj(cx, promise); if (promise->compartment() != cx->compartment()) { @@ -2717,15 +2717,20 @@ js::OriginalPromiseThen(JSContext* cx, Handle promise, RootedObject resolve(cx); RootedObject reject(cx); - if (createDependent) { + if (createDependent != CreateDependentPromise::Never) { // Step 3. RootedObject C(cx, SpeciesConstructor(cx, promiseObj, JSProto_Promise, IsPromiseSpecies)); if (!C) return false; // Step 4. - if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, true)) - return false; + if (createDependent == CreateDependentPromise::Always || + !IsNativeFunction(C, PromiseConstructor)) + { + // Step 4. + if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, true)) + return false; + } } // Step 5. @@ -3225,18 +3230,77 @@ js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, return true; } -// ES2016, 25.4.5.3. -bool -js::Promise_then(JSContext* cx, unsigned argc, Value* vp) +static bool Promise_then(JSContext* cx, unsigned argc, Value* vp); +static bool Promise_then_impl(JSContext* cx, HandleValue promiseVal, HandleValue onFulfilled, + HandleValue onRejected, MutableHandleValue rval, bool rvalUsed); + +static bool +Promise_catch_impl(JSContext* cx, unsigned argc, Value* vp, bool rvalUsed) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. - RootedValue promiseVal(cx, args.thisv()); + RootedValue thenVal(cx); + if (!GetProperty(cx, args.thisv(), cx->names().then, &thenVal)) + return false; - RootedValue onFulfilled(cx, args.get(0)); - RootedValue onRejected(cx, args.get(1)); + if (IsNativeFunction(thenVal, &Promise_then)) { + return Promise_then_impl(cx, args.thisv(), UndefinedHandleValue, args.get(0), + args.rval(), rvalUsed); + } + FixedInvokeArgs<2> iargs(cx); + iargs[0].setUndefined(); + iargs[1].set(args.get(0)); + + return Call(cx, thenVal, args.thisv(), iargs, args.rval()); +} + +static MOZ_ALWAYS_INLINE bool +IsPromiseThenOrCatchRetValImplicitlyUsed(JSContext* cx) +{ + // The returned promise of Promise#then and Promise#catch contains + // stack info if async stack is enabled. Even if their return value is not + // used explicitly in the script, the stack info is observable in devtools + // and profilers. We shouldn't apply the optimization not to allocate the + // returned Promise object if the it's implicitly used by them. + // + // FIXME: Once bug 1280819 gets fixed, we can use ShouldCaptureDebugInfo. + if (!cx->options().asyncStack()) + return false; + + // If devtools is opened, the current compartment will become debuggee. + if (cx->compartment()->isDebuggee()) + return true; + + // The profiler also makes the call stack observable + if (JS::IsProfileTimelineRecordingEnabled()) + return true; + + // The stack is also observable from Error#stack, but we don't care since + // it's nonstandard feature. + return false; +} + +// ES2016, 25.4.5.3. +static bool +Promise_catch_noRetVal(JSContext* cx, unsigned argc, Value* vp) +{ + return Promise_catch_impl(cx, argc, vp, IsPromiseThenOrCatchRetValImplicitlyUsed(cx)); +} + +// ES2016, 25.4.5.3. +static bool +Promise_catch(JSContext* cx, unsigned argc, Value* vp) +{ + return Promise_catch_impl(cx, argc, vp, true); +} + +static bool +Promise_then_impl(JSContext* cx, HandleValue promiseVal, HandleValue onFulfilled, + HandleValue onRejected, MutableHandleValue rval, bool rvalUsed) +{ + // Step 1 (implicit). // Step 2. if (!promiseVal.isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, @@ -3264,14 +3328,40 @@ js::Promise_then(JSContext* cx, unsigned argc, Value* vp) } // Steps 3-5. + CreateDependentPromise createDependent = rvalUsed + ? CreateDependentPromise::Always + : CreateDependentPromise::SkipIfCtorUnobservable; RootedObject resultPromise(cx); - if (!OriginalPromiseThen(cx, promise, onFulfilled, onRejected, &resultPromise, true)) + if (!OriginalPromiseThen(cx, promise, onFulfilled, onRejected, &resultPromise, + createDependent)) + { return false; + } - args.rval().setObject(*resultPromise); + if (rvalUsed) + rval.setObject(*resultPromise); + else + rval.setUndefined(); return true; } +// ES2016, 25.4.5.3. +bool +Promise_then_noRetVal(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return Promise_then_impl(cx, args.thisv(), args.get(0), args.get(1), args.rval(), + IsPromiseThenOrCatchRetValImplicitlyUsed(cx)); +} + +// ES2016, 25.4.5.3. +static bool +Promise_then(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return Promise_then_impl(cx, args.thisv(), args.get(0), args.get(1), args.rval(), true); +} + // ES2016, 25.4.5.3.1. static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled_, @@ -3723,9 +3813,27 @@ CreatePromisePrototype(JSContext* cx, JSProtoKey key) return GlobalObject::createBlankPrototype(cx, cx->global(), &PromiseObject::protoClass_); } +const JSJitInfo promise_then_info = { + { (JSJitGetterOp)Promise_then_noRetVal }, + { 0 }, /* unused */ + { 0 }, /* unused */ + JSJitInfo::IgnoresReturnValueNative, + JSJitInfo::AliasEverything, + JSVAL_TYPE_UNDEFINED, +}; + +const JSJitInfo promise_catch_info = { + { (JSJitGetterOp)Promise_catch_noRetVal }, + { 0 }, /* unused */ + { 0 }, /* unused */ + JSJitInfo::IgnoresReturnValueNative, + JSJitInfo::AliasEverything, + JSVAL_TYPE_UNDEFINED, +}; + static const JSFunctionSpec promise_methods[] = { - JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0), - JS_FN("then", Promise_then, 2, 0), + JS_FNINFO("then", Promise_then, &promise_then_info, 2, 0), + JS_FNINFO("catch", Promise_catch, &promise_catch_info, 1, 0), JS_SELF_HOSTED_FN("finally", "Promise_finally", 1, 0), JS_FS_END }; diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h index a6d8c0831d..811e8efb94 100644 --- a/js/src/builtin/Promise.h +++ b/js/src/builtin/Promise.h @@ -106,6 +106,12 @@ class PromiseObject : public NativeObject MOZ_MUST_USE JSObject* GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises); +enum class CreateDependentPromise { + Always, + SkipIfCtorUnobservable, + Never +}; + /** * Enqueues resolve/reject reactions in the given Promise's reactions lists * as though calling the original value of Promise.prototype.then. @@ -119,7 +125,7 @@ GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises); MOZ_MUST_USE bool OriginalPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled, HandleValue onRejected, - MutableHandleObject dependent, bool createDependent); + MutableHandleObject dependent, CreateDependentPromise createDependent); /** * PromiseResolve ( C, x ) @@ -205,13 +211,6 @@ class PromiseTask : public JS::AsyncTask bool executeAndFinish(JSContext* cx); }; -bool -Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp); -bool -Promise_reject(JSContext* cx, unsigned argc, Value* vp); -bool -Promise_then(JSContext* cx, unsigned argc, Value* vp); - } // namespace js #endif /* builtin_Promise_h */ diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 167cd28d60..2b660e2c11 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4994,7 +4994,8 @@ JS::RejectPromise(JSContext* cx, JS::HandleObject promiseObj, JS::HandleValue re static bool CallOriginalPromiseThenImpl(JSContext* cx, JS::HandleObject promiseObj, JS::HandleObject onResolvedObj_, JS::HandleObject onRejectedObj_, - JS::MutableHandleObject resultObj, bool createDependent) + JS::MutableHandleObject resultObj, + CreateDependentPromise createDependent) { AssertHeapIsIdle(cx); CHECK_REQUEST(cx); @@ -5044,8 +5045,11 @@ JS::CallOriginalPromiseThen(JSContext* cx, JS::HandleObject promiseObj, JS::HandleObject onResolvedObj, JS::HandleObject onRejectedObj) { RootedObject resultPromise(cx); - if (!CallOriginalPromiseThenImpl(cx, promiseObj, onResolvedObj, onRejectedObj, &resultPromise, true)) + if (!CallOriginalPromiseThenImpl(cx, promiseObj, onResolvedObj, onRejectedObj, &resultPromise, + CreateDependentPromise::Always)) + { return nullptr; + } return resultPromise; } @@ -5054,7 +5058,8 @@ JS::AddPromiseReactions(JSContext* cx, JS::HandleObject promiseObj, JS::HandleObject onResolvedObj, JS::HandleObject onRejectedObj) { RootedObject resultPromise(cx); - bool result = CallOriginalPromiseThenImpl(cx, promiseObj, onResolvedObj, onRejectedObj, &resultPromise, false); + bool result = CallOriginalPromiseThenImpl(cx, promiseObj, onResolvedObj, onRejectedObj, + &resultPromise, CreateDependentPromise::Never); MOZ_ASSERT(!resultPromise); return result; } diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index 07eeff655e..9039fc7eb6 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -566,6 +566,12 @@ IsNativeFunction(const js::Value& v, JSNative native) return IsFunctionObject(v, &fun) && fun->maybeNative() == native; } +static MOZ_ALWAYS_INLINE bool +IsNativeFunction(const JSObject* obj, JSNative native) +{ + return obj->is() && obj->as().maybeNative() == native; +} + // Return whether looking up a method on 'obj' definitely resolves to the // original specified native function. The method may conservatively return // 'false' in the case of proxies or other non-native objects. From 55eacd43a45cacbad13c13dda2e9253641540aa3 Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 22 Jan 2023 04:11:22 +0100 Subject: [PATCH 12/21] Issue #2089 - Turn AsyncGeneratorResumeNext recursion into iteration to avoid stack overflow Based-on: m-c 1416809 --- js/src/builtin/Promise.cpp | 305 ++++++++++-------- .../tests/auto-regress/bug1416809-1.js | 12 + .../tests/auto-regress/bug1416809-2.js | 12 + 3 files changed, 196 insertions(+), 133 deletions(-) create mode 100644 js/src/jit-test/tests/auto-regress/bug1416809-1.js create mode 100644 js/src/jit-test/tests/auto-regress/bug1416809-2.js diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 50c6c8f308..881eb6e774 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -3008,47 +3008,21 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co return true; } +enum class ResumeNextKind { + Enqueue, Reject, Resolve +}; + static MOZ_MUST_USE bool -AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenObj); +AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenObj, + ResumeNextKind kind, HandleValue valueOrException = UndefinedHandleValue, + bool done = false); // Async Iteration proposal 11.4.3.3. MOZ_MUST_USE bool js::AsyncGeneratorResolve(JSContext* cx, Handle asyncGenObj, HandleValue value, bool done) { - // Step 1 (implicit). - - // Steps 2-3. - MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); - - // Step 4. - Rooted request( - cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj)); - if (!request) - return false; - - // Step 5. - RootedObject resultPromise(cx, request->promise()); - - asyncGenObj->cacheRequest(request); - - // Step 6. - RootedObject resultObj(cx, CreateIterResultObject(cx, value, done)); - if (!resultObj) - return false; - - RootedValue resultValue(cx, ObjectValue(*resultObj)); - - // Step 7. - if (!ResolvePromiseInternal(cx, resultPromise, resultValue)) - return false; - - // Step 8. - if (!AsyncGeneratorResumeNext(cx, asyncGenObj)) - return false; - - // Step 9. - return true; + return AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Resolve, value, done); } // Async Iteration proposal 11.4.3.4. @@ -3056,123 +3030,188 @@ MOZ_MUST_USE bool js::AsyncGeneratorReject(JSContext* cx, Handle asyncGenObj, HandleValue exception) { - // Step 1 (implicit). - - // Steps 2-3. - MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); - - // Step 4. - Rooted request( - cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj)); - if (!request) - return false; - - // Step 5. - RootedObject resultPromise(cx, request->promise()); - - asyncGenObj->cacheRequest(request); - - // Step 6. - if (!RejectMaybeWrappedPromise(cx, resultPromise, exception)) - return false; - - // Step 7. - if (!AsyncGeneratorResumeNext(cx, asyncGenObj)) - return false; - - // Step 8. - return true; + return AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Reject, exception); } // Async Iteration proposal 11.4.3.5. static MOZ_MUST_USE bool -AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenObj) +AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenObj, + ResumeNextKind kind, + HandleValue valueOrException_ /* = UndefinedHandleValue */, + bool done /* = false */) { - // Step 1 (implicit). + RootedValue valueOrException(cx, valueOrException_); - // Steps 2-3. - MOZ_ASSERT(!asyncGenObj->isExecuting()); + while (true) { + switch (kind) { + case ResumeNextKind::Enqueue: + // No further action required. + break; + case ResumeNextKind::Reject: { + // 11.4.3.4 AsyncGeneratorReject ( generator, exception ) + HandleValue exception = valueOrException; - // Step 4. - if (asyncGenObj->isAwaitingYieldReturn() || asyncGenObj->isAwaitingReturn()) - return true; + // Step 1 (implicit). - // Steps 5-6. - if (asyncGenObj->isQueueEmpty()) - return true; + // Steps 2-3. + MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); - // Steps 7-8. - Rooted request( - cx, AsyncGeneratorObject::peekRequest(cx, asyncGenObj)); - if (!request) - return false; + // Step 4. + Rooted request( + cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj)); + if (!request) + return false; - // Step 9. - CompletionKind completionKind = request->completionKind(); + // Step 5. + RootedObject resultPromise(cx, request->promise()); - // Step 10. - if (completionKind != CompletionKind::Normal) { - // Step 10.a. - if (asyncGenObj->isSuspendedStart()) - asyncGenObj->setCompleted(); + asyncGenObj->cacheRequest(request); - // Step 10.b. - if (asyncGenObj->isCompleted()) { - RootedValue value(cx, request->completionValue()); + // Step 6. + if (!RejectMaybeWrappedPromise(cx, resultPromise, exception)) + return false; - // Step 10.b.i. - if (completionKind == CompletionKind::Return) { - // Steps 10.b.i.1. - asyncGenObj->setAwaitingReturn(); + // Steps 7-8. + break; + } + case ResumeNextKind::Resolve: { + // 11.4.3.3 AsyncGeneratorResolve ( generator, value, done ) + HandleValue value = valueOrException; - // Steps 10.b.i.4-6 (reordered). - RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled)); - RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorResumeNextReturnRejected)); + // Step 1 (implicit). - // Steps 10.b.i.2-3, 7-10. - auto extra = [&](Handle reaction) { - reaction->setIsAsyncGenerator(asyncGenObj); - }; - return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra); - } + // Steps 2-3. + MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); - // Step 10.b.ii.1. - MOZ_ASSERT(completionKind == CompletionKind::Throw); + // Step 4. + Rooted request( + cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj)); + if (!request) + return false; - // Steps 10.b.ii.2-3. - return AsyncGeneratorReject(cx, asyncGenObj, value); + // Step 5. + RootedObject resultPromise(cx, request->promise()); + + asyncGenObj->cacheRequest(request); + + // Step 6. + RootedObject resultObj(cx, CreateIterResultObject(cx, value, done)); + if (!resultObj) + return false; + + RootedValue resultValue(cx, ObjectValue(*resultObj)); + + // Step 7. + if (!ResolvePromiseInternal(cx, resultPromise, resultValue)) + return false; + + // Steps 8-9. + break; + } } - } else if (asyncGenObj->isCompleted()) { - // Step 11. - return AsyncGeneratorResolve(cx, asyncGenObj, UndefinedHandleValue, true); + + // Step 1 (implicit). + + // Steps 2-3. + MOZ_ASSERT(!asyncGenObj->isExecuting()); + + // Step 4. + if (asyncGenObj->isAwaitingYieldReturn() || asyncGenObj->isAwaitingReturn()) + return true; + + // Steps 5-6. + if (asyncGenObj->isQueueEmpty()) + return true; + + // Steps 7-8. + Rooted request( + cx, AsyncGeneratorObject::peekRequest(cx, asyncGenObj)); + if (!request) + return false; + + // Step 9. + CompletionKind completionKind = request->completionKind(); + + // Step 10. + if (completionKind != CompletionKind::Normal) { + // Step 10.a. + if (asyncGenObj->isSuspendedStart()) + asyncGenObj->setCompleted(); + + // Step 10.b. + if (asyncGenObj->isCompleted()) { + RootedValue value(cx, request->completionValue()); + + // Step 10.b.i. + if (completionKind == CompletionKind::Return) { + // Steps 10.b.i.1. + asyncGenObj->setAwaitingReturn(); + + // Steps 10.b.i.4-6 (reordered). + static constexpr int32_t ResumeNextReturnFulfilled = + PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled; + static constexpr int32_t ResumeNextReturnRejected = + PromiseHandlerAsyncGeneratorResumeNextReturnRejected; + + RootedValue onFulfilled(cx, Int32Value(ResumeNextReturnFulfilled)); + RootedValue onRejected(cx, Int32Value(ResumeNextReturnRejected)); + + // Steps 10.b.i.2-3, 7-10. + auto extra = [&](Handle reaction) { + reaction->setIsAsyncGenerator(asyncGenObj); + }; + return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra); + } + + // Step 10.b.ii.1. + MOZ_ASSERT(completionKind == CompletionKind::Throw); + + // Steps 10.b.ii.2-3. + kind = ResumeNextKind::Reject; + valueOrException.set(value); + // |done| is unused for ResumeNextKind::Reject. + continue; + } + } else if (asyncGenObj->isCompleted()) { + // Step 11. + kind = ResumeNextKind::Resolve; + valueOrException.setUndefined(); + done = true; + continue; + } + + // Step 12. + MOZ_ASSERT(asyncGenObj->isSuspendedStart() || asyncGenObj->isSuspendedYield()); + + // Step 16 (reordered). + asyncGenObj->setExecuting(); + + RootedValue argument(cx, request->completionValue()); + + if (completionKind == CompletionKind::Return) { + // 11.4.3.7 AsyncGeneratorYield step 8.b-e. + // Since we don't have the place that handles return from yield + // inside the generator, handle the case here, with extra state + // State_AwaitingYieldReturn. + asyncGenObj->setAwaitingYieldReturn(); + + static constexpr int32_t YieldReturnAwaitedFulfilled = + PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled; + static constexpr int32_t YieldReturnAwaitedRejected = + PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected; + + RootedValue onFulfilled(cx, Int32Value(YieldReturnAwaitedFulfilled)); + RootedValue onRejected(cx, Int32Value(YieldReturnAwaitedRejected)); + + auto extra = [&](Handle reaction) { + reaction->setIsAsyncGenerator(asyncGenObj); + }; + return InternalAwait(cx, argument, nullptr, onFulfilled, onRejected, extra); + } + + // Steps 13-15, 17-21. + return AsyncGeneratorResume(cx, asyncGenObj, completionKind, argument); } - - // Step 12. - MOZ_ASSERT(asyncGenObj->isSuspendedStart() || asyncGenObj->isSuspendedYield()); - - // Step 16 (reordered). - asyncGenObj->setExecuting(); - - RootedValue argument(cx, request->completionValue()); - - if (completionKind == CompletionKind::Return) { - // 11.4.3.7 AsyncGeneratorYield step 8.b-e. - // Since we don't have the place that handles return from yield - // inside the generator, handle the case here, with extra state - // State_AwaitingYieldReturn. - asyncGenObj->setAwaitingYieldReturn(); - - RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled)); - RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected)); - - auto extra = [&](Handle reaction) { - reaction->setIsAsyncGenerator(asyncGenObj); - }; - return InternalAwait(cx, argument, nullptr, onFulfilled, onRejected, extra); - } - - // Steps 13-15, 17-21. - return AsyncGeneratorResume(cx, asyncGenObj, completionKind, argument); } // Async Iteration proposal 11.4.3.6. @@ -3221,7 +3260,7 @@ js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, // Step 7. if (!asyncGenObj->isExecuting()) { // Step 8. - if (!AsyncGeneratorResumeNext(cx, asyncGenObj)) + if (!AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Enqueue)) return false; } diff --git a/js/src/jit-test/tests/auto-regress/bug1416809-1.js b/js/src/jit-test/tests/auto-regress/bug1416809-1.js new file mode 100644 index 0000000000..067943575b --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/bug1416809-1.js @@ -0,0 +1,12 @@ +// https://tc39.github.io/proposal-async-iteration + +// Recursion between: +// 11.4.3.3 AsyncGeneratorResolve, step 8 +// 11.4.3.5 AsyncGeneratorResumeNext, step 11. + +var asyncIter = async function*(){ yield; }(); +asyncIter.next(); + +for (var i = 0; i < 20000; i++) { + asyncIter.next(); +} diff --git a/js/src/jit-test/tests/auto-regress/bug1416809-2.js b/js/src/jit-test/tests/auto-regress/bug1416809-2.js new file mode 100644 index 0000000000..95b20ec607 --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/bug1416809-2.js @@ -0,0 +1,12 @@ +// https://tc39.github.io/proposal-async-iteration + +// Recursion between: +// 11.4.3.4 AsyncGeneratorReject, step 7. +// 11.4.3.5 AsyncGeneratorResumeNext, step 10.b.ii.2. + +var asyncIter = async function*(){ yield; }(); +asyncIter.next(); + +for (var i = 0; i < 20000; i++) { + asyncIter.throw(); +} From 9744b0c2bcac6d39ccc9f0d3306af1be3a896cc6 Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 22 Jan 2023 15:14:08 +0100 Subject: [PATCH 13/21] Issue #2089 - Optimize Promise.then and Promise.all/race with default resolving functions Based-on: m-c 1401508,1412200,1473970/{1-4} --- js/src/builtin/Promise.cpp | 672 ++++++++++++++---- .../debug/Promise-race-dependent-promises.js | 46 ++ .../debugger-reaction-does-not-resolve.js | 71 ++ .../resolve-promise-scripted-and-api.js | 15 + .../settle-now-breaks-all-invariants-1.js | 21 + .../settle-now-breaks-all-invariants-10.js | 18 + .../settle-now-breaks-all-invariants-11.js | 18 + .../settle-now-breaks-all-invariants-2.js | 27 + .../settle-now-breaks-all-invariants-3.js | 27 + .../settle-now-breaks-all-invariants-4.js | 18 + .../settle-now-breaks-all-invariants-5.js | 18 + .../settle-now-breaks-all-invariants-6.js | 20 + .../settle-now-breaks-all-invariants-7.js | 23 + .../settle-now-breaks-all-invariants-8.js | 7 + .../settle-now-breaks-all-invariants-9.js | 15 + js/src/jit-test/tests/realms/promise-then.js | 30 + js/src/vm/Runtime.cpp | 4 +- 17 files changed, 907 insertions(+), 143 deletions(-) create mode 100644 js/src/jit-test/tests/debug/Promise-race-dependent-promises.js create mode 100644 js/src/jit-test/tests/promise/debugger-reaction-does-not-resolve.js create mode 100644 js/src/jit-test/tests/promise/resolve-promise-scripted-and-api.js create mode 100644 js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-1.js create mode 100644 js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-10.js create mode 100644 js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-11.js create mode 100644 js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-2.js create mode 100644 js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-3.js create mode 100644 js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-4.js create mode 100644 js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-5.js create mode 100644 js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-6.js create mode 100644 js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-7.js create mode 100644 js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-8.js create mode 100644 js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-9.js create mode 100644 js/src/jit-test/tests/realms/promise-then.js diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 881eb6e774..a6f7a29d10 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -7,6 +7,7 @@ #include "builtin/Promise.h" #include "mozilla/Atomics.h" +#include "mozilla/Maybe.h" #include "mozilla/TimeStamp.h" #include "jscntxt.h" @@ -59,6 +60,9 @@ enum PromiseHandler { // enumerate both true and false cases here. PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone, PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone, + + // One past the maximum allowed PromiseHandler value. + PromiseHandlerLimit }; enum ResolutionMode { @@ -86,16 +90,35 @@ enum ReactionJobSlots { }; enum ThenableJobSlots { + // The handler to use as the Promise reaction. It is a callable object + // that's guaranteed to be from the same compartment as the + // PromiseReactionJob. ThenableJobSlot_Handler = 0, + + // JobData - a, potentially CCW-wrapped, dense list containing data + // required for proper execution of the reaction. ThenableJobSlot_JobData, }; enum ThenableJobDataIndices { + // The Promise to resolve using the given thenable. ThenableJobDataIndex_Promise = 0, + + // The thenable to use as the receiver when calling the `then` function. ThenableJobDataIndex_Thenable, + ThenableJobDataLength, }; +enum BuiltinThenableJobSlots { + // The Promise to resolve using the given thenable. + BuiltinThenableJobSlot_Promise = 0, + + // The thenable to use as the receiver when calling the built-in `then` + // function. + BuiltinThenableJobSlot_Thenable, +}; + enum PromiseAllDataHolderSlots { PromiseAllDataHolderSlot_Promise = 0, PromiseAllDataHolderSlot_RemainingElements, @@ -359,27 +382,74 @@ AbruptRejectPromise(JSContext *cx, CallArgs& args, HandleObject promiseObj, Hand } enum ReactionRecordSlots { + // The promise for which this record provides a reaction handler. + // Matches the [[Capability]].[[Promise]] field from the spec. + // + // The slot value is either an object, but not necessarily a built-in + // Promise object, or null. The latter case is only possible for async + // generator functions, in which case the REACTION_FLAG_ASYNC_GENERATOR + // flag must be set. ReactionRecordSlot_Promise = 0, + + // The [[Handler]] field(s) of a PromiseReaction record. We create a + // single reaction record for fulfillment and rejection, therefore our + // PromiseReaction implementation needs two [[Handler]] fields. + // + // The slot value is either a callable object, an integer constant from + // the |PromiseHandler| enum, or null. If the value is null, either the + // REACTION_FLAG_DEBUGGER_DUMMY or the + // REACTION_FLAG_DEFAULT_RESOLVING_HANDLER flag must be set. ReactionRecordSlot_OnFulfilled, ReactionRecordSlot_OnRejected, + + // The functions to resolve or reject the promise. Matches the + // [[Capability]].[[Resolve]] and [[Capability]].[[Reject]] fields from + // the spec. + // + // The slot values are either callable objects or null, but the latter + // case is only allowed if the promise is either a built-in Promise object + // or null. ReactionRecordSlot_Resolve, ReactionRecordSlot_Reject, + + // The incumbent global for this reaction record. Can be null. ReactionRecordSlot_IncumbentGlobalObject, + + // Bitmask of the REACTION_FLAG values. ReactionRecordSlot_Flags, + + // Argument when calling the function stored in OnFulfilled or OnRejected. ReactionRecordSlot_HandlerArg, - ReactionRecordSlot_Generator, + + // Additional slot to store extra data for specific reaction record types. + // + // - When the REACTION_FLAG_ASYNC_GENERATOR flag is set, this slot store + // the async generator function for this promise reaction. + // - When the REACTION_FLAG_DEFAULT_RESOLVING_HANDLER flag is set, this + // slot stores the promise to resolve when conceptually "calling" the + // OnFulfilled or OnRejected handlers. + ReactionRecordSlot_GeneratorOrPromiseToResolve, + ReactionRecordSlots, }; -#define REACTION_FLAG_RESOLVED 0x1 -#define REACTION_FLAG_FULFILLED 0x2 -#define REACTION_FLAG_IGNORE_DEFAULT_RESOLUTION 0x4 -#define REACTION_FLAG_ASYNC_FUNCTION 0x8 -#define REACTION_FLAG_ASYNC_GENERATOR 0x10 - // ES2016, 25.4.1.2. class PromiseReactionRecord : public NativeObject { + static constexpr size_t REACTION_FLAG_RESOLVED = 0x1; + static constexpr size_t REACTION_FLAG_FULFILLED = 0x2; + static constexpr size_t REACTION_FLAG_DEFAULT_RESOLVING_HANDLER = 0x4; + static constexpr size_t REACTION_FLAG_ASYNC_FUNCTION = 0x8; + static constexpr size_t REACTION_FLAG_ASYNC_GENERATOR = 0x10; + static constexpr size_t REACTION_FLAG_DEBUGGER_DUMMY = 0x20; + + void setFlagOnInitialState(size_t flag) { + int32_t flags = this->flags(); + MOZ_ASSERT(flags == 0, "Can't modify with non-default flags"); + flags |= flag; + setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); + } + public: static const Class class_; @@ -402,21 +472,29 @@ class PromiseReactionRecord : public NativeObject flags |= REACTION_FLAG_FULFILLED; setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); } - void setIsAsyncFunction() { + void setIsDefaultResolvingHandler(PromiseObject* promiseToResolve) { + setFlagOnInitialState(REACTION_FLAG_DEFAULT_RESOLVING_HANDLER); + setFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve, ObjectValue(*promiseToResolve)); + } + bool isDefaultResolvingHandler() { int32_t flags = this->flags(); - flags |= REACTION_FLAG_ASYNC_FUNCTION; - setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); + return flags & REACTION_FLAG_DEFAULT_RESOLVING_HANDLER; + } + PromiseObject* defaultResolvingPromise() { + MOZ_ASSERT(isDefaultResolvingHandler()); + const Value& promiseToResolve = getFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve); + return &promiseToResolve.toObject().as(); + } + void setIsAsyncFunction() { + setFlagOnInitialState(REACTION_FLAG_ASYNC_FUNCTION); } bool isAsyncFunction() { int32_t flags = this->flags(); return flags & REACTION_FLAG_ASYNC_FUNCTION; } - void setIsAsyncGenerator(Handle asyncGenObj) { - int32_t flags = this->flags(); - flags |= REACTION_FLAG_ASYNC_GENERATOR; - setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); - - setFixedSlot(ReactionRecordSlot_Generator, ObjectValue(*asyncGenObj)); + void setIsAsyncGenerator(AsyncGeneratorObject* asyncGenObj) { + setFlagOnInitialState(REACTION_FLAG_ASYNC_GENERATOR); + setFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve, ObjectValue(*asyncGenObj)); } bool isAsyncGenerator() { int32_t flags = this->flags(); @@ -424,8 +502,15 @@ class PromiseReactionRecord : public NativeObject } AsyncGeneratorObject* asyncGenerator() { MOZ_ASSERT(isAsyncGenerator()); - return &getFixedSlot(ReactionRecordSlot_Generator).toObject() - .as(); + const Value& generator = getFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve); + return &generator.toObject().as(); + } + void setIsDebuggerDummy() { + setFlagOnInitialState(REACTION_FLAG_DEBUGGER_DUMMY); + } + bool isDebuggerDummy() { + int32_t flags = this->flags(); + return flags & REACTION_FLAG_DEBUGGER_DUMMY; } Value handler() { MOZ_ASSERT(targetState() != JS::PromiseState::Pending); @@ -470,9 +555,9 @@ static bool RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp); // ES2016, 25.4.1.3. static MOZ_MUST_USE bool -CreateResolvingFunctions(JSContext* cx, HandleValue promise, - MutableHandleValue resolveVal, - MutableHandleValue rejectVal) +CreateResolvingFunctions(JSContext* cx, HandleObject promise, + MutableHandleObject resolveFn, + MutableHandleObject rejectFn) { RootedAtom funName(cx, cx->names().empty); RootedFunction resolve(cx, NewNativeFunction(cx, ResolvePromiseFunction, 1, funName, @@ -485,19 +570,34 @@ CreateResolvingFunctions(JSContext* cx, HandleValue promise, if (!reject) return false; - resolve->setExtendedSlot(ResolveFunctionSlot_Promise, promise); + resolve->setExtendedSlot(ResolveFunctionSlot_Promise, ObjectValue(*promise)); resolve->setExtendedSlot(ResolveFunctionSlot_RejectFunction, ObjectValue(*reject)); - reject->setExtendedSlot(RejectFunctionSlot_Promise, promise); + reject->setExtendedSlot(RejectFunctionSlot_Promise, ObjectValue(*promise)); reject->setExtendedSlot(RejectFunctionSlot_ResolveFunction, ObjectValue(*resolve)); - resolveVal.setObject(*resolve); - rejectVal.setObject(*reject); + resolveFn.set(resolve); + rejectFn.set(reject); return true; } static void ClearResolutionFunctionSlots(JSFunction* resolutionFun); + +static bool +IsSettledMaybeWrappedPromise(JSObject* promise) +{ + if (IsProxy(promise)) { + promise = UncheckedUnwrap(promise); + + // Caller needs to handle dead wrappers. + if (JS_IsDeadWrapper(promise)) + return false; + } + + return promise->as().state() != JS::PromiseState::Pending; +} + static MOZ_MUST_USE bool RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue reason); @@ -531,9 +631,8 @@ RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp) // In some cases the Promise reference on the resolution function won't // have been removed during resolution, so we need to check that here, // too. - if (promise->is() && - promise->as().state() != JS::PromiseState::Pending) - { + if (IsSettledMaybeWrappedPromise(promise)) { + args.rval().setUndefined(); return true; } @@ -552,10 +651,20 @@ static MOZ_MUST_USE bool EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue thenable, HandleValue thenVal); +static MOZ_MUST_USE bool EnqueuePromiseResolveThenableBuiltinJob(JSContext* cx, + HandleObject promiseToResolve, + HandleObject thenable); + +static bool Promise_then(JSContext* cx, unsigned argc, Value* vp); +static bool Promise_then_impl(JSContext* cx, HandleValue promiseVal, HandleValue onFulfilled, + HandleValue onRejected, MutableHandleValue rval, bool rvalUsed); + // ES2016, 25.4.1.3.2, steps 6-13. static MOZ_MUST_USE bool ResolvePromiseInternal(JSContext* cx, HandleObject promise, HandleValue resolutionVal) { + MOZ_ASSERT(!IsSettledMaybeWrappedPromise(promise)); + // Step 7 (reordered). if (!resolutionVal.isObject()) return FulfillMaybeWrappedPromise(cx, promise, resolutionVal); @@ -578,25 +687,57 @@ ResolvePromiseInternal(JSContext* cx, HandleObject promise, HandleValue resoluti RootedValue thenVal(cx); bool status = GetProperty(cx, resolution, resolution, cx->names().then, &thenVal); - // Step 9. + RootedValue error(cx); if (!status) { - RootedValue error(cx); if (!MaybeGetAndClearException(cx, &error)) return false; - - return RejectMaybeWrappedPromise(cx, promise, error); } + // Testing functions allow to directly settle a promise without going + // through the resolving functions. In that case the normal bookkeeping to + // ensure only pending promises can be resolved doesn't apply and we need + // to manually check for already settled promises. The exception is simply + // dropped when this case happens. + if (IsSettledMaybeWrappedPromise(promise)) + return true; + + // Step 9. + if (!status) + return RejectMaybeWrappedPromise(cx, promise, error); + // Step 10 (implicit). // Step 11. if (!IsCallable(thenVal)) return FulfillMaybeWrappedPromise(cx, promise, resolutionVal); + // If the resolution object is a built-in Promise object, possibly from a + // different realm in the same compartment, and the `then` property is the + // original Promise.prototype.then function from the current realm, we + // skip storing/calling it. + // And additionally require that |promise| itself is also a built-in + // Promise object from the same compartment, so the fast path doesn't need + // to cope with wrappers. + bool isBuiltinThen = false; + if (resolution->is() && + resolution->as().compartment() == cx->compartment() && + IsNativeFunction(thenVal, Promise_then) && + promise->is() && + promise->as().compartment() == cx->compartment()) + { + thenVal = UndefinedValue(); + isBuiltinThen = true; + } + // Step 12. - RootedValue promiseVal(cx, ObjectValue(*promise)); - if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal, thenVal)) - return false; + if (!isBuiltinThen) { + RootedValue promiseVal(cx, ObjectValue(*promise)); + if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal, thenVal)) + return false; + } else { + if (!EnqueuePromiseResolveThenableBuiltinJob(cx, promise, resolution)) + return false; + } // Step 13. return true; @@ -631,9 +772,8 @@ ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp) // In some cases the Promise reference on the resolution function won't // have been removed during resolution, so we need to check that here, // too. - if (promise->is() && - promise->as().state() != JS::PromiseState::Pending) - { + if (IsSettledMaybeWrappedPromise(promise)) { + args.rval().setUndefined(); return true; } @@ -659,6 +799,9 @@ MOZ_MUST_USE static bool EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj, HandleValue handlerArg_, JS::PromiseState targetState) { + MOZ_ASSERT(targetState == JS::PromiseState::Fulfilled || + targetState == JS::PromiseState::Rejected); + // The reaction might have been stored on a Promise from another // compartment, which means it would've been wrapped in a CCW. // To properly handle that case here, unwrap it and enter its @@ -715,7 +858,7 @@ EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj, } // Create the JS function to call when the job is triggered. - RootedAtom funName(cx, cx->names().empty); + HandlePropertyName funName = cx->names().empty; RootedFunction job(cx, NewNativeFunction(cx, PromiseReactionJob, 0, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!job) @@ -738,8 +881,8 @@ EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj, // single compartment to the enqueuePromiseJob callback. RootedObject promise(cx, reaction->promise()); if (promise && promise->is()) { - if (!cx->compartment()->wrap(cx, &promise)) - return false; + if (!cx->compartment()->wrap(cx, &promise)) + return false; } // Using objectFromIncumbentGlobal, we can derive the incumbent global by @@ -829,8 +972,6 @@ FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue v return false; } - MOZ_ASSERT(promise->state() == JS::PromiseState::Pending); - return ResolvePromise(cx, promise, value, JS::PromiseState::Fulfilled); } @@ -858,6 +999,32 @@ CreatePromiseObjectWithoutResolutionFunctions(JSContext* cx) return promise; } +static MOZ_MUST_USE PromiseObject* +CreatePromiseWithDefaultResolutionFunctions(JSContext* cx, MutableHandleObject resolve, + MutableHandleObject reject) +{ + // ES2016, 25.4.3.1., as if called with GetCapabilitiesExecutor as the + // executor argument. + + // Steps 1-2 (Not applicable). + + // Steps 3-7. + Rooted promise(cx, CreatePromiseObjectInternal(cx)); + if (!promise) + return nullptr; + + // Step 8. + if (!CreateResolvingFunctions(cx, promise, resolve, reject)) + return nullptr; + + promise->setFixedSlot(PromiseSlot_RejectFunction, ObjectValue(*reject)); + + // Steps 9-10 (Not applicable). + + // Step 11. + return promise; +} + // ES2016, 25.4.1.5. static MOZ_MUST_USE bool NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, @@ -881,8 +1048,15 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, // pass the reject (and resolve, in the race case) function to thenables // in the list passed to all/race, which (potentially) means exposing them // to content. - if (canOmitResolutionFunctions && IsNativeFunction(cVal, PromiseConstructor)) { - promise.set(CreatePromiseObjectWithoutResolutionFunctions(cx)); + // + // For Promise.all and Promise.race we can only optimize away the creation + // of the GetCapabilitiesExecutor function, and directly allocate the + // result promise instead of invoking the Promise constructor. + if (IsNativeFunction(cVal, PromiseConstructor)) { + if (canOmitResolutionFunctions) + promise.set(CreatePromiseObjectWithoutResolutionFunctions(cx)); + else + promise.set(CreatePromiseWithDefaultResolutionFunctions(cx, resolve, reject)); if (!promise) return false; return true; @@ -891,7 +1065,7 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, // Step 3 (omitted). // Step 4. - RootedAtom funName(cx, cx->names().empty); + HandlePropertyName funName = cx->names().empty; RootedFunction executor(cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!executor) @@ -995,8 +1169,6 @@ RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue re } } - MOZ_ASSERT(promise->state() == JS::PromiseState::Pending); - return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected); } @@ -1005,6 +1177,8 @@ static MOZ_MUST_USE bool TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseState state, HandleValue valueOrReason) { + MOZ_ASSERT(state == JS::PromiseState::Fulfilled || state == JS::PromiseState::Rejected); + RootedObject reactions(cx, &reactionsVal.toObject()); RootedObject reaction(cx); @@ -1024,6 +1198,54 @@ TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseStat return true; } +// Implements PromiseReactionJob optimized for the case when the reaction +// handler is one of the default resolving functions as created by the +// CreateResolvingFunctions abstract operation. +static MOZ_MUST_USE bool +DefaultResolvingPromiseReactionJob(JSContext* cx, Handle reaction, + MutableHandleValue rval) +{ + MOZ_ASSERT(reaction->targetState() != JS::PromiseState::Pending); + + Rooted promiseToResolve(cx, reaction->defaultResolvingPromise()); + + // Testing functions allow to directly settle a promise without going + // through the resolving functions. In that case the normal bookkeeping to + // ensure only pending promises can be resolved doesn't apply and we need + // to manually check for already settled promises. We still call + // RunResolutionFunction for consistency with PromiseReactionJob. + ResolutionMode resolutionMode = ResolveMode; + RootedValue handlerResult(cx, UndefinedValue()); + if (promiseToResolve->state() == JS::PromiseState::Pending) { + RootedValue argument(cx, reaction->handlerArg()); + + // Step 6. + bool ok; + if (reaction->targetState() == JS::PromiseState::Fulfilled) + ok = ResolvePromiseInternal(cx, promiseToResolve, argument); + else + ok = RejectMaybeWrappedPromise(cx, promiseToResolve, argument); + + if (!ok) { + resolutionMode = RejectMode; + if (!MaybeGetAndClearException(cx, &handlerResult)) + return false; + } + } + + // Steps 7-9. + size_t hookSlot = resolutionMode == RejectMode + ? ReactionRecordSlot_Reject + : ReactionRecordSlot_Resolve; + RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull()); + RootedObject promiseObj(cx, reaction->promise()); + if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode, promiseObj)) + return false; + + rval.setUndefined(); + return true; +} + static MOZ_MUST_USE bool AsyncFunctionPromiseReactionJob(JSContext* cx, Handle reaction, MutableHandleValue rval) @@ -1035,7 +1257,7 @@ AsyncFunctionPromiseReactionJob(JSContext* cx, Handle re Rooted resultPromise(cx, &reaction->promise()->as()); RootedValue generatorVal(cx, resultPromise->getFixedSlot(PromiseSlot_AwaitGenerator)); - int32_t handlerNum = int32_t(handlerVal.toNumber()); + int32_t handlerNum = handlerVal.toInt32(); // Await's handlers don't return a value, nor throw exception. // They fail only on OOM. @@ -1062,7 +1284,7 @@ AsyncGeneratorPromiseReactionJob(JSContext* cx, Handle r RootedValue argument(cx, reaction->handlerArg()); Rooted asyncGenObj(cx, reaction->asyncGenerator()); - int32_t handlerNum = int32_t(handlerVal.toNumber()); + int32_t handlerNum = handlerVal.toInt32(); // Await's handlers don't return a value, nor throw exception. // They fail only on OOM. @@ -1144,10 +1366,14 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) // Steps 1-2. Rooted reaction(cx, &reactionObj->as()); + if (reaction->isDefaultResolvingHandler()) + return DefaultResolvingPromiseReactionJob(cx, reaction, args.rval()); if (reaction->isAsyncFunction()) return AsyncFunctionPromiseReactionJob(cx, reaction, args.rval()); if (reaction->isAsyncGenerator()) return AsyncGeneratorPromiseReactionJob(cx, reaction, args.rval()); + if (reaction->isDebuggerDummy()) + return true; // Step 3. RootedValue handlerVal(cx, reaction->handler()); @@ -1158,8 +1384,8 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) ResolutionMode resolutionMode = ResolveMode; // Steps 4-6. - if (handlerVal.isNumber()) { - int32_t handlerNum = int32_t(handlerVal.toNumber()); + if (handlerVal.isInt32()) { + int32_t handlerNum = handlerVal.toInt32(); // Step 4. if (handlerNum == PromiseHandlerIdentity) { @@ -1181,6 +1407,9 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) handlerResult = ObjectValue(*resultObj); } } else { + MOZ_ASSERT(handlerVal.isObject()); + MOZ_ASSERT(IsCallable(handlerVal)); + // Step 6. FixedInvokeArgs<1> args2(cx); args2[0].set(argument); @@ -1209,28 +1438,14 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) * Callback for resolving a thenable, to be invoked by the embedding during * its processing of the Promise job queue. * - * See http://www.ecma-international.org/ecma-262/7.0/index.html#sec-jobs-and-job-queues + * See https://tc39.github.io/ecma262/#sec-jobs-and-job-queues * * A PromiseResolveThenableJob is set as the native function of an extended * JSFunction object, with all information required for the job's * execution stored in the function's extended slots. * - * Usage of the function's extended slots is as follows: - * ThenableJobSlot_Handler: The handler to use as the Promise reaction. - * This can be PromiseHandlerIdentity, - * PromiseHandlerThrower, or a callable. In the - * latter case, it's guaranteed to be an object - * from the same compartment as the - * PromiseReactionJob. - * ThenableJobSlot_JobData: JobData - a, potentially CCW-wrapped, dense list - * containing data required for proper execution of - * the reaction. - * - * The JobData list has the following entries: - * ThenableJobDataSlot_Promise: The Promise to resolve using the given - * thenable. - * ThenableJobDataSlot_Thenable: The thenable to use as the receiver when - * calling the `then` function. + * Usage of the function's extended slots is described in the ThenableJobSlots + * enum. */ static bool PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp) @@ -1239,39 +1454,96 @@ PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp) RootedFunction job(cx, &args.callee().as()); RootedValue then(cx, job->getExtendedSlot(ThenableJobSlot_Handler)); + MOZ_ASSERT(then.isObject()); MOZ_ASSERT(!IsWrapper(&then.toObject())); RootedNativeObject jobArgs(cx, &job->getExtendedSlot(ThenableJobSlot_JobData) .toObject().as()); - RootedValue promise(cx, jobArgs->getDenseElement(ThenableJobDataIndex_Promise)); + RootedObject promise(cx, &jobArgs->getDenseElement(ThenableJobDataIndex_Promise).toObject()); RootedValue thenable(cx, jobArgs->getDenseElement(ThenableJobDataIndex_Thenable)); // Step 1. - RootedValue resolveVal(cx); - RootedValue rejectVal(cx); - if (!CreateResolvingFunctions(cx, promise, &resolveVal, &rejectVal)) + RootedObject resolveFn(cx); + RootedObject rejectFn(cx); + if (!CreateResolvingFunctions(cx, promise, &resolveFn, &rejectFn)) return false; // Step 2. FixedInvokeArgs<2> args2(cx); - args2[0].set(resolveVal); - args2[1].set(rejectVal); - - RootedValue rval(cx); + args2[0].setObject(*resolveFn); + args2[1].setObject(*rejectFn); // In difference to the usual pattern, we return immediately on success. + RootedValue rval(cx); if (Call(cx, then, thenable, args2, &rval)) return true; + // Steps 3-4. if (!MaybeGetAndClearException(cx, &rval)) return false; FixedInvokeArgs<1> rejectArgs(cx); rejectArgs[0].set(rval); + RootedValue rejectVal(cx, ObjectValue(*rejectFn)); return Call(cx, rejectVal, UndefinedHandleValue, rejectArgs, &rval); } +static MOZ_MUST_USE bool +OriginalPromiseThenWithoutSettleHandlers(JSContext* cx, Handle promise, + Handle promiseToResolve); + +/** + * Specialization of PromiseResolveThenableJob when the `thenable` is a + * built-in Promise object and the `then` property is the built-in + * `Promise.prototype.then` function. + * + * A PromiseResolveBuiltinThenableJob is set as the native function of an + * extended JSFunction object, with all information required for the job's + * execution stored in the function's extended slots. + * + * Usage of the function's extended slots is described in the + * BuiltinThenableJobSlots enum. + */ +static bool +PromiseResolveBuiltinThenableJob(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedFunction job(cx, &args.callee().as()); + RootedObject promise(cx, &job->getExtendedSlot(BuiltinThenableJobSlot_Promise).toObject()); + RootedObject thenable(cx, &job->getExtendedSlot(BuiltinThenableJobSlot_Thenable).toObject()); + + assertSameCompartment(cx, promise, thenable); + MOZ_ASSERT(promise->is()); + MOZ_ASSERT(thenable->is()); + + // Step 1 (Skipped). + + // Step 2. + // In difference to the usual pattern, we return immediately on success. + if (OriginalPromiseThenWithoutSettleHandlers(cx, thenable.as(), + promise.as())) + { + return true; + } + + // Steps 3-4. + RootedValue exception(cx); + if (!MaybeGetAndClearException(cx, &exception)) + return false; + + // Testing functions allow to directly settle a promise without going + // through the resolving functions. In that case the normal bookkeeping to + // ensure only pending promises can be resolved doesn't apply and we need + // to manually check for already settled promises. The exception is simply + // dropped when this case happens. + if (promise->as().state() != JS::PromiseState::Pending) + return true; + + return RejectMaybeWrappedPromise(cx, promise, exception); +} + /** * Tells the embedding to enqueue a Promise resolve thenable job, based on * three parameters: @@ -1295,7 +1567,7 @@ EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_, RootedObject then(cx, CheckedUnwrap(&thenVal.toObject())); AutoCompartment ac(cx, then); - RootedAtom funName(cx, cx->names().empty); + HandlePropertyName funName = cx->names().empty; RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!job) @@ -1306,7 +1578,7 @@ EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_, // Create a dense array to hold the data needed for the reaction job to // work. - // See the doc comment for PromiseResolveThenableJob for the layout. + // The layout is described in the ThenableJobDataIndices enum. RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataLength)); if (!data || data->ensureDenseElements(cx, 0, ThenableJobDataLength) != DenseElementResult::Success) @@ -1335,10 +1607,37 @@ EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_, return cx->runtime()->enqueuePromiseJob(cx, job, promise, incumbentGlobal); } +/** + * Tells the embedding to enqueue a Promise resolve thenable built-in job, + * based on two parameters: + * promiseToResolve - The promise to resolve, obviously. + * thenable - The thenable to resolve the Promise with. + */ static MOZ_MUST_USE bool -AddPromiseReaction(JSContext* cx, Handle promise, HandleValue onFulfilled, - HandleValue onRejected, HandleObject dependentPromise, - HandleObject resolve, HandleObject reject, HandleObject incumbentGlobal); +EnqueuePromiseResolveThenableBuiltinJob(JSContext* cx, HandleObject promiseToResolve, + HandleObject thenable) +{ + assertSameCompartment(cx, promiseToResolve, thenable); + MOZ_ASSERT(promiseToResolve->is()); + MOZ_ASSERT(thenable->is()); + + HandlePropertyName funName = cx->names().empty; + RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveBuiltinThenableJob, 0, funName, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); + if (!job) + return false; + + // Store the promise and the thenable on the reaction job. + job->setExtendedSlot(BuiltinThenableJobSlot_Promise, ObjectValue(*promiseToResolve)); + job->setExtendedSlot(BuiltinThenableJobSlot_Thenable, ObjectValue(*thenable)); + + RootedObject incumbentGlobal(cx, cx->runtime()->getIncumbentGlobal(cx)); + return cx->runtime()->enqueuePromiseJob(cx, job, promiseToResolve, incumbentGlobal); +} + +static MOZ_MUST_USE bool +AddDummyPromiseReactionForDebugger(JSContext* cx, Handle promise, + HandleObject dependentPromise); static MOZ_MUST_USE bool AddPromiseReaction(JSContext* cx, Handle promise, @@ -1389,6 +1688,11 @@ GetResolveFunctionFromPromise(PromiseObject* promise) if (rejectFun->maybeNative() != &RejectPromiseFunction) return nullptr; + // The reject function was already called and cleared its resolve-function + // extended slot. + if (rejectFun->getExtendedSlot(RejectFunctionSlot_ResolveFunction).isUndefined()) + return nullptr; + return GetResolveFunctionFromReject(rejectFun); } @@ -1565,28 +1869,28 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto / if (!promise) return nullptr; - RootedValue promiseVal(cx, ObjectValue(*promise)); - if (needsWrapping && !cx->compartment()->wrap(cx, &promiseVal)) + RootedObject promiseObj(cx, promise); + if (needsWrapping && !cx->compartment()->wrap(cx, &promiseObj)) return nullptr; // Step 8. // The resolving functions are created in the compartment active when the // (maybe wrapped) Promise constructor was called. They contain checks and // can unwrap the Promise if required. - RootedValue resolveVal(cx); - RootedValue rejectVal(cx); - if (!CreateResolvingFunctions(cx, promiseVal, &resolveVal, &rejectVal)) + RootedObject resolveFn(cx); + RootedObject rejectFn(cx); + if (!CreateResolvingFunctions(cx, promiseObj, &resolveFn, &rejectFn)) return nullptr; // Need to wrap the resolution functions before storing them on the Promise. if (needsWrapping) { AutoCompartment ac(cx, promise); - RootedValue wrappedRejectVal(cx, rejectVal); - if (!cx->compartment()->wrap(cx, &wrappedRejectVal)) + RootedObject wrappedRejectFn(cx, rejectFn); + if (!cx->compartment()->wrap(cx, &wrappedRejectFn)) return nullptr; - promise->setFixedSlot(PromiseSlot_RejectFunction, wrappedRejectVal); + promise->setFixedSlot(PromiseSlot_RejectFunction, ObjectValue(*wrappedRejectFn)); } else { - promise->setFixedSlot(PromiseSlot_RejectFunction, rejectVal); + promise->setFixedSlot(PromiseSlot_RejectFunction, ObjectValue(*rejectFn)); } // Step 9. @@ -1594,8 +1898,8 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto / { FixedInvokeArgs<2> args(cx); - args[0].set(resolveVal); - args[1].set(rejectVal); + args[0].setObject(*resolveFn); + args[1].setObject(*rejectFn); RootedValue calleeOrRval(cx, ObjectValue(*executor)); success = Call(cx, calleeOrRval, UndefinedHandleValue, args, &calleeOrRval); @@ -1611,8 +1915,8 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto / args[0].set(exceptionVal); - // |rejectVal| is unused after this, so we can safely write to it. - if (!Call(cx, rejectVal, UndefinedHandleValue, args, &rejectVal)) + RootedValue calleeOrRval(cx, ObjectValue(*rejectFn)); + if (!Call(cx, calleeOrRval, UndefinedHandleValue, args, &calleeOrRval)) return nullptr; } @@ -1747,6 +2051,12 @@ static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle promise, + Handle promiseToResolve, + HandleObject resultPromise, HandleObject resolve, + HandleObject reject); + static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); static bool PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); static bool PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp); @@ -2026,7 +2336,7 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // "resolve" on the canonical Promise constructor. RootedValue nextPromise(cx); RootedValue staticResolve(cx); - if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve)) + if (!GetProperty(cx, C, CVal, cx->names().resolve, &staticResolve)) return false; FixedInvokeArgs<1> resolveArgs(cx); @@ -2502,7 +2812,7 @@ PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // "resolve" on the canonical Promise constructor. RootedValue nextPromise(cx); RootedValue staticResolve(cx); - if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve)) + if (!GetProperty(cx, C, CVal, cx->names().resolve, &staticResolve)) return false; FixedInvokeArgs<1> resolveArgs(cx); @@ -2658,10 +2968,14 @@ Promise_static_species(JSContext* cx, unsigned argc, Value* vp) // ES2016, 25.4.5.1, implemented in Promise.js. +enum class IncumbentGlobalObject { + Yes, No +}; + static PromiseReactionRecord* NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfilled, HandleValue onRejected, HandleObject resolve, HandleObject reject, - HandleObject incumbentGlobalObject) + IncumbentGlobalObject incumbentGlobalObjectOption) { // Either of the following conditions must be met: // * resultPromise is a PromiseObject @@ -2672,6 +2986,27 @@ NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfi MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), reject); MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), IsCallable(reject)); + // Ensure the onFulfilled handler has the expected type. + MOZ_ASSERT(onFulfilled.isInt32() || onFulfilled.isObjectOrNull()); + MOZ_ASSERT_IF(onFulfilled.isObject(), IsCallable(onFulfilled)); + MOZ_ASSERT_IF(onFulfilled.isInt32(), + 0 <= onFulfilled.toInt32() && onFulfilled.toInt32() < PromiseHandlerLimit); + + // Ensure the onRejected handler has the expected type. + MOZ_ASSERT(onRejected.isInt32() || onRejected.isObjectOrNull()); + MOZ_ASSERT_IF(onRejected.isObject(), IsCallable(onRejected)); + MOZ_ASSERT_IF(onRejected.isInt32(), + 0 <= onRejected.toInt32() && onRejected.toInt32() < PromiseHandlerLimit); + + // Handlers must either both be present or both be absent. + MOZ_ASSERT(onFulfilled.isNull() == onRejected.isNull()); + + RootedObject incumbentGlobalObject(cx); + if (incumbentGlobalObjectOption == IncumbentGlobalObject::Yes) { + if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobalObject)) + return nullptr; + } + Rooted reaction(cx, NewObjectWithClassProto(cx)); if (!reaction) return nullptr; @@ -2701,6 +3036,30 @@ IsPromiseSpecies(JSContext* cx, JSFunction* species) return species->maybeNative() == Promise_static_species; } +static bool +PromiseThenNewPromiseCapability(JSContext* cx, HandleObject promiseObj, + CreateDependentPromise createDependent, + MutableHandleObject resultPromise, + MutableHandleObject resolve, MutableHandleObject reject) +{ + if (createDependent != CreateDependentPromise::Never) { + // Step 3. + RootedObject C(cx, SpeciesConstructor(cx, promiseObj, JSProto_Promise, IsPromiseSpecies)); + if (!C) + return false; + + if (createDependent == CreateDependentPromise::Always || + !IsNativeFunction(C, PromiseConstructor)) + { + // Step 4. + if (!NewPromiseCapability(cx, C, resultPromise, resolve, reject, true)) + return false; + } + } + + return true; +} + // ES2016, 25.4.5.3., steps 3-5. MOZ_MUST_USE bool js::OriginalPromiseThen(JSContext* cx, Handle promise, @@ -2717,20 +3076,10 @@ js::OriginalPromiseThen(JSContext* cx, Handle promise, RootedObject resolve(cx); RootedObject reject(cx); - if (createDependent != CreateDependentPromise::Never) { - // Step 3. - RootedObject C(cx, SpeciesConstructor(cx, promiseObj, JSProto_Promise, IsPromiseSpecies)); - if (!C) - return false; - - // Step 4. - if (createDependent == CreateDependentPromise::Always || - !IsNativeFunction(C, PromiseConstructor)) - { - // Step 4. - if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, true)) - return false; - } + if (!PromiseThenNewPromiseCapability(cx, promiseObj, createDependent, &resultPromise, + &resolve, &reject)) + { + return false; } // Step 5. @@ -2741,6 +3090,27 @@ js::OriginalPromiseThen(JSContext* cx, Handle promise, return true; } +static MOZ_MUST_USE bool +OriginalPromiseThenWithoutSettleHandlers(JSContext* cx, Handle promise, + Handle promiseToResolve) +{ + assertSameCompartment(cx, promise); + + // Steps 3-4. + RootedObject resultPromise(cx); + RootedObject resolve(cx); + RootedObject reject(cx); + if (!PromiseThenNewPromiseCapability(cx, promise, CreateDependentPromise::SkipIfCtorUnobservable, + &resultPromise, &resolve, &reject)) + { + return false; + } + + // Step 5. + return PerformPromiseThenWithoutSettleHandlers(cx, promise, promiseToResolve, resultPromise, + resolve, reject); +} + static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx, Handle promise, Handle reaction); @@ -2804,8 +3174,8 @@ static MOZ_MUST_USE bool InternalAwait(JSContext* cx, HandleValue value, HandleObject resultPromise, HandleValue onFulfilled, HandleValue onRejected, T extraStep) { - MOZ_ASSERT(onFulfilled.isNumber() || onFulfilled.isObject()); - MOZ_ASSERT(onRejected.isNumber() || onRejected.isObject()); + MOZ_ASSERT(onFulfilled.isInt32()); + MOZ_ASSERT(onRejected.isInt32()); // Step 2. Rooted promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); @@ -2816,15 +3186,11 @@ InternalAwait(JSContext* cx, HandleValue value, HandleObject resultPromise, if (!ResolvePromiseInternal(cx, promise, value)) return false; - RootedObject incumbentGlobal(cx); - if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal)) - return false; - // Steps 7-8. Rooted reaction(cx, NewReactionRecord(cx, resultPromise, onFulfilled, onRejected, nullptr, nullptr, - incumbentGlobal)); + IncumbentGlobalObject::Yes)); if (!reaction) return false; @@ -3269,10 +3635,6 @@ js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, return true; } -static bool Promise_then(JSContext* cx, unsigned argc, Value* vp); -static bool Promise_then_impl(JSContext* cx, HandleValue promiseVal, HandleValue onFulfilled, - HandleValue onRejected, MutableHandleValue rval, bool rvalUsed); - static bool Promise_catch_impl(JSContext* cx, unsigned argc, Value* vp, bool rvalUsed) { @@ -3420,18 +3782,42 @@ PerformPromiseThen(JSContext* cx, Handle promise, HandleValue on if (!IsCallable(onRejected)) onRejected = Int32Value(PromiseHandlerThrower); - RootedObject incumbentGlobal(cx); - if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal)) + // Step 7. + Rooted reaction(cx, NewReactionRecord(cx, resultPromise, + onFulfilled, onRejected, + resolve, reject, + IncumbentGlobalObject::Yes)); + if (!reaction) return false; + return PerformPromiseThenWithReaction(cx, promise, reaction); +} + +static MOZ_MUST_USE bool +PerformPromiseThenWithoutSettleHandlers(JSContext* cx, Handle promise, + Handle promiseToResolve, + HandleObject resultPromise, HandleObject resolve, + HandleObject reject) +{ + // Step 1 (implicit). + // Step 2 (implicit). + + // Step 3. + HandleValue onFulfilled = NullHandleValue; + + // Step 4. + HandleValue onRejected = NullHandleValue; + // Step 7. Rooted reaction(cx, NewReactionRecord(cx, resultPromise, onFulfilled, onRejected, resolve, reject, - incumbentGlobal)); + IncumbentGlobalObject::Yes)); if (!reaction) return false; + reaction->setIsDefaultResolvingHandler(promiseToResolve); + return PerformPromiseThenWithReaction(cx, promise, reaction); } @@ -3492,15 +3878,15 @@ static MOZ_MUST_USE bool BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromise_, HandleValue onFulfilled, HandleValue onRejected) { - RootedValue thenVal(cx); - if (!GetProperty(cx, promiseVal, cx->names().then, &thenVal)) + RootedObject promiseObj(cx, ToObject(cx, promiseVal)); + if (!promiseObj) return false; - RootedObject promiseObj(cx); - if (promiseVal.isObject()) - promiseObj = &promiseVal.toObject(); + RootedValue thenVal(cx); + if (!GetProperty(cx, promiseObj, promiseVal, cx->names().then, &thenVal)) + return false; - if (promiseObj && promiseObj->is() && IsNativeFunction(thenVal, Promise_then)) { + if (promiseObj->is() && IsNativeFunction(thenVal, Promise_then)) { // |promise| is an unwrapped Promise, and |then| is the original // |Promise.prototype.then|, inline it here. // 25.4.5.3., step 3. @@ -3550,7 +3936,7 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis // (potentially after unwrapping them), and non-object values can't be // Promise objects. This can happen if Promise.all is called on an object // with a `resolve` method that returns primitives. - if (!promiseObj) + if (!promiseVal.isObject()) return true; // The object created by the |promise.then| call or the inlined version @@ -3589,8 +3975,7 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis return true; Rooted promise(cx, &unwrappedPromiseObj->as()); - return AddPromiseReaction(cx, promise, UndefinedHandleValue, UndefinedHandleValue, - blockedPromise, nullptr, nullptr, nullptr); + return AddDummyPromiseReactionForDebugger(cx, promise, blockedPromise); } static MOZ_MUST_USE bool @@ -3661,19 +4046,21 @@ AddPromiseReaction(JSContext* cx, Handle promise, } static MOZ_MUST_USE bool -AddPromiseReaction(JSContext* cx, Handle promise, HandleValue onFulfilled, - HandleValue onRejected, HandleObject dependentPromise, - HandleObject resolve, HandleObject reject, HandleObject incumbentGlobal) +AddDummyPromiseReactionForDebugger(JSContext* cx, Handle promise, + HandleObject dependentPromise) { if (promise->state() != JS::PromiseState::Pending) return true; Rooted reaction(cx, NewReactionRecord(cx, dependentPromise, - onFulfilled, onRejected, - resolve, reject, - incumbentGlobal)); + NullHandleValue, NullHandleValue, + nullptr, nullptr, + IncumbentGlobalObject::No)); if (!reaction) return false; + + reaction->setIsDebuggerDummy(); + return AddPromiseReaction(cx, promise, reaction); } @@ -3760,6 +4147,9 @@ PromiseObject::resolve(JSContext* cx, Handle promise, HandleValu return ResolvePromiseInternal(cx, promise, resolutionValue); RootedObject resolveFun(cx, GetResolveFunctionFromPromise(promise)); + if (!resolveFun) + return true; + RootedValue funVal(cx, ObjectValue(*resolveFun)); // For xray'd Promises, the resolve fun may have been created in another diff --git a/js/src/jit-test/tests/debug/Promise-race-dependent-promises.js b/js/src/jit-test/tests/debug/Promise-race-dependent-promises.js new file mode 100644 index 0000000000..a2437c3e2d --- /dev/null +++ b/js/src/jit-test/tests/debug/Promise-race-dependent-promises.js @@ -0,0 +1,46 @@ +// Promise.race(...) may add a dummy PromiseReaction which is only used for the +// debugger. +// +// See BlockOnPromise when called from PerformPromiseRace for when this dummy +// reaction is created. + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +function test(withFastPath) { + g.eval(` + function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; + } + + var {promise: alwaysPending} = newPromiseCapability(); + + if (!${withFastPath}) { + // Disable the BlockOnPromise fast path by giving |alwaysPending| a + // non-default "then" function property. This will ensure the dummy + // reaction is created. + alwaysPending.then = function() {}; + } + + var result = Promise.race([alwaysPending]); + `); + + var alwaysPending = gw.makeDebuggeeValue(g.alwaysPending); + var result = gw.makeDebuggeeValue(g.result); + + assertEq(alwaysPending.promiseDependentPromises.length, 1); + assertEq(alwaysPending.promiseDependentPromises[0], result); + + assertEq(result.promiseDependentPromises.length, 0); +} + +// No dummy reaction created when the fast path is taken. +test(true); + +// Dummy reaction is created when we can't take the fast path. +test(false); diff --git a/js/src/jit-test/tests/promise/debugger-reaction-does-not-resolve.js b/js/src/jit-test/tests/promise/debugger-reaction-does-not-resolve.js new file mode 100644 index 0000000000..48aec08e26 --- /dev/null +++ b/js/src/jit-test/tests/promise/debugger-reaction-does-not-resolve.js @@ -0,0 +1,71 @@ +// Promise.race(...) may add a dummy PromiseReaction which is only used for the +// debugger. Ensure that this dummy reaction can't influence the normal Promise +// resolution behaviour. +// +// See BlockOnPromise when called from PerformPromiseRace for when this dummy +// reaction is created. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + +function neverCalled() { + // Quit with non-zero exit code to ensure a test suite error is shown, + // even when this function is called within promise handlers which normally + // swallow any exceptions. + quit(1); +} + +var c = 0; +var g_resolve; + +var resolvedValues = []; + +function resolveCapability(v) { + resolvedValues.push(v); +} + +class P extends Promise { + constructor(executor) { + // Only the very first object created through this constructor gets + // special treatment, all other invocations create built-in Promise + // objects. + if (c++ > 1) { + return new Promise(executor); + } + + executor(resolveCapability, neverCalled); + + var {promise, resolve} = newPromiseCapability(); + g_resolve = resolve; + + // Use an async function to create a Promise without resolving functions. + var p = async function(){ await promise; return 456; }(); + + // Ensure the species constructor is not the built-in Promise constructor + // to avoid falling into the fast path. + p.constructor = { + [Symbol.species]: P + }; + + return p; + } +} + +var {promise: alwaysPending} = newPromiseCapability(); + +// The promise returned from race() should never be resolved. +P.race([alwaysPending]).then(neverCalled, neverCalled); + +g_resolve(123); + +drainJobQueue(); + +// Check |resolvedValues| to ensure resolving functions were properly called. +assertEq(resolvedValues.length, 2); +assertEq(resolvedValues[0], alwaysPending); +assertEq(resolvedValues[1], 456); diff --git a/js/src/jit-test/tests/promise/resolve-promise-scripted-and-api.js b/js/src/jit-test/tests/promise/resolve-promise-scripted-and-api.js new file mode 100644 index 0000000000..d69a6d9167 --- /dev/null +++ b/js/src/jit-test/tests/promise/resolve-promise-scripted-and-api.js @@ -0,0 +1,15 @@ +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +resolve(Promise.resolve(0)); + +// Don't assert when the Promise was already resolved. +resolvePromise(promise, 123); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-1.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-1.js new file mode 100644 index 0000000000..940adf660d --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-1.js @@ -0,0 +1,21 @@ +// Test we don't assert when the promise is settled after enqueuing a PromiseReactionJob. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +var p = Promise.resolve(0); + +// Enqueue a PromiseResolveThenableJob followed by a PromiseReactionJob. +resolve(p); + +// The PromiseReactionJob expects a pending promise, but this settlePromiseNow +// call will already have settled the promise. +settlePromiseNow(promise); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-10.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-10.js new file mode 100644 index 0000000000..4d19966cba --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-10.js @@ -0,0 +1,18 @@ +// Don't assert when the promise in the resolving functions is wrapped in a CCW. + +function newPromiseCapability(newTarget) { + var resolve, reject, promise = Reflect.construct(Promise, [function(r1, r2) { + resolve = r1; + reject = r2; + }], newTarget); + return {promise, resolve, reject}; +} + +var g = newGlobal(); + +var {promise, resolve} = newPromiseCapability(g.Promise); + +g.settlePromiseNow(promise); + +// Don't assert when resolving the promise. +resolve(0); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-11.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-11.js new file mode 100644 index 0000000000..e4887b7267 --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-11.js @@ -0,0 +1,18 @@ +// Don't assert when the promise in the resolving functions is wrapped in a CCW. + +function newPromiseCapability(newTarget) { + var resolve, reject, promise = Reflect.construct(Promise, [function(r1, r2) { + resolve = r1; + reject = r2; + }], newTarget); + return {promise, resolve, reject}; +} + +var g = newGlobal(); + +var {promise, reject} = newPromiseCapability(g.Promise); + +g.settlePromiseNow(promise); + +// Don't assert when rejecting the promise. +reject(0); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-2.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-2.js new file mode 100644 index 0000000000..79129003e3 --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-2.js @@ -0,0 +1,27 @@ +// Test we don't assert when the promise is settled and the SpeciesConstructor +// call in Promise.prototype.then throws an exception. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +var p = Promise.resolve(0); + +p.constructor = { + [Symbol.species]: function() { + throw new Error(); + } +}; + +// Enqueue a PromiseResolveThenableJob. +resolve(p); + +// Settle the promise after the resolve call. +settlePromiseNow(promise); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-3.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-3.js new file mode 100644 index 0000000000..591eba456f --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-3.js @@ -0,0 +1,27 @@ +// Test we don't assert when the promise is settled and the SpeciesConstructor +// call in Promise.prototype.then throws an exception. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +var p = Promise.resolve(0); + +p.constructor = { + [Symbol.species]: function() { + // Settle the promise in the SpeciesConstructor call. + settlePromiseNow(promise); + + throw new Error(); + } +}; + +// Enqueue a PromiseResolveThenableJob. +resolve(p); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-4.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-4.js new file mode 100644 index 0000000000..a9d147ccaa --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-4.js @@ -0,0 +1,18 @@ +// Test we don't assert when the promise is settled and we then try to call the +// resolving function. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +settlePromiseNow(promise); + +// Don't assert when the promise is already settled. +resolve(0); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-5.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-5.js new file mode 100644 index 0000000000..e5d2ceaafe --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-5.js @@ -0,0 +1,18 @@ +// Test we don't assert when the promise is settled and we then try to call the +// rejecting function. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, reject} = newPromiseCapability(); + +settlePromiseNow(promise); + +// Don't assert when the promise is already settled. +reject(0); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-6.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-6.js new file mode 100644 index 0000000000..d694f849a6 --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-6.js @@ -0,0 +1,20 @@ +// Don't assert when a side-effect when getting the "then" property settled the promise. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +var thenable = { + get then() { + settlePromiseNow(promise); + } +}; + +resolve(thenable); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-7.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-7.js new file mode 100644 index 0000000000..d6920f9b79 --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-7.js @@ -0,0 +1,23 @@ +// Don't assert when a side-effect when getting the "then" property settled the promise. + +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve} = newPromiseCapability(); + +var thenable = { + get then() { + settlePromiseNow(promise); + + // Throw an error to reject the promise. + throw new Error(); + } +}; + +resolve(thenable); diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-8.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-8.js new file mode 100644 index 0000000000..8b581b7725 --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-8.js @@ -0,0 +1,7 @@ +// Don't assert when settlePromiseNow() is called on an async-function promise. + +var promise = async function(){ await 0; }(); + +try { + settlePromiseNow(promise); +} catch {} diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-9.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-9.js new file mode 100644 index 0000000000..45413d768c --- /dev/null +++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-9.js @@ -0,0 +1,15 @@ +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + + +var {promise, resolve, reject} = newPromiseCapability(); + +settlePromiseNow(promise); + +assertEq(resolve(0), undefined); +assertEq(reject(0), undefined); diff --git a/js/src/jit-test/tests/realms/promise-then.js b/js/src/jit-test/tests/realms/promise-then.js new file mode 100644 index 0000000000..4242239a03 --- /dev/null +++ b/js/src/jit-test/tests/realms/promise-then.js @@ -0,0 +1,30 @@ +load(libdir + "asserts.js"); + +const g = newGlobal({sameCompartmentAs: this}); + +let resolve, reject; +let promise = new Promise((resolveFn, rejectFn) => { + resolve = resolveFn; + reject = rejectFn; +}); + +// Set to a built-in Promise.prototype.then function, but from a different realm. +promise.then = g.Promise.prototype.then; + +// Make SpeciesConstructor throw a TypeError exception. +promise.constructor = { + [Symbol.species]: "not a constructor" +}; + +async function f(p) { + await p; +} + +let error; +f(promise).catch(e => { error = e; }); + +resolve(promise); + +drainJobQueue(); + +assertEq(error.constructor === g.TypeError, true); diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index a12255c636..2430c72846 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -721,8 +721,8 @@ JSRuntime::enqueuePromiseJob(JSContext* cx, HandleFunction job, HandleObject pro if (promise) { RootedObject unwrappedPromise(cx, promise); // While the job object is guaranteed to be unwrapped, the promise - // might be wrapped. See the comments in - // intrinsic_EnqueuePromiseReactionJob for details. + // might be wrapped. See the comments in EnqueuePromiseReactionJob in + // builtin/Promise.cpp for details. if (IsWrapper(promise)) unwrappedPromise = UncheckedUnwrap(promise); if (unwrappedPromise->is()) From ee395231dd413f157143f70305a48a80c359e687 Mon Sep 17 00:00:00 2001 From: Martok Date: Mon, 23 Jan 2023 00:13:31 +0100 Subject: [PATCH 14/21] Issue #2089 - Don't use BlockOnPromise fast path with non-default resolving functions, combine default resolving functions flags Based-on: m-c 1474348 --- js/src/builtin/Promise.cpp | 75 ++++++++++++------- js/src/builtin/Promise.h | 20 +++-- js/src/builtin/TestingFunctions.cpp | 4 +- ...se-race-with-default-resolving-internal.js | 54 +++++++++++++ ...promise-race-with-non-default-resolving.js | 56 ++++++++++++++ 5 files changed, 174 insertions(+), 35 deletions(-) create mode 100644 js/src/jit-test/tests/promise/promise-race-with-default-resolving-internal.js create mode 100644 js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index a6f7a29d10..133f18db86 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -540,14 +540,14 @@ const Class PromiseReactionRecord::class_ = { static void AddPromiseFlags(PromiseObject& promise, int32_t flag) { - int32_t flags = promise.getFixedSlot(PromiseSlot_Flags).toInt32(); + int32_t flags = promise.flags(); promise.setFixedSlot(PromiseSlot_Flags, Int32Value(flags | flag)); } static bool PromiseHasAnyFlag(PromiseObject& promise, int32_t flag) { - return promise.getFixedSlot(PromiseSlot_Flags).toInt32() & flag; + return promise.flags() & flag; } static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp); @@ -922,7 +922,7 @@ ResolvePromise(JSContext* cx, Handle promise, HandleValue valueO // instead of getting the right list of reactions, we determine the // resolution type to retrieve the right information from the // reaction records. - RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult)); + RootedValue reactionsVal(cx, promise->reactions()); // Steps 3-5. // The same slot is used for the reactions list and the result, so setting @@ -930,7 +930,7 @@ ResolvePromise(JSContext* cx, Handle promise, HandleValue valueO promise->setFixedSlot(PromiseSlot_ReactionsOrResult, valueOrReason); // Step 6. - int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32(); + int32_t flags = promise->flags(); flags |= PROMISE_FLAG_RESOLVED; if (state == JS::PromiseState::Fulfilled) flags |= PROMISE_FLAG_FULFILLED; @@ -994,8 +994,7 @@ CreatePromiseObjectWithoutResolutionFunctions(JSContext* cx) if (!promise) return nullptr; - AddPromiseFlags(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION | - PROMISE_FLAG_DEFAULT_REJECT_FUNCTION); + AddPromiseFlags(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS); return promise; } @@ -1643,9 +1642,9 @@ static MOZ_MUST_USE bool AddPromiseReaction(JSContext* cx, Handle promise, Handle reaction); -static MOZ_MUST_USE bool BlockOnPromise(JSContext* cx, HandleValue promise, - HandleObject blockedPromise, - HandleValue onFulfilled, HandleValue onRejected); +static MOZ_MUST_USE bool +BlockOnPromise(JSContext* cx, HandleValue promise, HandleObject blockedPromise, + HandleValue onFulfilled, HandleValue onRejected, bool onFulfilledReturnsUndefined); static JSFunction* GetResolveFunctionFromReject(JSFunction* reject) @@ -2210,14 +2209,12 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res if (promise->state() != JS::PromiseState::Pending) return true; - if (mode == ResolveMode) { - if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION)) - return true; - return ResolvePromiseInternal(cx, promise, result); - } - - if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_REJECT_FUNCTION)) + if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) return true; + + if (mode == ResolveMode) + return ResolvePromiseInternal(cx, promise, result); + return RejectMaybeWrappedPromise(cx, promiseObj, result); } @@ -2364,7 +2361,7 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // Step q. RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc)); - if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal)) + if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal, true)) return false; // Step r. @@ -2479,7 +2476,7 @@ PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObjec RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc)); RootedValue rejectFunVal(cx, ObjectValue(*rejectFunc)); - if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal)) + if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal, true)) return false; index++; @@ -2785,6 +2782,11 @@ PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, MOZ_ASSERT(C->isConstructor()); RootedValue CVal(cx, ObjectValue(*C)); + // BlockOnPromise fast path requires the passed onFulfilled function + // doesn't return an object value, because otherwise the skipped promise + // creation is detectable due to missing property lookups. + bool isDefaultResolveFn = IsNativeFunction(resolve, ResolvePromiseFunction); + RootedValue nextValue(cx); RootedValue resolveFunVal(cx, ObjectValue(*resolve)); RootedValue rejectFunVal(cx, ObjectValue(*reject)); @@ -2821,8 +2823,11 @@ PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, return false; // Step i. - if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal)) + if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal, + isDefaultResolveFn)) + { return false; + } } MOZ_ASSERT_UNREACHABLE("Shouldn't reach the end of PerformPromiseRace"); @@ -3826,7 +3831,7 @@ PerformPromiseThenWithReaction(JSContext* cx, Handle promise, Handle reaction) { JS::PromiseState state = promise->state(); - int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32(); + int32_t flags = promise->flags(); if (state == JS::PromiseState::Pending) { // Steps 5,6 (reordered). // Instead of creating separate reaction records for fulfillment and @@ -3842,7 +3847,7 @@ PerformPromiseThenWithReaction(JSContext* cx, Handle promise, MOZ_ASSERT_IF(state != JS::PromiseState::Fulfilled, state == JS::PromiseState::Rejected); // Step 8.a. / 9.b. - RootedValue valueOrReason(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult)); + RootedValue valueOrReason(cx, promise->valueOrReason()); // We might be operating on a promise from another compartment. In // that case, we need to wrap the result/reason value before using it. @@ -3876,7 +3881,7 @@ PerformPromiseThenWithReaction(JSContext* cx, Handle promise, */ static MOZ_MUST_USE bool BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromise_, - HandleValue onFulfilled, HandleValue onRejected) + HandleValue onFulfilled, HandleValue onRejected, bool onFulfilledReturnsUndefined) { RootedObject promiseObj(cx, ToObject(cx, promiseVal)); if (!promiseObj) @@ -3898,7 +3903,7 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis if (!C) return false; - RootedObject resultPromise(cx, blockedPromise_); + RootedObject resultPromise(cx); RootedObject resolveFun(cx); RootedObject rejectFun(cx); @@ -3906,7 +3911,21 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis // rejected promises list. bool addToDependent = true; - if (C == PromiseCtor && resultPromise->is()) { + // Skip the creation of a built-in Promise object if: + // 1. `C` is the built-in Promise constructor. + // 2. The `onFulfilled` handler doesn't return an object, which + // ensures no side-effects take place in ResolvePromiseInternal. + // 3. The blocked promise is a built-in Promise object. + // 4. The blocked promise doesn't use the default resolving functions, + // which in turn means RunResolutionFunction when called from + // PromiseRectionJob won't try to resolve the promise. + if (C == PromiseCtor && + onFulfilledReturnsUndefined && + blockedPromise_->is() && + !PromiseHasAnyFlag(blockedPromise_->as(), + PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) + { + resultPromise.set(blockedPromise_); addToDependent = false; } else { // 25.4.5.3., step 4. @@ -3997,7 +4016,7 @@ AddPromiseReaction(JSContext* cx, Handle promise, } // 25.4.5.3.1 steps 7.a,b. - RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult)); + RootedValue reactionsVal(cx, promise->reactions()); RootedNativeObject reactions(cx); if (reactionsVal.isUndefined()) { @@ -4093,7 +4112,7 @@ PromiseObject::dependentPromises(JSContext* cx, MutableHandle> v if (state() != JS::PromiseState::Pending) return true; - RootedValue reactionsVal(cx, getFixedSlot(PromiseSlot_ReactionsOrResult)); + RootedValue reactionsVal(cx, reactions()); // If no reactions are pending, we don't have list and are done. if (reactionsVal.isNullOrUndefined()) @@ -4143,7 +4162,7 @@ PromiseObject::resolve(JSContext* cx, Handle promise, HandleValu if (promise->state() != JS::PromiseState::Pending) return true; - if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION)) + if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) return ResolvePromiseInternal(cx, promise, resolutionValue); RootedObject resolveFun(cx, GetResolveFunctionFromPromise(promise)); @@ -4172,7 +4191,7 @@ PromiseObject::reject(JSContext* cx, Handle promise, HandleValue if (promise->state() != JS::PromiseState::Pending) return true; - if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_REJECT_FUNCTION)) + if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) return RejectMaybeWrappedPromise(cx, promise, rejectionValue); RootedValue funVal(cx, promise->getFixedSlot(PromiseSlot_RejectFunction)); diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h index 811e8efb94..1fe9b060ad 100644 --- a/js/src/builtin/Promise.h +++ b/js/src/builtin/Promise.h @@ -24,9 +24,8 @@ enum PromiseSlots { #define PROMISE_FLAG_FULFILLED 0x2 #define PROMISE_FLAG_HANDLED 0x4 #define PROMISE_FLAG_REPORTED 0x8 -#define PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION 0x10 -#define PROMISE_FLAG_DEFAULT_REJECT_FUNCTION 0x20 -#define PROMISE_FLAG_ASYNC 0x40 +#define PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS 0x10 +#define PROMISE_FLAG_ASYNC 0x20 class AutoSetNewObjectMetadata; @@ -44,8 +43,11 @@ class PromiseObject : public NativeObject static JSObject* unforgeableResolve(JSContext* cx, HandleValue value); static JSObject* unforgeableReject(JSContext* cx, HandleValue value); + int32_t flags() { + return getFixedSlot(PromiseSlot_Flags).toInt32(); + } JS::PromiseState state() { - int32_t flags = getFixedSlot(PromiseSlot_Flags).toInt32(); + int32_t flags = this->flags(); if (!(flags & PROMISE_FLAG_RESOLVED)) { MOZ_ASSERT(!(flags & PROMISE_FLAG_FULFILLED)); return JS::PromiseState::Pending; @@ -54,6 +56,10 @@ class PromiseObject : public NativeObject return JS::PromiseState::Fulfilled; return JS::PromiseState::Rejected; } + Value reactions() { + MOZ_ASSERT(state() == JS::PromiseState::Pending); + return getFixedSlot(PromiseSlot_ReactionsOrResult); + } Value value() { MOZ_ASSERT(state() == JS::PromiseState::Fulfilled); return getFixedSlot(PromiseSlot_ReactionsOrResult); @@ -62,6 +68,10 @@ class PromiseObject : public NativeObject MOZ_ASSERT(state() == JS::PromiseState::Rejected); return getFixedSlot(PromiseSlot_ReactionsOrResult); } + Value valueOrReason() { + MOZ_ASSERT(state() != JS::PromiseState::Pending); + return getFixedSlot(PromiseSlot_ReactionsOrResult); + } static MOZ_MUST_USE bool resolve(JSContext* cx, Handle promise, HandleValue resolutionValue); @@ -83,7 +93,7 @@ class PromiseObject : public NativeObject uint64_t getID(); bool isUnhandled() { MOZ_ASSERT(state() == JS::PromiseState::Rejected); - return !(getFixedSlot(PromiseSlot_Flags).toInt32() & PROMISE_FLAG_HANDLED); + return !(flags() & PROMISE_FLAG_HANDLED); } void markAsReported() { MOZ_ASSERT(isUnhandled()); diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 914d3b989b..95c724bf90 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1334,8 +1334,8 @@ SettlePromiseNow(JSContext* cx, unsigned argc, Value* vp) return false; } - RootedNativeObject promise(cx, &args[0].toObject().as()); - int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32(); + Rooted promise(cx, &args[0].toObject().as()); + int32_t flags = promise->flags(); promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_RESOLVED | PROMISE_FLAG_FULFILLED)); promise->setFixedSlot(PromiseSlot_ReactionsOrResult, UndefinedValue()); diff --git a/js/src/jit-test/tests/promise/promise-race-with-default-resolving-internal.js b/js/src/jit-test/tests/promise/promise-race-with-default-resolving-internal.js new file mode 100644 index 0000000000..815124c46f --- /dev/null +++ b/js/src/jit-test/tests/promise/promise-race-with-default-resolving-internal.js @@ -0,0 +1,54 @@ +function newPromiseCapability() { + let resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + +function neverCalled() { + // Quit with non-zero exit code to ensure a test suite error is shown, + // even when this function is called within promise handlers which normally + // swallow any exceptions. + quit(1); +} + +var c = 0; +var g_resolve; + +class P extends Promise { + constructor(executor) { + // Only the very first object created through this constructor gets + // special treatment, all other invocations create built-in Promise + // objects. + if (c++ > 1) { + return new Promise(executor); + } + + // Pass a native ResolvePromiseFunction function as the resolve handler. + // (It's okay that the promise of this promise capability is never used.) + executor(newPromiseCapability().resolve, neverCalled); + + let {promise, resolve} = newPromiseCapability(); + g_resolve = resolve; + + // Use an async function to create a Promise without resolving functions. + return async function(){ await promise; return 456; }(); + } + + // Ensure we don't take the (spec) fast path in Promise.resolve and instead + // create a new promise object. (We could not provide an override at all + // and rely on the default behaviour, but giving an explicit definition + // may help to interpret this test case.) + static resolve(v) { + return super.resolve(v); + } +} + +let {promise: alwaysPending} = newPromiseCapability(); + +P.race([alwaysPending]).then(neverCalled, neverCalled); + +g_resolve(123); + +drainJobQueue(); diff --git a/js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js b/js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js new file mode 100644 index 0000000000..23d4b95bc3 --- /dev/null +++ b/js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js @@ -0,0 +1,56 @@ +function newPromiseCapability() { + var resolve, reject, promise = new Promise(function(r1, r2) { + resolve = r1; + reject = r2; + }); + return {promise, resolve, reject}; +} + +function neverCalled() { + // Quit with non-zero exit code to ensure a test suite error is shown, + // even when this function is called within promise handlers which normally + // swallow any exceptions. + quit(1); +} + +var {promise, resolve} = newPromiseCapability(); + +var getterCount = 0; + +class P extends Promise { + constructor(executor) { + var {promise, resolve, reject} = newPromiseCapability(); + + executor(function(v) { + // Resolve the promise. + resolve(v); + + // But then return an object from the resolve function. This object + // must be treated as the resolution value for the otherwise + // skipped promise which gets created when Promise.prototype.then is + // called in PerformPromiseRace. + return { + get then() { + getterCount++; + } + }; + }, neverCalled); + + return promise; + } + + // Default to the standard Promise.resolve function, so we don't create + // another instance of this class when resolving the passed promise objects + // in Promise.race. + static resolve(v) { + return Promise.resolve(v); + } +} + +P.race([promise]); + +resolve(0); + +drainJobQueue(); + +assertEq(getterCount, 1); From 1fd51e14f388eab018de0cbe3b859e9217719b81 Mon Sep 17 00:00:00 2001 From: Martok Date: Mon, 23 Jan 2023 00:19:20 +0100 Subject: [PATCH 15/21] Issue #2089 - Improve Runtime perf of Promises - reduce typecasting - reduce rooting already rooted values - directly handle unwrapped promises Based-on: m-c 1475678/{1-7} --- js/src/builtin/Promise.cpp | 383 +++++++++++++++++++---------------- js/src/vm/AsyncIteration.cpp | 4 +- js/src/vm/AsyncIteration.h | 12 +- 3 files changed, 213 insertions(+), 186 deletions(-) diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 133f18db86..75024c8a7e 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -162,7 +162,7 @@ static PromiseAllDataHolder* NewPromiseAllDataHolder(JSContext* cx, HandleObject resultPromise, HandleValue valuesArray, HandleObject resolve) { - Rooted dataHolder(cx, NewObjectWithClassProto(cx)); + PromiseAllDataHolder* dataHolder = NewObjectWithClassProto(cx); if (!dataHolder) return nullptr; @@ -436,19 +436,19 @@ enum ReactionRecordSlots { // ES2016, 25.4.1.2. class PromiseReactionRecord : public NativeObject { - static constexpr size_t REACTION_FLAG_RESOLVED = 0x1; - static constexpr size_t REACTION_FLAG_FULFILLED = 0x2; - static constexpr size_t REACTION_FLAG_DEFAULT_RESOLVING_HANDLER = 0x4; - static constexpr size_t REACTION_FLAG_ASYNC_FUNCTION = 0x8; - static constexpr size_t REACTION_FLAG_ASYNC_GENERATOR = 0x10; - static constexpr size_t REACTION_FLAG_DEBUGGER_DUMMY = 0x20; + static constexpr uint32_t REACTION_FLAG_RESOLVED = 0x1; + static constexpr uint32_t REACTION_FLAG_FULFILLED = 0x2; + static constexpr uint32_t REACTION_FLAG_DEFAULT_RESOLVING_HANDLER = 0x4; + static constexpr uint32_t REACTION_FLAG_ASYNC_FUNCTION = 0x8; + static constexpr uint32_t REACTION_FLAG_ASYNC_GENERATOR = 0x10; + static constexpr uint32_t REACTION_FLAG_DEBUGGER_DUMMY = 0x20; - void setFlagOnInitialState(size_t flag) { - int32_t flags = this->flags(); - MOZ_ASSERT(flags == 0, "Can't modify with non-default flags"); - flags |= flag; - setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); - } + void setFlagOnInitialState(uint32_t flag) { + int32_t flags = this->flags(); + MOZ_ASSERT(flags == 0, "Can't modify with non-default flags"); + flags |= flag; + setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); + } public: static const Class class_; @@ -598,8 +598,13 @@ IsSettledMaybeWrappedPromise(JSObject* promise) return promise->as().state() != JS::PromiseState::Pending; } -static MOZ_MUST_USE bool RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, - HandleValue reason); +// ES2016, 25.4.1.7. +static MOZ_MUST_USE bool +RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue reason); + +// ES2016, 25.4.1.7. +static MOZ_MUST_USE bool +RejectPromiseInternal(JSContext* cx, Handle promise, HandleValue reason); // ES2016, 25.4.1.3.1. static bool @@ -607,11 +612,11 @@ RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - RootedFunction reject(cx, &args.callee().as()); - RootedValue reasonVal(cx, args.get(0)); + JSFunction* reject = &args.callee().as(); + HandleValue reasonVal = args.get(0); // Steps 1-2. - RootedValue promiseVal(cx, reject->getExtendedSlot(RejectFunctionSlot_Promise)); + const Value& promiseVal = reject->getExtendedSlot(RejectFunctionSlot_Promise); // Steps 3-4. // If the Promise isn't available anymore, it has been resolved and the @@ -621,13 +626,15 @@ RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp) return true; } + // Store the promise value in |promise| before ClearResolutionFunctionSlots + // removes the reference. + RootedObject promise(cx, &promiseVal.toObject()); + // Step 5. // Here, we only remove the Promise reference from the resolution // functions. Actually marking it as fulfilled/rejected happens later. ClearResolutionFunctionSlots(reject); - RootedObject promise(cx, &promiseVal.toObject()); - // In some cases the Promise reference on the resolution function won't // have been removed during resolution, so we need to check that here, // too. @@ -749,8 +756,8 @@ ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - RootedFunction resolve(cx, &args.callee().as()); - RootedValue resolutionVal(cx, args.get(0)); + JSFunction* resolve = &args.callee().as(); + HandleValue resolutionVal = args.get(0); // Steps 3-4 (reordered). // We use the reference to the reject function as a signal for whether @@ -813,14 +820,15 @@ EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj, MOZ_RELEASE_ASSERT(reactionObj->is()); reaction = &reactionObj->as(); } else { - if (JS_IsDeadWrapper(UncheckedUnwrap(reactionObj))) { + JSObject* unwrappedReactionObj = UncheckedUnwrap(reactionObj); + if (JS_IsDeadWrapper(unwrappedReactionObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } - reaction = &UncheckedUnwrap(reactionObj)->as(); + reaction = &unwrappedReactionObj->as(); MOZ_RELEASE_ASSERT(reaction->is()); ac.emplace(cx, reaction); - if (!reaction->compartment()->wrap(cx, &handlerArg)) + if (!cx->compartment()->wrap(cx, &handlerArg)) return false; } @@ -842,13 +850,11 @@ EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj, // from said global. mozilla::Maybe ac2; if (handler.isObject()) { - RootedObject handlerObj(cx, &handler.toObject()); - // The unwrapping has to be unchecked because we specifically want to // be able to use handlers with wrappers that would only allow calls. // E.g., it's ok to have a handler from a chrome compartment in a // reaction to a content compartment's Promise instance. - handlerObj = UncheckedUnwrap(handlerObj); + JSObject* handlerObj = UncheckedUnwrap(&handler.toObject()); MOZ_ASSERT(handlerObj); ac2.emplace(cx, handlerObj); @@ -890,8 +896,7 @@ EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj, // much better than having to store the original global as a private value // because we couldn't wrap it to store it as a normal JS value. RootedObject global(cx); - RootedObject objectFromIncumbentGlobal(cx, reaction->incumbentGlobalObject()); - if (objectFromIncumbentGlobal) { + if (JSObject* objectFromIncumbentGlobal = reaction->incumbentGlobalObject()) { objectFromIncumbentGlobal = CheckedUnwrap(objectFromIncumbentGlobal); MOZ_ASSERT(objectFromIncumbentGlobal); global = &objectFromIncumbentGlobal->global(); @@ -951,6 +956,13 @@ ResolvePromise(JSContext* cx, Handle promise, HandleValue valueO return true; } +// ES2016, 25.4.1.7. +static MOZ_MUST_USE bool +RejectPromiseInternal(JSContext* cx, Handle promise, HandleValue reason) +{ + return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected); +} + // ES2016, 25.4.1.4. static MOZ_MUST_USE bool FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue value_) @@ -962,13 +974,14 @@ FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue v if (!IsProxy(promiseObj)) { promise = &promiseObj->as(); } else { - if (JS_IsDeadWrapper(UncheckedUnwrap(promiseObj))) { + JSObject* unwrappedPromiseObj = UncheckedUnwrap(promiseObj); + if (JS_IsDeadWrapper(unwrappedPromiseObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } - promise = &UncheckedUnwrap(promiseObj)->as(); + promise = &unwrappedPromiseObj->as(); ac.emplace(cx, promise); - if (!promise->compartment()->wrap(cx, &value)) + if (!cx->compartment()->wrap(cx, &value)) return false; } @@ -1079,7 +1092,7 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, return false; // Step 7. - RootedValue resolveVal(cx, executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve)); + const Value& resolveVal = executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve); if (!IsCallable(resolveVal)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE); @@ -1087,7 +1100,7 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, } // Step 8. - RootedValue rejectVal(cx, executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject)); + const Value& rejectVal = executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject); if (!IsCallable(rejectVal)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE); @@ -1107,7 +1120,7 @@ static bool GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - RootedFunction F(cx, &args.callee().as()); + JSFunction* F = &args.callee().as(); // Steps 1-2 (implicit). @@ -1142,11 +1155,12 @@ RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue re if (!IsProxy(promiseObj)) { promise = &promiseObj->as(); } else { - if (JS_IsDeadWrapper(UncheckedUnwrap(promiseObj))) { + JSObject* unwrappedPromiseObj = UncheckedUnwrap(promiseObj); + if (JS_IsDeadWrapper(unwrappedPromiseObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } - promise = &UncheckedUnwrap(promiseObj)->as(); + promise = &unwrappedPromiseObj->as(); ac.emplace(cx, promise); // The rejection reason might've been created in a compartment with higher @@ -1156,7 +1170,7 @@ RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue re // avoid that situation, we synthesize a generic error that doesn't // expose any privileged information but can safely be used in the // rejection handler. - if (!promise->compartment()->wrap(cx, &reason)) + if (!cx->compartment()->wrap(cx, &reason)) return false; if (reason.isObject() && !CheckedUnwrap(&reason.toObject())) { // Async stacks are only properly adopted if there's at least one @@ -1179,17 +1193,19 @@ TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseStat MOZ_ASSERT(state == JS::PromiseState::Fulfilled || state == JS::PromiseState::Rejected); RootedObject reactions(cx, &reactionsVal.toObject()); - RootedObject reaction(cx); if (reactions->is() || IsWrapper(reactions)) return EnqueuePromiseReactionJob(cx, reactions, valueOrReason, state); - RootedNativeObject reactionsList(cx, &reactions->as()); - size_t reactionsCount = reactionsList->getDenseInitializedLength(); + HandleNativeObject reactionsList = reactions.as(); + uint32_t reactionsCount = reactionsList->getDenseInitializedLength(); MOZ_ASSERT(reactionsCount > 1, "Reactions list should be created lazily"); - for (size_t i = 0; i < reactionsCount; i++) { - reaction = &reactionsList->getDenseElement(i).toObject(); + RootedObject reaction(cx); + for (uint32_t i = 0; i < reactionsCount; i++) { + const Value& reactionVal = reactionsList->getDenseElement(i); + MOZ_RELEASE_ASSERT(reactionVal.isObject()); + reaction = &reactionVal.toObject(); if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state)) return false; } @@ -1223,7 +1239,7 @@ DefaultResolvingPromiseReactionJob(JSContext* cx, Handle if (reaction->targetState() == JS::PromiseState::Fulfilled) ok = ResolvePromiseInternal(cx, promiseToResolve, argument); else - ok = RejectMaybeWrappedPromise(cx, promiseToResolve, argument); + ok = RejectPromiseInternal(cx, promiseToResolve, argument); if (!ok) { resolutionMode = RejectMode; @@ -1233,9 +1249,9 @@ DefaultResolvingPromiseReactionJob(JSContext* cx, Handle } // Steps 7-9. - size_t hookSlot = resolutionMode == RejectMode - ? ReactionRecordSlot_Reject - : ReactionRecordSlot_Resolve; + uint32_t hookSlot = resolutionMode == RejectMode + ? ReactionRecordSlot_Reject + : ReactionRecordSlot_Resolve; RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull()); RootedObject promiseObj(cx, reaction->promise()); if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode, promiseObj)) @@ -1364,7 +1380,7 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) } // Steps 1-2. - Rooted reaction(cx, &reactionObj->as()); + Handle reaction = reactionObj.as(); if (reaction->isDefaultResolvingHandler()) return DefaultResolvingPromiseReactionJob(cx, reaction, args.rval()); if (reaction->isAsyncFunction()) @@ -1399,7 +1415,7 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) bool done = handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone; // Async Iteration proposal 11.1.3.2.5 step 1. - RootedObject resultObj(cx, CreateIterResultObject(cx, argument, done)); + JSObject* resultObj = CreateIterResultObject(cx, argument, done); if (!resultObj) return false; @@ -1420,9 +1436,9 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) } // Steps 7-9. - size_t hookSlot = resolutionMode == RejectMode - ? ReactionRecordSlot_Reject - : ReactionRecordSlot_Resolve; + uint32_t hookSlot = resolutionMode == RejectMode + ? ReactionRecordSlot_Reject + : ReactionRecordSlot_Resolve; RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull()); RootedObject promiseObj(cx, reaction->promise()); if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode, promiseObj)) @@ -1540,7 +1556,7 @@ PromiseResolveBuiltinThenableJob(JSContext* cx, unsigned argc, Value* vp) if (promise->as().state() != JS::PromiseState::Pending) return true; - return RejectMaybeWrappedPromise(cx, promise, exception); + return RejectPromiseInternal(cx, promise.as(), exception); } /** @@ -1566,6 +1582,14 @@ EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_, RootedObject then(cx, CheckedUnwrap(&thenVal.toObject())); AutoCompartment ac(cx, then); + // Wrap the `promiseToResolve` and `thenable` arguments. + if (!cx->compartment()->wrap(cx, &promiseToResolve)) + return false; + + MOZ_ASSERT(thenable.isObject()); + if (!cx->compartment()->wrap(cx, &thenable)) + return false; + HandlePropertyName funName = cx->names().empty; RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); @@ -1579,29 +1603,21 @@ EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_, // work. // The layout is described in the ThenableJobDataIndices enum. RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataLength)); - if (!data || - data->ensureDenseElements(cx, 0, ThenableJobDataLength) != DenseElementResult::Success) - { + if (!data) return false; - } - // Wrap and set the `promiseToResolve` argument. - if (!cx->compartment()->wrap(cx, &promiseToResolve)) - return false; - data->setDenseElement(ThenableJobDataIndex_Promise, promiseToResolve); - // At this point the promise is guaranteed to be wrapped into the job's - // compartment. - RootedObject promise(cx, &promiseToResolve.toObject()); - - // Wrap and set the `thenable` argument. - MOZ_ASSERT(thenable.isObject()); - if (!cx->compartment()->wrap(cx, &thenable)) - return false; - data->setDenseElement(ThenableJobDataIndex_Thenable, thenable); + // Set the `promiseToResolve` and `thenable` arguments. + data->setDenseInitializedLength(ThenableJobDataLength); + data->initDenseElement(ThenableJobDataIndex_Promise, promiseToResolve); + data->initDenseElement(ThenableJobDataIndex_Thenable, thenable); // Store the data array on the reaction job. job->setExtendedSlot(ThenableJobSlot_JobData, ObjectValue(*data)); + // At this point the promise is guaranteed to be wrapped into the job's + // compartment. + RootedObject promise(cx, &promiseToResolve.toObject()); + RootedObject incumbentGlobal(cx, cx->runtime()->getIncumbentGlobal(cx)); return cx->runtime()->enqueuePromiseJob(cx, job, promise, incumbentGlobal); } @@ -1771,7 +1787,7 @@ PromiseConstructor(JSContext* cx, unsigned argc, Value* vp) return false; // Step 2. - RootedValue executorVal(cx, args.get(0)); + HandleValue executorVal = args.get(0); if (!IsCallable(executorVal)) return ReportIsNotFunction(cx, executorVal); RootedObject executor(cx, &executorVal.toObject()); @@ -1809,13 +1825,16 @@ PromiseConstructor(JSContext* cx, unsigned argc, Value* vp) // Promises, with the unprivileged one resolved with the resolution of the // privileged one. if (IsWrapper(newTarget)) { - newTarget = CheckedUnwrap(newTarget); - MOZ_ASSERT(newTarget); - MOZ_ASSERT(newTarget != originalNewTarget); + JSObject* unwrappedNewTarget = CheckedUnwrap(newTarget); + MOZ_ASSERT(unwrappedNewTarget); + MOZ_ASSERT(unwrappedNewTarget != newTarget); + + newTarget = unwrappedNewTarget; { AutoCompartment ac(cx, newTarget); - RootedObject promiseCtor(cx); - if (!GetBuiltinConstructor(cx, JSProto_Promise, &promiseCtor)) + Handle global = cx->global(); + JSFunction* promiseCtor = GlobalObject::getOrCreatePromiseConstructor(cx, global); + if (!promiseCtor) return false; // Promise subclasses don't get the special Xray treatment, so @@ -1857,8 +1876,10 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto / if (needsWrapping) { MOZ_ASSERT(proto); usedProto = CheckedUnwrap(proto); - if (!usedProto) + if (!usedProto) { + /* ReportAccessDenied(cx); */ return nullptr; + } } @@ -1944,10 +1965,10 @@ static bool Promise_static_all(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - RootedValue iterable(cx, args.get(0)); + HandleValue iterable = args.get(0); // Step 2 (reordered). - RootedValue CVal(cx, args.thisv()); + HandleValue CVal = args.thisv(); if (!CVal.isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.all call"); @@ -2098,8 +2119,7 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) RootedNativeObject valuesArray(cx, NewDenseFullyAllocatedArray(cx, promiseCount)); if (!valuesArray) return nullptr; - if (valuesArray->ensureDenseElements(cx, 0, promiseCount) != DenseElementResult::Success) - return nullptr; + valuesArray->ensureDenseInitializedLength(cx, 0, promiseCount); // Sub-step 4. // Create our data holder that holds all the things shared across @@ -2112,7 +2132,6 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) resolve)); if (!dataHolder) return nullptr; - RootedValue dataHolderVal(cx, ObjectValue(*dataHolder)); // Sub-step 5 (inline in loop-header below). @@ -2137,7 +2156,8 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) return nullptr; // Steps k-o. - resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); + resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, + ObjectValue(*dataHolder)); resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, Int32Value(index)); @@ -2205,7 +2225,7 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res if (!promiseObj) return true; - Rooted promise(cx, &promiseObj->as()); + Handle promise = promiseObj.as(); if (promise->state() != JS::PromiseState::Pending) return true; @@ -2215,7 +2235,7 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res if (mode == ResolveMode) return ResolvePromiseInternal(cx, promise, result); - return RejectMaybeWrappedPromise(cx, promiseObj, result); + return RejectPromiseInternal(cx, promise, result); } // ES2016, 25.4.4.1.1. @@ -2226,12 +2246,6 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, { *done = false; - RootedObject unwrappedPromiseObj(cx); - if (IsWrapper(promiseObj)) { - unwrappedPromiseObj = CheckedUnwrap(promiseObj); - MOZ_ASSERT(unwrappedPromiseObj); - } - // Step 1. MOZ_ASSERT(C->isConstructor()); RootedValue CVal(cx, ObjectValue(*C)); @@ -2259,8 +2273,11 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // canonical "then" function and content can't see our // PromiseAllResolveElement. RootedObject valuesArray(cx); - if (unwrappedPromiseObj) { - JSAutoCompartment ac(cx, unwrappedPromiseObj); + if (IsWrapper(promiseObj)) { + JSObject* unwrappedPromiseObj = CheckedUnwrap(promiseObj); + MOZ_ASSERT(unwrappedPromiseObj); + + AutoCompartment ac(cx, unwrappedPromiseObj); valuesArray = NewDenseFullyAllocatedArray(cx, 0); } else { valuesArray = NewDenseFullyAllocatedArray(cx, 0); @@ -2281,15 +2298,17 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, valuesArrayVal, resolve)); if (!dataHolder) return false; - RootedValue dataHolderVal(cx, ObjectValue(*dataHolder)); // Step 5. uint32_t index = 0; // Step 6. RootedValue nextValue(cx); + RootedValue nextPromise(cx); RootedId indexId(cx); RootedValue rejectFunVal(cx, ObjectValue(*reject)); + RootedValue resolveFunVal(cx); + RootedValue staticResolve(cx); while (true) { // Steps a-c, e-g. @@ -2319,10 +2338,10 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, } // Step h. - { // Scope for the JSAutoCompartment we need to work with valuesArray. We + { // Scope for the AutoCompartment we need to work with valuesArray. We // mostly do this for performance; we could go ahead and do the define via // a cross-compartment proxy instead... - JSAutoCompartment ac(cx, valuesArray); + AutoCompartment ac(cx, valuesArray); indexId = INT_TO_JSID(index); if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue)) return false; @@ -2331,9 +2350,7 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // Step i. // Sadly, because someone could have overridden // "resolve" on the canonical Promise constructor. - RootedValue nextPromise(cx); - RootedValue staticResolve(cx); - if (!GetProperty(cx, C, CVal, cx->names().resolve, &staticResolve)) + if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve)) return false; FixedInvokeArgs<1> resolveArgs(cx); @@ -2342,15 +2359,15 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, return false; // Step j. - RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction, - 1, nullptr, - gc::AllocKind::FUNCTION_EXTENDED, - GenericObject)); + JSFunction* resolveFunc = NewNativeFunction(cx, PromiseAllResolveElementFunction, 1, + nullptr,gc::AllocKind::FUNCTION_EXTENDED, + GenericObject); if (!resolveFunc) return false; // Steps k,m,n. - resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); + resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, + ObjectValue(*dataHolder)); // Step l. resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, @@ -2360,7 +2377,7 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, dataHolder->increaseRemainingCount(); // Step q. - RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc)); + resolveFunVal.setObject(*resolveFunc); if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal, true)) return false; @@ -2490,11 +2507,11 @@ PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - RootedFunction resolve(cx, &args.callee().as()); + JSFunction* resolve = &args.callee().as(); RootedValue xVal(cx, args.get(0)); // Step 1. - RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data)); + const Value& dataVal = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data); // Step 2. // We use the existence of the data holder as a signal for whether the @@ -2517,24 +2534,27 @@ PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) // Step 5. RootedValue valuesVal(cx, data->valuesArray()); RootedObject valuesObj(cx, &valuesVal.toObject()); - bool valuesListIsWrapped = false; - if (IsWrapper(valuesObj)) { - valuesListIsWrapped = true; + if (IsProxy(valuesObj)) { // See comment for PerformPromiseAll, step 3 for why we unwrap here. valuesObj = UncheckedUnwrap(valuesObj); + + if (JS_IsDeadWrapper(valuesObj)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); + return false; + } + + AutoCompartment ac(cx, valuesObj); + if (!cx->compartment()->wrap(cx, &xVal)) + return false; } - RootedNativeObject values(cx, &valuesObj->as()); + HandleNativeObject values = valuesObj.as(); // Step 6 (moved under step 10). // Step 7 (moved to step 9). // Step 8. // The index is guaranteed to be initialized to `undefined`. - if (valuesListIsWrapped) { - AutoCompartment ac(cx, values); - if (!cx->compartment()->wrap(cx, &xVal)) - return false; - } + MOZ_ASSERT(values->getDenseElement(index).isUndefined()); values->setDenseElement(index, xVal); // Steps 7,9. @@ -2581,20 +2601,23 @@ PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) RootedValue valuesVal(cx, data->valuesArray()); RootedObject valuesObj(cx, &valuesVal.toObject()); - bool valuesListIsWrapped = false; - if (IsWrapper(valuesObj)) { - valuesListIsWrapped = true; + if (IsProxy(valuesObj)) { // See comment for PerformPromiseAll, step 3 for why we unwrap here. valuesObj = UncheckedUnwrap(valuesObj); + + if (JS_IsDeadWrapper(valuesObj)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); + return false; + } + + AutoCompartment ac(cx, valuesObj); + if (!cx->compartment()->wrap(cx, &xVal)) + return false; } NativeObject* values = &valuesObj->as(); // The index is guaranteed to be initialized to `undefined`. - if (valuesListIsWrapped) { - AutoCompartment ac(cx, values); - if (!cx->compartment()->wrap(cx, &xVal)) - return false; - } + MOZ_ASSERT(values->getDenseElement(index).isUndefined()); RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); if (!obj) { @@ -2656,20 +2679,23 @@ PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp) RootedValue valuesVal(cx, data->valuesArray()); RootedObject valuesObj(cx, &valuesVal.toObject()); - bool valuesListIsWrapped = false; - if (IsWrapper(valuesObj)) { - valuesListIsWrapped = true; + if (IsProxy(valuesObj)) { // See comment for PerformPromiseAll, step 3 for why we unwrap here. valuesObj = UncheckedUnwrap(valuesObj); + + if (JS_IsDeadWrapper(valuesObj)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); + return false; + } + + AutoCompartment ac(cx, valuesObj); + if (!cx->compartment()->wrap(cx, &xVal)) + return false; } NativeObject* values = &valuesObj->as(); // The index is guaranteed to be initialized to `undefined`. - if (valuesListIsWrapped) { - AutoCompartment ac(cx, values); - if (!cx->compartment()->wrap(cx, &xVal)) - return false; - } + MOZ_ASSERT(values->getDenseElement(index).isUndefined()); RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); if (!obj) { @@ -2720,10 +2746,10 @@ static bool Promise_static_race(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - RootedValue iterable(cx, args.get(0)); + HandleValue iterable = args.get(0); // Step 2 (reordered). - RootedValue CVal(cx, args.thisv()); + HandleValue CVal = args.thisv(); if (!CVal.isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.race call"); @@ -2862,7 +2888,7 @@ CommonStaticResolveRejectImpl(JSContext* cx, HandleValue thisVal, HandleValue ar // outcome, so instead of unwrapping and then performing the // GetProperty, just check here and then operate on the original // object again. - RootedObject unwrappedObject(cx, CheckedUnwrap(xObj)); + JSObject* unwrappedObject = CheckedUnwrap(xObj); if (unwrappedObject && unwrappedObject->is()) isPromise = true; } @@ -2907,8 +2933,8 @@ static bool Promise_reject(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - RootedValue thisVal(cx, args.thisv()); - RootedValue argVal(cx, args.get(0)); + HandleValue thisVal = args.thisv(); + HandleValue argVal = args.get(0); JSObject* result = CommonStaticResolveRejectImpl(cx, thisVal, argVal, RejectMode); if (!result) return false; @@ -2922,7 +2948,7 @@ Promise_reject(JSContext* cx, unsigned argc, Value* vp) /* static */ JSObject* PromiseObject::unforgeableReject(JSContext* cx, HandleValue value) { - RootedObject promiseCtor(cx, JS::GetPromiseConstructor(cx)); + JSObject* promiseCtor = JS::GetPromiseConstructor(cx); if (!promiseCtor) return nullptr; RootedValue cVal(cx, ObjectValue(*promiseCtor)); @@ -2936,8 +2962,8 @@ static bool Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - RootedValue thisVal(cx, args.thisv()); - RootedValue argVal(cx, args.get(0)); + HandleValue thisVal = args.thisv(); + HandleValue argVal = args.get(0); JSObject* result = CommonStaticResolveRejectImpl(cx, thisVal, argVal, ResolveMode); if (!result) return false; @@ -2951,7 +2977,7 @@ Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp) /* static */ JSObject* PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value) { - RootedObject promiseCtor(cx, JS::GetPromiseConstructor(cx)); + JSObject* promiseCtor = JS::GetPromiseConstructor(cx); if (!promiseCtor) return nullptr; RootedValue cVal(cx, ObjectValue(*promiseCtor)); @@ -3012,7 +3038,7 @@ NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfi return nullptr; } - Rooted reaction(cx, NewObjectWithClassProto(cx)); + PromiseReactionRecord* reaction = NewObjectWithClassProto(cx); if (!reaction) return nullptr; @@ -3153,7 +3179,7 @@ js::AsyncFunctionThrown(JSContext* cx, Handle resultPromise) if (!MaybeGetAndClearException(cx, &exc)) return false; - if (!RejectMaybeWrappedPromise(cx, resultPromise, exc)) + if (!RejectPromiseInternal(cx, resultPromise, exc)) return false; // Step 3.g. @@ -3244,10 +3270,10 @@ bool js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind completionKind) { // Step 1. - RootedValue thisVal(cx, args.thisv()); + HandleValue thisVal = args.thisv(); // Step 2. - RootedObject resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); + Rooted resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!resultPromise) return false; @@ -3259,7 +3285,7 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co return false; // Step 3.b. - if (!RejectMaybeWrappedPromise(cx, resultPromise, badGeneratorError)) + if (!RejectPromiseInternal(cx, resultPromise, badGeneratorError)) return false; // Step 3.c. @@ -3273,7 +3299,6 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co // Step 4. RootedObject iter(cx, asyncIter->iterator()); - RootedValue resultVal(cx); RootedValue func(cx); if (completionKind == CompletionKind::Normal) { // 11.1.3.2.1 steps 5-6 (partially). @@ -3287,7 +3312,7 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co // Step 7. if (func.isNullOrUndefined()) { // Step 7.a. - RootedObject resultObj(cx, CreateIterResultObject(cx, args.get(0), true)); + JSObject* resultObj = CreateIterResultObject(cx, args.get(0), true); if (!resultObj) return AbruptRejectPromise(cx, args, resultPromise, nullptr); @@ -3310,7 +3335,7 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co // Step 7. if (func.isNullOrUndefined()) { // Step 7.a. - if (!RejectMaybeWrappedPromise(cx, resultPromise, args.get(0))) + if (!RejectPromiseInternal(cx, resultPromise, args.get(0))) return AbruptRejectPromise(cx, args, resultPromise, nullptr); // Step 7.b. @@ -3324,6 +3349,8 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co RootedValue iterVal(cx, ObjectValue(*iter)); FixedInvokeArgs<1> args2(cx); args2[0].set(args.get(0)); + + RootedValue resultVal(cx); if (!js::Call(cx, func, iterVal, args2, &resultVal)) return AbruptRejectPromise(cx, args, resultPromise, nullptr); @@ -3428,18 +3455,18 @@ AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenOb MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); // Step 4. - Rooted request( - cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj)); + AsyncGeneratorRequest* request = + AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj); if (!request) return false; // Step 5. - RootedObject resultPromise(cx, request->promise()); + Rooted resultPromise(cx, request->promise()); asyncGenObj->cacheRequest(request); // Step 6. - if (!RejectMaybeWrappedPromise(cx, resultPromise, exception)) + if (!RejectPromiseInternal(cx, resultPromise, exception)) return false; // Steps 7-8. @@ -3455,18 +3482,18 @@ AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenOb MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); // Step 4. - Rooted request( - cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj)); + AsyncGeneratorRequest* request = + AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj); if (!request) return false; // Step 5. - RootedObject resultPromise(cx, request->promise()); + Rooted resultPromise(cx, request->promise()); asyncGenObj->cacheRequest(request); // Step 6. - RootedObject resultObj(cx, CreateIterResultObject(cx, value, done)); + JSObject* resultObj = CreateIterResultObject(cx, value, done); if (!resultObj) return false; @@ -3594,7 +3621,7 @@ js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, // Step 1 (implicit). // Step 2. - RootedObject resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); + Rooted resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!resultPromise) return false; @@ -3606,7 +3633,7 @@ js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, return false; // Step 3.b. - if (!RejectMaybeWrappedPromise(cx, resultPromise, badGeneratorError)) + if (!RejectPromiseInternal(cx, resultPromise, badGeneratorError)) return false; // Step 3.c. @@ -3716,11 +3743,10 @@ Promise_then_impl(JSContext* cx, HandleValue promiseVal, HandleValue onFulfilled RootedObject promiseObj(cx, &promiseVal.toObject()); Rooted promise(cx); - bool isPromise = promiseObj->is(); - if (isPromise) { + if (promiseObj->is()) { promise = &promiseObj->as(); } else { - RootedObject unwrappedPromiseObj(cx, CheckedUnwrap(promiseObj)); + JSObject* unwrappedPromiseObj = CheckedUnwrap(promiseObj); if (!unwrappedPromiseObj) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); return false; @@ -3934,7 +3960,7 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis } // 25.4.5.3., step 5. - Rooted promise(cx, &promiseObj->as()); + Handle promise = promiseObj.as(); if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, resolveFun, rejectFun)) { @@ -3993,7 +4019,7 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis if (!blockedPromise_->is()) return true; - Rooted promise(cx, &unwrappedPromiseObj->as()); + Handle promise = unwrappedPromiseObj.as(); return AddDummyPromiseReactionForDebugger(cx, promise, blockedPromise); } @@ -4017,7 +4043,6 @@ AddPromiseReaction(JSContext* cx, Handle promise, // 25.4.5.3.1 steps 7.a,b. RootedValue reactionsVal(cx, promise->reactions()); - RootedNativeObject reactions(cx); if (reactionsVal.isUndefined()) { // If no reactions existed so far, just store the reaction record directly. @@ -4042,22 +4067,24 @@ AddPromiseReaction(JSContext* cx, Handle promise, if (reactionsObj->is()) { // If a single reaction existed so far, create a list and store the // old and the new reaction in it. - reactions = NewDenseFullyAllocatedArray(cx, 2); + ArrayObject* reactions = NewDenseFullyAllocatedArray(cx, 2); if (!reactions) return false; - if (reactions->ensureDenseElements(cx, 0, 2) != DenseElementResult::Success) - return false; - reactions->setDenseElement(0, reactionsVal); - reactions->setDenseElement(1, reactionVal); + reactions->setDenseInitializedLength(2); + reactions->initDenseElement(0, reactionsVal); + reactions->initDenseElement(1, reactionVal); promise->setFixedSlot(PromiseSlot_ReactionsOrResult, ObjectValue(*reactions)); } else { // Otherwise, just store the new reaction. - reactions = &reactionsObj->as(); + HandleNativeObject reactions = reactionsObj.as(); uint32_t len = reactions->getDenseInitializedLength(); - if (reactions->ensureDenseElements(cx, 0, len + 1) != DenseElementResult::Success) + DenseElementResult result = reactions->ensureDenseElements(cx, len, 1); + if (result != DenseElementResult::Success) { + MOZ_ASSERT(result == DenseElementResult::Failure); return false; + } reactions->setDenseElement(len, reactionVal); } @@ -4137,10 +4164,10 @@ PromiseObject::dependentPromises(JSContext* cx, MutableHandle> v uint32_t len = reactions->getDenseInitializedLength(); MOZ_ASSERT(len >= 2); - size_t valuesIndex = 0; - Rooted reaction(cx); - for (size_t i = 0; i < len; i++) { - reaction = &reactions->getDenseElement(i).toObject().as(); + uint32_t valuesIndex = 0; + for (uint32_t i = 0; i < len; i++) { + const Value& element = reactions->getDenseElement(i); + PromiseReactionRecord* reaction = &element.toObject().as(); // Not all reactions have a Promise on them. RootedObject promiseObj(cx, reaction->promise()); @@ -4165,7 +4192,7 @@ PromiseObject::resolve(JSContext* cx, Handle promise, HandleValu if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) return ResolvePromiseInternal(cx, promise, resolutionValue); - RootedObject resolveFun(cx, GetResolveFunctionFromPromise(promise)); + JSFunction* resolveFun = GetResolveFunctionFromPromise(promise); if (!resolveFun) return true; diff --git a/js/src/vm/AsyncIteration.cpp b/js/src/vm/AsyncIteration.cpp index 58ad09815c..49fdf7b8d6 100644 --- a/js/src/vm/AsyncIteration.cpp +++ b/js/src/vm/AsyncIteration.cpp @@ -319,7 +319,7 @@ AsyncGeneratorObject::create(JSContext* cx, HandleFunction asyncGen, HandleValue /* static */ AsyncGeneratorRequest* AsyncGeneratorObject::createRequest(JSContext* cx, Handle asyncGenObj, CompletionKind completionKind, HandleValue completionValue, - HandleObject promise) + Handle promise) { if (!asyncGenObj->hasCachedRequest()) return AsyncGeneratorRequest::create(cx, completionKind, completionValue, promise); @@ -444,7 +444,7 @@ const Class AsyncGeneratorRequest::class_ = { // Async Iteration proposal 11.4.3.1. /* static */ AsyncGeneratorRequest* AsyncGeneratorRequest::create(JSContext* cx, CompletionKind completionKind, - HandleValue completionValue, HandleObject promise) + HandleValue completionValue, Handle promise) { RootedObject obj(cx, NewNativeObjectWithGivenProto(cx, &class_, nullptr)); if (!obj) diff --git a/js/src/vm/AsyncIteration.h b/js/src/vm/AsyncIteration.h index 66c9c091f1..d3deb90b1a 100644 --- a/js/src/vm/AsyncIteration.h +++ b/js/src/vm/AsyncIteration.h @@ -62,8 +62,7 @@ class AsyncGeneratorRequest : public NativeObject Slots, }; - void init(CompletionKind completionKind, HandleValue completionValue, - HandleObject promise) { + void init(CompletionKind completionKind, const Value& completionValue, PromiseObject* promise) { setFixedSlot(Slot_CompletionKind, Int32Value(static_cast(completionKind))); setFixedSlot(Slot_CompletionValue, completionValue); @@ -81,7 +80,8 @@ class AsyncGeneratorRequest : public NativeObject static const Class class_; static AsyncGeneratorRequest* create(JSContext* cx, CompletionKind completionKind, - HandleValue completionValue, HandleObject promise); + HandleValue completionValue, + Handle promise); CompletionKind completionKind() const { return static_cast(getFixedSlot(Slot_CompletionKind).toInt32()); @@ -89,8 +89,8 @@ class AsyncGeneratorRequest : public NativeObject JS::Value completionValue() const { return getFixedSlot(Slot_CompletionValue); } - JSObject* promise() const { - return &getFixedSlot(Slot_Promise).toObject(); + PromiseObject* promise() const { + return &getFixedSlot(Slot_Promise).toObject().as(); } }; @@ -232,7 +232,7 @@ class AsyncGeneratorObject : public NativeObject Handle asyncGenObj, CompletionKind completionKind, HandleValue completionValue, - HandleObject promise); + Handle promise); // Stores the given request to the generator's cache after clearing its data // slots. The cached request will be reused in the subsequent createRequest From c0b1bf7e1ec11afc1488de3f1f85de939afd9d53 Mon Sep 17 00:00:00 2001 From: Martok Date: Thu, 26 Jan 2023 22:25:07 +0100 Subject: [PATCH 16/21] Issue #2089 - Add a PromiseCapability struct, directly initialize dense elements in PerformPromiseAll using NewbornArrayPush Based-on: m-c 1475678/{8,9} --- js/src/builtin/Promise.cpp | 428 ++++++++++++++++++++--------------- js/src/vm/NativeObject-inl.h | 15 +- js/src/vm/NativeObject.h | 4 + 3 files changed, 262 insertions(+), 185 deletions(-) diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 75024c8a7e..c29d6380e6 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -127,6 +127,67 @@ enum PromiseAllDataHolderSlots { PromiseAllDataHolderSlots, }; +struct PromiseCapability { + JSObject* promise = nullptr; + JSObject* resolve = nullptr; + JSObject* reject = nullptr; + + PromiseCapability() = default; + + static void trace(PromiseCapability* self, JSTracer* trc) { self->trace(trc); } + void trace(JSTracer* trc); +}; + +void +PromiseCapability::trace(JSTracer* trc) +{ + if (promise) + TraceRoot(trc, &promise, "PromiseCapability::promise"); + if (resolve) + TraceRoot(trc, &resolve, "PromiseCapability::resolve"); + if (reject) + TraceRoot(trc, &reject, "PromiseCapability::reject"); +} + +namespace js { + +template +class WrappedPtrOperations +{ + const PromiseCapability& capability() const { return static_cast(this)->get(); } + + public: + HandleObject promise() const { + return HandleObject::fromMarkedLocation(&capability().promise); + } + HandleObject resolve() const { + return HandleObject::fromMarkedLocation(&capability().resolve); + } + HandleObject reject() const { + return HandleObject::fromMarkedLocation(&capability().reject); + } +}; + +template +class MutableWrappedPtrOperations + : public WrappedPtrOperations +{ + PromiseCapability& capability() { return static_cast(this)->get(); } + + public: + MutableHandleObject promise() { + return MutableHandleObject::fromMarkedLocation(&capability().promise); + } + MutableHandleObject resolve() { + return MutableHandleObject::fromMarkedLocation(&capability().resolve); + } + MutableHandleObject reject() { + return MutableHandleObject::fromMarkedLocation(&capability().reject); + } +}; + +} // namespace js + class PromiseAllDataHolder : public NativeObject { public: @@ -366,7 +427,7 @@ static MOZ_MUST_USE bool RunResolutionFunction(JSContext *cx, HandleObject resol // be tedious, so the check in step 1 and the entirety of step 2 aren't // included. static bool -AbruptRejectPromise(JSContext *cx, CallArgs& args, HandleObject promiseObj, HandleObject reject) +AbruptRejectPromise(JSContext* cx, CallArgs& args, HandleObject promiseObj, HandleObject reject) { // Step 1.a. RootedValue reason(cx); @@ -381,6 +442,12 @@ AbruptRejectPromise(JSContext *cx, CallArgs& args, HandleObject promiseObj, Hand return true; } +static bool +AbruptRejectPromise(JSContext* cx, CallArgs& args, Handle capability) +{ + return AbruptRejectPromise(cx, args, capability.promise(), capability.reject()); +} + enum ReactionRecordSlots { // The promise for which this record provides a reaction handler. // Matches the [[Capability]].[[Promise]] field from the spec. @@ -1039,8 +1106,7 @@ CreatePromiseWithDefaultResolutionFunctions(JSContext* cx, MutableHandleObject r // ES2016, 25.4.1.5. static MOZ_MUST_USE bool -NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, - MutableHandleObject resolve, MutableHandleObject reject, +NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandle capability, bool canOmitResolutionFunctions) { RootedValue cVal(cx, ObjectValue(*C)); @@ -1065,12 +1131,17 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, // of the GetCapabilitiesExecutor function, and directly allocate the // result promise instead of invoking the Promise constructor. if (IsNativeFunction(cVal, PromiseConstructor)) { - if (canOmitResolutionFunctions) - promise.set(CreatePromiseObjectWithoutResolutionFunctions(cx)); - else - promise.set(CreatePromiseWithDefaultResolutionFunctions(cx, resolve, reject)); + PromiseObject* promise; + if (canOmitResolutionFunctions) { + promise = CreatePromiseObjectWithoutResolutionFunctions(cx); + } else { + promise = CreatePromiseWithDefaultResolutionFunctions(cx, capability.resolve(), + capability.reject()); + } if (!promise) return false; + + capability.promise().set(promise); return true; } @@ -1088,7 +1159,7 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, // Step 6. FixedConstructArgs<1> cargs(cx); cargs[0].setObject(*executor); - if (!Construct(cx, cVal, cargs, cVal, promise)) + if (!Construct(cx, cVal, cargs, cVal, capability.promise())) return false; // Step 7. @@ -1108,8 +1179,8 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, } // Step 9 (well, the equivalent for all of promiseCapabilities' fields.) - resolve.set(&resolveVal.toObject()); - reject.set(&rejectVal.toObject()); + capability.resolve().set(&resolveVal.toObject()); + capability.reject().set(&rejectVal.toObject()); // Step 10. return true; @@ -1955,10 +2026,9 @@ PromiseObject::createSkippingExecutor(JSContext* cx) return CreatePromiseObjectWithoutResolutionFunctions(cx); } -static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, - HandleObject C, HandleObject promiseObj, - HandleObject resolve, HandleObject reject, - bool* done); +static MOZ_MUST_USE bool +PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, + Handle resultCapability, bool* done); // ES2016, 25.4.4.1. static bool @@ -1979,28 +2049,26 @@ Promise_static_all(JSContext* cx, unsigned argc, Value* vp) RootedObject C(cx, &CVal.toObject()); // Step 3. - RootedObject resultPromise(cx); - RootedObject resolve(cx); - RootedObject reject(cx); - if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) + Rooted promiseCapability(cx); + if (!NewPromiseCapability(cx, C, &promiseCapability, false)) return false; // Steps 4-5. JS::ForOfIterator iter(cx); if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); if (!iter.valueIsIterable()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, "Argument of Promise.all"); - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); } // Step 6 (implicit). // Step 7. bool done; - bool result = PerformPromiseAll(cx, iter, C, resultPromise, resolve, reject, &done); + bool result = PerformPromiseAll(cx, iter, C, promiseCapability, &done); // Step 8. if (!result) { @@ -2009,18 +2077,17 @@ Promise_static_all(JSContext* cx, unsigned argc, Value* vp) iter.closeThrow(); // Step 8.b. - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); } // Step 9. - args.rval().setObject(*resultPromise); + args.rval().setObject(*promiseCapability.promise()); return true; } -static MOZ_MUST_USE bool PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, - HandleObject C, HandleObject promiseObj, - HandleObject resolve, HandleObject reject, - bool* done); +static MOZ_MUST_USE bool +PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, + Handle resultCapability, bool* done); // ES2020 static bool @@ -2036,46 +2103,42 @@ Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) } RootedObject C(cx, &CVal.toObject()); - RootedObject resultPromise(cx); - RootedObject resolve(cx); - RootedObject reject(cx); - if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) + Rooted promiseCapability(cx); + if (!NewPromiseCapability(cx, C, &promiseCapability, false)) return false; JS::ForOfIterator iter(cx); if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); if (!iter.valueIsIterable()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, "Argument of Promise.allSettled"); - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); } bool done; - bool result = PerformPromiseAllSettled(cx, iter, C, resultPromise, resolve, reject, &done); + bool result = PerformPromiseAllSettled(cx, iter, C, promiseCapability, &done); if (!result) { if (!done) iter.closeThrow(); - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); } - args.rval().setObject(*resultPromise); + args.rval().setObject(*promiseCapability.promise()); return true; } -static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle promise, - HandleValue onFulfilled_, HandleValue onRejected_, - HandleObject resultPromise, - HandleObject resolve, HandleObject reject); +static MOZ_MUST_USE bool +PerformPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled_, + HandleValue onRejected_, Handle resultCapability); static MOZ_MUST_USE bool PerformPromiseThenWithoutSettleHandlers(JSContext* cx, Handle promise, Handle promiseToResolve, - HandleObject resultPromise, HandleObject resolve, - HandleObject reject); + Handle resultCapability); static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); static bool PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); @@ -2101,10 +2164,8 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) // Step 2 (omitted). // Step 3. - RootedObject resultPromise(cx); - RootedObject resolve(cx); - RootedObject reject(cx); - if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) + Rooted resultCapability(cx); + if (!NewPromiseCapability(cx, C, &resultCapability, false)) return nullptr; // Steps 4-6 (omitted). @@ -2127,12 +2188,16 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) // remainingElementsCount (as an integer reserved slot), the array of // values, and the resolve function from our PromiseCapability. RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); - Rooted dataHolder(cx, NewPromiseAllDataHolder(cx, resultPromise, - valuesArrayVal, - resolve)); + Rooted dataHolder(cx); + dataHolder = NewPromiseAllDataHolder(cx, resultCapability.promise(), valuesArrayVal, + resultCapability.resolve()); if (!dataHolder) return nullptr; + // Call PerformPromiseThen with resolve and reject set to nullptr. + Rooted resultCapabilityWithoutResolving(cx); + resultCapabilityWithoutResolving.promise().set(resultCapability.promise()); + // Sub-step 5 (inline in loop-header below). // Sub-step 6. @@ -2166,7 +2231,7 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) // Step q, very roughly. RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc)); - RootedValue rejectFunVal(cx, ObjectValue(*reject)); + RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject())); Rooted nextPromise(cx); // GetWaitForAllPromise is used internally only and must not @@ -2178,7 +2243,7 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) nextPromise = &UncheckedUnwrap(nextPromiseObj)->as(); if (!PerformPromiseThen(cx, nextPromise, resolveFunVal, rejectFunVal, - resultPromise, nullptr, nullptr)) + resultCapabilityWithoutResolving)) { return nullptr; } @@ -2193,7 +2258,7 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) // Sub-step d.iii-iv. if (remainingCount == 0) { RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); - if (!ResolvePromiseInternal(cx, resultPromise, valuesArrayVal)) + if (!ResolvePromiseInternal(cx, resultCapability.promise(), valuesArrayVal)) return nullptr; } } @@ -2201,7 +2266,7 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) // Step 8 (omitted). // Step 9. - return resultPromise; + return resultCapability.promise(); } static MOZ_MUST_USE bool @@ -2241,11 +2306,12 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res // ES2016, 25.4.4.1.1. static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, - HandleObject promiseObj, HandleObject resolve, HandleObject reject, - bool* done) + Handle resultCapability, bool* done) { *done = false; + HandleObject promiseObj = resultCapability.promise(); + // Step 1. MOZ_ASSERT(C->isConstructor()); RootedValue CVal(cx, ObjectValue(*C)); @@ -2272,30 +2338,38 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // those are happening over Xrays anyway, which means they get the // canonical "then" function and content can't see our // PromiseAllResolveElement. - RootedObject valuesArray(cx); + RootedArrayObject valuesArray(cx); + RootedValue valuesArrayVal(cx); if (IsWrapper(promiseObj)) { JSObject* unwrappedPromiseObj = CheckedUnwrap(promiseObj); MOZ_ASSERT(unwrappedPromiseObj); - AutoCompartment ac(cx, unwrappedPromiseObj); - valuesArray = NewDenseFullyAllocatedArray(cx, 0); - } else { - valuesArray = NewDenseFullyAllocatedArray(cx, 0); - } - if (!valuesArray) - return false; + { + AutoCompartment ac(cx, unwrappedPromiseObj); + valuesArray = NewDenseEmptyArray(cx); + if (!valuesArray) + return false; + } - RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); - if (!cx->compartment()->wrap(cx, &valuesArrayVal)) - return false; + valuesArrayVal.setObject(*valuesArray); + if (!cx->compartment()->wrap(cx, &valuesArrayVal)) + return false; + } else { + valuesArray = NewDenseEmptyArray(cx); + if (!valuesArray) + return false; + + valuesArrayVal.setObject(*valuesArray); + } // Step 4. // Create our data holder that holds all the things shared across // every step of the iterator. In particular, this holds the // remainingElementsCount (as an integer reserved slot), the array of // values, and the resolve function from our PromiseCapability. - Rooted dataHolder(cx, NewPromiseAllDataHolder(cx, promiseObj, - valuesArrayVal, resolve)); + Rooted dataHolder(cx); + dataHolder = NewPromiseAllDataHolder(cx, promiseObj, valuesArrayVal, + resultCapability.resolve()); if (!dataHolder) return false; @@ -2305,8 +2379,7 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // Step 6. RootedValue nextValue(cx); RootedValue nextPromise(cx); - RootedId indexId(cx); - RootedValue rejectFunVal(cx, ObjectValue(*reject)); + RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject())); RootedValue resolveFunVal(cx); RootedValue staticResolve(cx); @@ -2329,8 +2402,8 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // Steps d.iii-iv. if (remainingCount == 0) { - return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode, - promiseObj); + return RunResolutionFunction(cx, resultCapability.resolve(), valuesArrayVal, + ResolveMode, promiseObj); } // We're all set for now! @@ -2342,8 +2415,8 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // mostly do this for performance; we could go ahead and do the define via // a cross-compartment proxy instead... AutoCompartment ac(cx, valuesArray); - indexId = INT_TO_JSID(index); - if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue)) + + if (!NewbornArrayPush(cx, valuesArray, UndefinedValue())) return false; } @@ -2389,35 +2462,41 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, static MOZ_MUST_USE bool PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, - HandleObject promiseObj, HandleObject resolve, HandleObject reject, - bool* done) + Handle resultCapability, bool* done) { *done = false; - RootedObject unwrappedPromiseObj(cx); - if (IsWrapper(promiseObj)) { - unwrappedPromiseObj = CheckedUnwrap(promiseObj); - MOZ_ASSERT(unwrappedPromiseObj); - } + HandleObject promiseObj = resultCapability.promise(); RootedValue CVal(cx, ObjectValue(*C)); - RootedObject valuesArray(cx); - if (unwrappedPromiseObj) { - JSAutoCompartment ac(cx, unwrappedPromiseObj); - valuesArray = NewDenseFullyAllocatedArray(cx, 0); + RootedArrayObject valuesArray(cx); + RootedValue valuesArrayVal(cx); + if (IsWrapper(promiseObj)) { + JSObject* unwrappedPromiseObj = CheckedUnwrap(promiseObj); + MOZ_ASSERT(unwrappedPromiseObj); + + { + AutoCompartment ac(cx, unwrappedPromiseObj); + valuesArray = NewDenseEmptyArray(cx); + if (!valuesArray) + return false; + } + + valuesArrayVal.setObject(*valuesArray); + if (!cx->compartment()->wrap(cx, &valuesArrayVal)) + return false; } else { - valuesArray = NewDenseFullyAllocatedArray(cx, 0); + valuesArray = NewDenseEmptyArray(cx); + if (!valuesArray) + return false; + + valuesArrayVal.setObject(*valuesArray); } - if (!valuesArray) - return false; - RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); - if (!cx->compartment()->wrap(cx, &valuesArrayVal)) - return false; - - Rooted dataHolder(cx, NewPromiseAllDataHolder(cx, promiseObj, - valuesArrayVal, resolve)); + Rooted dataHolder(cx); + dataHolder = NewPromiseAllDataHolder(cx, promiseObj, valuesArrayVal, + resultCapability.resolve()); if (!dataHolder) return false; RootedValue dataHolderVal(cx, ObjectValue(*dataHolder)); @@ -2425,7 +2504,6 @@ PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObjec uint32_t index = 0; RootedValue nextValue(cx); - RootedId indexId(cx); while (true) { if (!iterator.next(&nextValue, done)) { @@ -2436,16 +2514,16 @@ PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObjec if (*done) { int32_t remainingCount = dataHolder->decreaseRemainingCount(); if (remainingCount == 0) { - return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode, - promiseObj); + return RunResolutionFunction(cx, resultCapability.resolve(), valuesArrayVal, + ResolveMode, promiseObj); } return true; } { // Scoped for AutoCompartment JSAutoCompartment ac(cx, valuesArray); - indexId = INT_TO_JSID(index); - if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue)) + + if (!NewbornArrayPush(cx, valuesArray, UndefinedValue())) return false; } @@ -2736,10 +2814,9 @@ PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp) return true; } -static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, - HandleObject C, HandleObject promiseObj, - HandleObject resolve, HandleObject reject, - bool* done); +static MOZ_MUST_USE bool +PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, + Handle resultCapability, bool* done); // ES2016, 25.4.4.3. static bool @@ -2760,28 +2837,26 @@ Promise_static_race(JSContext* cx, unsigned argc, Value* vp) RootedObject C(cx, &CVal.toObject()); // Step 3. - RootedObject resultPromise(cx); - RootedObject resolve(cx); - RootedObject reject(cx); - if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) + Rooted promiseCapability(cx); + if (!NewPromiseCapability(cx, C, &promiseCapability, false)) return false; // Steps 4-5. JS::ForOfIterator iter(cx); if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); if (!iter.valueIsIterable()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, "Argument of Promise.race"); - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); } // Step 6 (implicit). // Step 7. bool done; - bool result = PerformPromiseRace(cx, iter, C, resultPromise, resolve, reject, &done); + bool result = PerformPromiseRace(cx, iter, C, promiseCapability, &done); // Step 8. if (!result) { @@ -2790,19 +2865,18 @@ Promise_static_race(JSContext* cx, unsigned argc, Value* vp) iter.closeThrow(); // Step 8.b. - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); } // Step 9. - args.rval().setObject(*resultPromise); + args.rval().setObject(*promiseCapability.promise()); return true; } // ES2016, 25.4.4.3.1. static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, - HandleObject promiseObj, HandleObject resolve, HandleObject reject, - bool* done) + Handle resultCapability, bool* done) { *done = false; MOZ_ASSERT(C->isConstructor()); @@ -2811,11 +2885,16 @@ PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // BlockOnPromise fast path requires the passed onFulfilled function // doesn't return an object value, because otherwise the skipped promise // creation is detectable due to missing property lookups. - bool isDefaultResolveFn = IsNativeFunction(resolve, ResolvePromiseFunction); + bool isDefaultResolveFn = IsNativeFunction(resultCapability.resolve(), + ResolvePromiseFunction); + + HandleObject promiseObj = resultCapability.promise(); + RootedValue resolveFunVal(cx, ObjectValue(*resultCapability.resolve())); + RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject())); RootedValue nextValue(cx); - RootedValue resolveFunVal(cx, ObjectValue(*resolve)); - RootedValue rejectFunVal(cx, ObjectValue(*reject)); + RootedValue nextPromise(cx); + RootedValue staticResolve(cx); while (true) { // Steps a-c, e-g. @@ -2838,8 +2917,6 @@ PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // Step h. // Sadly, because someone could have overridden // "resolve" on the canonical Promise constructor. - RootedValue nextPromise(cx); - RootedValue staticResolve(cx); if (!GetProperty(cx, C, CVal, cx->names().resolve, &staticResolve)) return false; @@ -2902,21 +2979,19 @@ CommonStaticResolveRejectImpl(JSContext* cx, HandleValue thisVal, HandleValue ar } // Step 4 of Resolve, 3 of Reject. - RootedObject promise(cx); - RootedObject resolveFun(cx); - RootedObject rejectFun(cx); - if (!NewPromiseCapability(cx, C, &promise, &resolveFun, &rejectFun, true)) + Rooted capability(cx); + if (!NewPromiseCapability(cx, C, &capability, true)) return nullptr; // Step 5 of Resolve, 4 of Reject. - if (!RunResolutionFunction(cx, mode == ResolveMode ? resolveFun : rejectFun, argVal, mode, - promise)) + if (!RunResolutionFunction(cx, mode == ResolveMode ? capability.resolve() : capability.reject(), + argVal, mode, capability.promise())) { return nullptr; } // Step 6 of Resolve, 4 of Reject. - return promise; + return capability.promise(); } MOZ_MUST_USE JSObject* @@ -3004,18 +3079,22 @@ enum class IncumbentGlobalObject { }; static PromiseReactionRecord* -NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfilled, - HandleValue onRejected, HandleObject resolve, HandleObject reject, +NewReactionRecord(JSContext* cx, Handle resultCapability, + HandleValue onFulfilled, HandleValue onRejected, IncumbentGlobalObject incumbentGlobalObjectOption) { // Either of the following conditions must be met: - // * resultPromise is a PromiseObject - // * resolve and reject are callable + // * resultCapability.promise is a PromiseObject + // * resultCapability.resolve and resultCapability.resolve are callable // except for Async Generator, there resultPromise can be nullptr. - MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), resolve); - MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), IsCallable(resolve)); - MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), reject); - MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), IsCallable(reject)); +#ifdef DEBUG + if (resultCapability.promise() && !resultCapability.promise()->is()) { + MOZ_ASSERT(resultCapability.resolve()); + MOZ_ASSERT(IsCallable(resultCapability.resolve())); + MOZ_ASSERT(resultCapability.reject()); + MOZ_ASSERT(IsCallable(resultCapability.reject())); + } +#endif // Ensure the onFulfilled handler has the expected type. MOZ_ASSERT(onFulfilled.isInt32() || onFulfilled.isObjectOrNull()); @@ -3042,19 +3121,22 @@ NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfi if (!reaction) return nullptr; - assertSameCompartment(cx, resultPromise); + assertSameCompartment(cx, resultCapability.promise()); assertSameCompartment(cx, onFulfilled); assertSameCompartment(cx, onRejected); - assertSameCompartment(cx, resolve); - assertSameCompartment(cx, reject); + assertSameCompartment(cx, resultCapability.resolve()); + assertSameCompartment(cx, resultCapability.reject()); assertSameCompartment(cx, incumbentGlobalObject); - reaction->setFixedSlot(ReactionRecordSlot_Promise, ObjectOrNullValue(resultPromise)); + reaction->setFixedSlot(ReactionRecordSlot_Promise, + ObjectOrNullValue(resultCapability.promise())); reaction->setFixedSlot(ReactionRecordSlot_Flags, Int32Value(0)); reaction->setFixedSlot(ReactionRecordSlot_OnFulfilled, onFulfilled); reaction->setFixedSlot(ReactionRecordSlot_OnRejected, onRejected); - reaction->setFixedSlot(ReactionRecordSlot_Resolve, ObjectOrNullValue(resolve)); - reaction->setFixedSlot(ReactionRecordSlot_Reject, ObjectOrNullValue(reject)); + reaction->setFixedSlot(ReactionRecordSlot_Resolve, + ObjectOrNullValue(resultCapability.resolve())); + reaction->setFixedSlot(ReactionRecordSlot_Reject, + ObjectOrNullValue(resultCapability.reject())); reaction->setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject, ObjectOrNullValue(incumbentGlobalObject)); @@ -3070,8 +3152,7 @@ IsPromiseSpecies(JSContext* cx, JSFunction* species) static bool PromiseThenNewPromiseCapability(JSContext* cx, HandleObject promiseObj, CreateDependentPromise createDependent, - MutableHandleObject resultPromise, - MutableHandleObject resolve, MutableHandleObject reject) + MutableHandle resultCapability) { if (createDependent != CreateDependentPromise::Never) { // Step 3. @@ -3083,7 +3164,7 @@ PromiseThenNewPromiseCapability(JSContext* cx, HandleObject promiseObj, !IsNativeFunction(C, PromiseConstructor)) { // Step 4. - if (!NewPromiseCapability(cx, C, resultPromise, resolve, reject, true)) + if (!NewPromiseCapability(cx, C, resultCapability, true)) return false; } } @@ -3103,21 +3184,16 @@ js::OriginalPromiseThen(JSContext* cx, Handle promise, return false; } - RootedObject resultPromise(cx); - RootedObject resolve(cx); - RootedObject reject(cx); - - if (!PromiseThenNewPromiseCapability(cx, promiseObj, createDependent, &resultPromise, - &resolve, &reject)) - { + // Steps 3-4. + Rooted resultCapability(cx); + if (!PromiseThenNewPromiseCapability(cx, promiseObj, createDependent, &resultCapability)) return false; - } // Step 5. - if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, resolve, reject)) + if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultCapability)) return false; - dependent.set(resultPromise); + dependent.set(resultCapability.promise()); return true; } @@ -3128,18 +3204,15 @@ OriginalPromiseThenWithoutSettleHandlers(JSContext* cx, Handle p assertSameCompartment(cx, promise); // Steps 3-4. - RootedObject resultPromise(cx); - RootedObject resolve(cx); - RootedObject reject(cx); + Rooted resultCapability(cx); if (!PromiseThenNewPromiseCapability(cx, promise, CreateDependentPromise::SkipIfCtorUnobservable, - &resultPromise, &resolve, &reject)) + &resultCapability)) { return false; } // Step 5. - return PerformPromiseThenWithoutSettleHandlers(cx, promise, promiseToResolve, resultPromise, - resolve, reject); + return PerformPromiseThenWithoutSettleHandlers(cx, promise, promiseToResolve, resultCapability); } static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx, @@ -3218,9 +3291,10 @@ InternalAwait(JSContext* cx, HandleValue value, HandleObject resultPromise, return false; // Steps 7-8. - Rooted reaction(cx, NewReactionRecord(cx, resultPromise, + Rooted resultCapability(cx); + resultCapability.promise().set(resultPromise); + Rooted reaction(cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected, - nullptr, nullptr, IncumbentGlobalObject::Yes)); if (!reaction) return false; @@ -3797,8 +3871,7 @@ Promise_then(JSContext* cx, unsigned argc, Value* vp) // ES2016, 25.4.5.3.1. static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled_, - HandleValue onRejected_, HandleObject resultPromise, - HandleObject resolve, HandleObject reject) + HandleValue onRejected_, Handle resultCapability) { // Step 1 (implicit). // Step 2 (implicit). @@ -3814,9 +3887,8 @@ PerformPromiseThen(JSContext* cx, Handle promise, HandleValue on onRejected = Int32Value(PromiseHandlerThrower); // Step 7. - Rooted reaction(cx, NewReactionRecord(cx, resultPromise, + Rooted reaction(cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected, - resolve, reject, IncumbentGlobalObject::Yes)); if (!reaction) return false; @@ -3827,8 +3899,7 @@ PerformPromiseThen(JSContext* cx, Handle promise, HandleValue on static MOZ_MUST_USE bool PerformPromiseThenWithoutSettleHandlers(JSContext* cx, Handle promise, Handle promiseToResolve, - HandleObject resultPromise, HandleObject resolve, - HandleObject reject) + Handle resultCapability) { // Step 1 (implicit). // Step 2 (implicit). @@ -3840,9 +3911,8 @@ PerformPromiseThenWithoutSettleHandlers(JSContext* cx, Handle pr HandleValue onRejected = NullHandleValue; // Step 7. - Rooted reaction(cx, NewReactionRecord(cx, resultPromise, + Rooted reaction(cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected, - resolve, reject, IncumbentGlobalObject::Yes)); if (!reaction) return false; @@ -3929,9 +3999,7 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis if (!C) return false; - RootedObject resultPromise(cx); - RootedObject resolveFun(cx); - RootedObject rejectFun(cx); + Rooted resultCapability(cx); // By default, the blocked promise is added as an extra entry to the // rejected promises list. @@ -3951,21 +4019,18 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis !PromiseHasAnyFlag(blockedPromise_->as(), PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) { - resultPromise.set(blockedPromise_); + resultCapability.promise().set(blockedPromise_); addToDependent = false; } else { // 25.4.5.3., step 4. - if (!NewPromiseCapability(cx, C, &resultPromise, &resolveFun, &rejectFun, true)) + if (!NewPromiseCapability(cx, C, &resultCapability, true)) return false; } // 25.4.5.3., step 5. Handle promise = promiseObj.as(); - if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, - resolveFun, rejectFun)) - { + if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultCapability)) return false; - } if (!addToDependent) return true; @@ -4098,9 +4163,12 @@ AddDummyPromiseReactionForDebugger(JSContext* cx, Handle promise if (promise->state() != JS::PromiseState::Pending) return true; - Rooted reaction(cx, NewReactionRecord(cx, dependentPromise, + // Leave resolve and reject as null. + Rooted capability(cx); + capability.promise().set(dependentPromise); + + Rooted reaction(cx, NewReactionRecord(cx, capability, NullHandleValue, NullHandleValue, - nullptr, nullptr, IncumbentGlobalObject::No)); if (!reaction) return false; diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h index 4d692f4a9f..e2fccbdd44 100644 --- a/js/src/vm/NativeObject-inl.h +++ b/js/src/vm/NativeObject-inl.h @@ -64,13 +64,19 @@ NativeObject::clearShouldConvertDoubleElements() } inline void -NativeObject::setDenseElementWithType(ExclusiveContext* cx, uint32_t index, const Value& val) +NativeObject::addDenseElementType(ExclusiveContext* cx, uint32_t index, const Value& val) { // Avoid a slow AddTypePropertyId call if the type is the same as the type // of the previous element. TypeSet::Type thisType = TypeSet::GetValueType(val); if (index == 0 || TypeSet::GetValueType(elements_[index - 1]) != thisType) AddTypePropertyId(cx, this, JSID_VOID, thisType); +} + +inline void +NativeObject::setDenseElementWithType(ExclusiveContext* cx, uint32_t index, const Value& val) +{ + addDenseElementType(cx, index, val); setDenseElementMaybeConvertDouble(index, val); } @@ -78,10 +84,9 @@ inline void NativeObject::initDenseElementWithType(ExclusiveContext* cx, uint32_t index, const Value& val) { MOZ_ASSERT(!shouldConvertDoubleElements()); - if (val.isMagic(JS_ELEMENTS_HOLE)) - markDenseElementsNotPacked(cx); - else - AddTypePropertyId(cx, this, JSID_VOID, val); + MOZ_ASSERT(!val.isMagic(JS_ELEMENTS_HOLE)); + + addDenseElementType(cx, index, val); initDenseElement(index, val); } diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index 030dcfb888..c5865caa03 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -1076,6 +1076,10 @@ class NativeObject : public ShapedObject setDenseElement(index, val); } + private: + inline void addDenseElementType(ExclusiveContext* cx, uint32_t index, const Value& val); + + public: inline void setDenseElementWithType(ExclusiveContext* cx, uint32_t index, const Value& val); inline void initDenseElementWithType(ExclusiveContext* cx, uint32_t index, From 5ff7a51da0ee94b1b0c7b957c6a902c568e9b967 Mon Sep 17 00:00:00 2001 From: Martok Date: Thu, 26 Jan 2023 22:33:28 +0100 Subject: [PATCH 17/21] Issue #2089 - Create a shared helper for PerformPromiseRace, PerformPromiseAll and PerformPromiseAllSettled Replace the previous non-CommonPerformPromiseAllRace port with the unified one Based-on: m-c 1475678/10,1539694 --- js/src/builtin/Promise.cpp | 1213 ++++++++++++++++++------------------ 1 file changed, 593 insertions(+), 620 deletions(-) diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index c29d6380e6..0d4ef98237 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -85,6 +85,11 @@ enum PromiseAllResolveElementFunctionSlots { PromiseAllResolveElementFunctionSlot_ElementIndex, }; +enum PromiseAllSettledElementFunctionSlots { + PromiseAllSettledElementFunctionSlot_Data = 0, + PromiseAllSettledElementFunctionSlot_ElementIndex, +}; + enum ReactionJobSlots { ReactionJobSlot_ReactionRecord = 0, }; @@ -1729,10 +1734,6 @@ static MOZ_MUST_USE bool AddPromiseReaction(JSContext* cx, Handle promise, Handle reaction); -static MOZ_MUST_USE bool -BlockOnPromise(JSContext* cx, HandleValue promise, HandleObject blockedPromise, - HandleValue onFulfilled, HandleValue onRejected, bool onFulfilledReturnsUndefined); - static JSFunction* GetResolveFunctionFromReject(JSFunction* reject) { @@ -2030,18 +2031,47 @@ static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, Handle resultCapability, bool* done); -// ES2016, 25.4.4.1. -static bool -Promise_static_all(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); +static MOZ_MUST_USE bool +PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, + Handle resultCapability, bool* done); + +static MOZ_MUST_USE bool PerformPromiseRace( + JSContext* cx, JS::ForOfIterator& iterator, HandleObject C, + Handle resultCapability, bool* done); + +enum class IterationMode { All, AllSettled, Race }; + +// ES2020 draft rev a09fc232c137800dbf51b6204f37fdede4ba1646 +// +// Unified implementation of +// 25.6.4.1 Promise.all ( iterable ) +// 25.6.4.3 Promise.race ( iterable ) +// +// Promise.allSettled (Stage 3 proposal) +// https://tc39.github.io/proposal-promise-allSettled/ +// +// Promise.allSettled ( iterable ) +static MOZ_MUST_USE bool CommonStaticAllRace(JSContext* cx, CallArgs& args, + IterationMode mode) { HandleValue iterable = args.get(0); // Step 2 (reordered). HandleValue CVal = args.thisv(); if (!CVal.isObject()) { + const char* message; + switch (mode) { + case IterationMode::All: + message = "Receiver of Promise.all call"; + break; + case IterationMode::AllSettled: + message = "Receiver of Promise.allSettled call"; + break; + case IterationMode::Race: + message = "Receiver of Promise.race call"; + break; + } JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, - "Receiver of Promise.all call"); + message); return false; } @@ -2059,16 +2089,38 @@ Promise_static_all(JSContext* cx, unsigned argc, Value* vp) return AbruptRejectPromise(cx, args, promiseCapability); if (!iter.valueIsIterable()) { + const char* message; + switch (mode) { + case IterationMode::All: + message = "Argument of Promise.all"; + break; + case IterationMode::AllSettled: + message = "Argument of Promise.allSettled"; + break; + case IterationMode::Race: + message = "Argument of Promise.race"; + break; + } JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, - "Argument of Promise.all"); + message); return AbruptRejectPromise(cx, args, promiseCapability); } // Step 6 (implicit). // Step 7. - bool done; - bool result = PerformPromiseAll(cx, iter, C, promiseCapability, &done); + bool done, result; + switch (mode) { + case IterationMode::All: + result = PerformPromiseAll(cx, iter, C, promiseCapability, &done); + break; + case IterationMode::AllSettled: + result = PerformPromiseAllSettled(cx, iter, C, promiseCapability, &done); + break; + case IterationMode::Race: + result = PerformPromiseRace(cx, iter, C, promiseCapability, &done); + break; + } // Step 8. if (!result) { @@ -2085,50 +2137,11 @@ Promise_static_all(JSContext* cx, unsigned argc, Value* vp) return true; } -static MOZ_MUST_USE bool -PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, - Handle resultCapability, bool* done); - -// ES2020 -static bool -Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - RootedValue iterable(cx, args.get(0)); - RootedValue CVal(cx, args.thisv()); - if (!CVal.isObject()) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, - "Receiver of Promise.allSettled call"); - return false; - } - - RootedObject C(cx, &CVal.toObject()); - Rooted promiseCapability(cx); - if (!NewPromiseCapability(cx, C, &promiseCapability, false)) - return false; - - JS::ForOfIterator iter(cx); - if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) - return AbruptRejectPromise(cx, args, promiseCapability); - - if (!iter.valueIsIterable()) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, - "Argument of Promise.allSettled"); - return AbruptRejectPromise(cx, args, promiseCapability); - } - - bool done; - bool result = PerformPromiseAllSettled(cx, iter, C, promiseCapability, &done); - - if (!result) { - if (!done) - iter.closeThrow(); - - return AbruptRejectPromise(cx, args, promiseCapability); - } - - args.rval().setObject(*promiseCapability.promise()); - return true; +// ES2020 draft rev a09fc232c137800dbf51b6204f37fdede4ba1646 +// 25.6.4.1 Promise.all ( iterable ) +static bool Promise_static_all(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CommonStaticAllRace(cx, args, IterationMode::All); } static MOZ_MUST_USE bool @@ -2141,8 +2154,6 @@ PerformPromiseThenWithoutSettleHandlers(JSContext* cx, Handle pr Handle resultCapability); static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); -static bool PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); -static bool PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp); // Unforgeable version of ES2016, 25.4.4.1. MOZ_MUST_USE JSObject* @@ -2303,18 +2314,206 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res return RejectPromiseInternal(cx, promise, result); } -// ES2016, 25.4.4.1.1. + +static bool +IsPromiseSpecies(JSContext* cx, JSFunction* species); + +// ES2019 draft rev dd269df67d37409a6f2099a842b8f5c75ee6fc24 +// 25.6.4.1.1 Runtime Semantics: PerformPromiseAll, step 6. +// 25.6.4.3.1 Runtime Semantics: PerformPromiseRace, step 3. +// +// Promise.allSettled (Stage 3 proposal) +// https://tc39.github.io/proposal-promise-allSettled/ +// Runtime Semantics: PerformPromiseAllSettled, step 6. +template +static MOZ_MUST_USE bool +CommonPerformPromiseAllRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, + HandleObject resultPromise, bool* done, bool resolveReturnsUndefined, + T getResolveAndReject) +{ + RootedObject promiseCtor(cx, GlobalObject::getOrCreatePromiseConstructor(cx, cx->global())); + if (!promiseCtor) + return false; + + RootedValue CVal(cx, ObjectValue(*C)); + RootedValue resolveFunVal(cx); + RootedValue rejectFunVal(cx); + + // We're reusing rooted variables in the loop below, so we don't need to + // declare a gazillion different rooted variables here. Rooted variables + // which are reused include "Or" in their name. + RootedValue nextValueOrNextPromise(cx); + RootedObject nextPromiseObj(cx); + RootedValue resolveOrThen(cx); + RootedObject thenSpeciesOrBlockedPromise(cx); + Rooted thenCapability(cx); + + while (true) { + // Steps a-c, e-g. + RootedValue& nextValue = nextValueOrNextPromise; + if (!iterator.next(&nextValue, done)) { + // Steps b, f. + *done = true; + + // Steps c, g. + return false; + } + + // Step d. + if (*done) + return true; + + // 25.6.4.1.1, step 6.i. + // 25.6.4.3.1, step 3.h. + // Sadly, because someone could have overridden + // "resolve" on the canonical Promise constructor. + RootedValue& staticResolve = resolveOrThen; + if (!GetProperty(cx, C, CVal, cx->names().resolve, &staticResolve)) + return false; + + RootedValue& nextPromise = nextValueOrNextPromise; + if (!Call(cx, staticResolve, CVal, nextValue, &nextPromise)) + return false; + + // Get the resolving functions for this iteration. + // 25.6.4.1.1, steps 6.j-q. + if (!getResolveAndReject(&resolveFunVal, &rejectFunVal)) + return false; + + // Call |nextPromise.then| with the provided hooks and add + // |resultPromise| to the list of dependent promises. + // + // If |nextPromise.then| is the original |Promise.prototype.then| + // function and the call to |nextPromise.then| would use the original + // |Promise| constructor to create the resulting promise, we skip the + // call to |nextPromise.then| and thus creating a new promise that + // would not be observable by content. + + // 25.6.4.1.1, step 6.r. + // 25.6.4.3.1, step 3.i. + nextPromiseObj = ToObject(cx, nextPromise); + if (!nextPromiseObj) + return false; + + RootedValue& thenVal = resolveOrThen; + if (!GetProperty(cx, nextPromiseObj, nextPromise, cx->names().then, &thenVal)) + return false; + + // By default, the blocked promise is added as an extra entry to the + // rejected promises list. + bool addToDependent = true; + + if (nextPromiseObj->is() && IsNativeFunction(thenVal, Promise_then)) { + // |nextPromise| is an unwrapped Promise, and |then| is the + // original |Promise.prototype.then|, inline it here. + + // 25.6.5.4, step 3. + RootedObject& thenSpecies = thenSpeciesOrBlockedPromise; + thenSpecies = SpeciesConstructor(cx, nextPromiseObj, JSProto_Promise, + IsPromiseSpecies); + if (!thenSpecies) + return false; + + // The fast path here and in NewPromiseCapability may not set + // the resolve and reject handlers, so we need to clear the fields + // in case they were set in the previous iteration. + thenCapability.resolve().set(nullptr); + thenCapability.reject().set(nullptr); + + // Skip the creation of a built-in Promise object if: + // 1. `thenSpecies` is the built-in Promise constructor. + // 2. `resolveFun` doesn't return an object, which ensures no + // side-effects take place in ResolvePromiseInternal. + // 3. The result promise is a built-in Promise object. + // 4. The result promise doesn't use the default resolving + // functions, which in turn means RunResolutionFunction when + // called from PromiseRectionJob won't try to resolve the + // promise. + if (thenSpecies == promiseCtor && + resolveReturnsUndefined && + resultPromise->is() && + !PromiseHasAnyFlag(resultPromise->as(), + PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) + { + thenCapability.promise().set(resultPromise); + addToDependent = false; + } else { + // 25.6.5.4, step 4. + if (!NewPromiseCapability(cx, thenSpecies, &thenCapability, true)) + return false; + } + + // 25.6.5.4, step 5. + Handle promise = nextPromiseObj.as(); + if (!PerformPromiseThen(cx, promise, resolveFunVal, rejectFunVal, thenCapability)) + return false; + } else { + // Optimization failed, do the normal call. + RootedValue& ignored = thenVal; + if (!Call(cx, thenVal, nextPromise, resolveFunVal, rejectFunVal, &ignored)) + return false; + + // In case the value to depend on isn't an object at all, there's + // nothing more to do here: we can only add reactions to Promise + // objects (potentially after unwrapping them), and non-object + // values can't be Promise objects. This can happen if Promise.all + // is called on an object with a `resolve` method that returns + // primitives. + if (!nextPromise.isObject()) + addToDependent = false; + } + + // Adds |resultPromise| to the list of dependent promises. + if (addToDependent) { + // The object created by the |promise.then| call or the inlined + // version of it above is visible to content (either because + // |promise.then| was overridden by content and could leak it, + // or because a constructor other than the original value of + // |Promise| was used to create it). To have both that object and + // |resultPromise| show up as dependent promises in the debugger, + // add a dummy reaction to the list of reject reactions that + // contains |resultPromise|, but otherwise does nothing. + RootedObject& blockedPromise = thenSpeciesOrBlockedPromise; + blockedPromise = resultPromise; + + mozilla::Maybe ac; + if (IsProxy(nextPromiseObj)) { + nextPromiseObj = CheckedUnwrap(nextPromiseObj); + if (!nextPromiseObj) { + return false; + } + if (JS_IsDeadWrapper(nextPromiseObj)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); + return false; + } + ac.emplace(cx, nextPromiseObj); + if (!cx->compartment()->wrap(cx, &blockedPromise)) + return false; + } + + // If either the object to depend on or the object that gets + // blocked isn't a, maybe-wrapped, Promise instance, we ignore it. + // All this does is lose some small amount of debug information in + // scenarios that are highly unlikely to occur in useful code. + if (nextPromiseObj->is() && resultPromise->is()) { + Handle promise = nextPromiseObj.as(); + if (!AddDummyPromiseReactionForDebugger(cx, promise, blockedPromise)) + return false; + } + } + } +} + +// ES2020 draft rev a09fc232c137800dbf51b6204f37fdede4ba1646 +// 25.6.4.1.1 PerformPromiseAll (iteratorRecord, constructor, resultCapability) static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, Handle resultCapability, bool* done) { *done = false; - HandleObject promiseObj = resultCapability.promise(); - // Step 1. MOZ_ASSERT(C->isConstructor()); - RootedValue CVal(cx, ObjectValue(*C)); // Step 2 (omitted). @@ -2340,8 +2539,8 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // PromiseAllResolveElement. RootedArrayObject valuesArray(cx); RootedValue valuesArrayVal(cx); - if (IsWrapper(promiseObj)) { - JSObject* unwrappedPromiseObj = CheckedUnwrap(promiseObj); + if (IsWrapper(resultCapability.promise())) { + JSObject* unwrappedPromiseObj = CheckedUnwrap(resultCapability.promise()); MOZ_ASSERT(unwrappedPromiseObj); { @@ -2368,7 +2567,7 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // remainingElementsCount (as an integer reserved slot), the array of // values, and the resolve function from our PromiseCapability. Rooted dataHolder(cx); - dataHolder = NewPromiseAllDataHolder(cx, promiseObj, valuesArrayVal, + dataHolder = NewPromiseAllDataHolder(cx, resultCapability.promise(), valuesArrayVal, resultCapability.resolve()); if (!dataHolder) return false; @@ -2376,41 +2575,10 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // Step 5. uint32_t index = 0; - // Step 6. - RootedValue nextValue(cx); - RootedValue nextPromise(cx); - RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject())); - RootedValue resolveFunVal(cx); - RootedValue staticResolve(cx); - - while (true) { - // Steps a-c, e-g. - if (!iterator.next(&nextValue, done)) { - // Steps b, f. - *done = true; - - // Steps c, g. - return false; - } - - // Step d. - if (*done) { - // Step d.i (implicit). - - // Step d.ii. - int32_t remainingCount = dataHolder->decreaseRemainingCount(); - - // Steps d.iii-iv. - if (remainingCount == 0) { - return RunResolutionFunction(cx, resultCapability.resolve(), valuesArrayVal, - ResolveMode, promiseObj); - } - - // We're all set for now! - return true; - } - - // Step h. + auto getResolveAndReject = [cx, &resultCapability, &valuesArray, &dataHolder, + &index](MutableHandleValue resolveFunVal, + MutableHandleValue rejectFunVal) { + // Step 6.h. { // Scope for the AutoCompartment we need to work with valuesArray. We // mostly do this for performance; we could go ahead and do the define via // a cross-compartment proxy instead... @@ -2420,163 +2588,48 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, return false; } - // Step i. - // Sadly, because someone could have overridden - // "resolve" on the canonical Promise constructor. - if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve)) - return false; - - FixedInvokeArgs<1> resolveArgs(cx); - resolveArgs[0].set(nextValue); - if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise)) - return false; - - // Step j. + // Steps 6.j-k. JSFunction* resolveFunc = NewNativeFunction(cx, PromiseAllResolveElementFunction, 1, nullptr,gc::AllocKind::FUNCTION_EXTENDED, GenericObject); if (!resolveFunc) return false; - // Steps k,m,n. + // Steps 6.l, 6.n-p. resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, ObjectValue(*dataHolder)); - // Step l. + // Step 6.m. resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, Int32Value(index)); - // Steps o-p. + // Step 6.q. dataHolder->increaseRemainingCount(); - // Step q. + // Step 6.s. + index++; + MOZ_ASSERT(index > 0); + resolveFunVal.setObject(*resolveFunc); - if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal, true)) - return false; + rejectFunVal.setObject(*resultCapability.reject()); + return true; + }; - // Step r. - index++; - MOZ_ASSERT(index > 0); - } -} - -static MOZ_MUST_USE bool -PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, - Handle resultCapability, bool* done) -{ - *done = false; - - HandleObject promiseObj = resultCapability.promise(); - - RootedValue CVal(cx, ObjectValue(*C)); - - RootedArrayObject valuesArray(cx); - RootedValue valuesArrayVal(cx); - if (IsWrapper(promiseObj)) { - JSObject* unwrappedPromiseObj = CheckedUnwrap(promiseObj); - MOZ_ASSERT(unwrappedPromiseObj); - - { - AutoCompartment ac(cx, unwrappedPromiseObj); - valuesArray = NewDenseEmptyArray(cx); - if (!valuesArray) - return false; - } - - valuesArrayVal.setObject(*valuesArray); - if (!cx->compartment()->wrap(cx, &valuesArrayVal)) - return false; - } else { - valuesArray = NewDenseEmptyArray(cx); - if (!valuesArray) - return false; - - valuesArrayVal.setObject(*valuesArray); - } - - Rooted dataHolder(cx); - dataHolder = NewPromiseAllDataHolder(cx, promiseObj, valuesArrayVal, - resultCapability.resolve()); - if (!dataHolder) + // Step 6. + if (!CommonPerformPromiseAllRace(cx, iterator, C, resultCapability.promise(), + done, true, getResolveAndReject)) return false; - RootedValue dataHolderVal(cx, ObjectValue(*dataHolder)); - uint32_t index = 0; + // Step 6.d.ii. + int32_t remainingCount = dataHolder->decreaseRemainingCount(); - RootedValue nextValue(cx); - - while (true) { - if (!iterator.next(&nextValue, done)) { - *done = true; - return false; - } - - if (*done) { - int32_t remainingCount = dataHolder->decreaseRemainingCount(); - if (remainingCount == 0) { - return RunResolutionFunction(cx, resultCapability.resolve(), valuesArrayVal, - ResolveMode, promiseObj); - } - return true; - } - - { // Scoped for AutoCompartment - JSAutoCompartment ac(cx, valuesArray); - - if (!NewbornArrayPush(cx, valuesArray, UndefinedValue())) - return false; - } - - RootedValue nextPromise(cx); - RootedValue staticResolve(cx); - RootedValue staticReject(cx); - - // Because Promise.allSettled can continue whether the promise is fulfilled or rejected, we - // should only return false if neither condition is true. - - if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve) && - !GetProperty(cx, CVal, cx->names().reject, &staticReject)) - return false; - - FixedInvokeArgs<1> resolveArgs(cx); - resolveArgs[0].set(nextValue); - FixedInvokeArgs<1> rejectArgs(cx); - rejectArgs[0].set(nextValue); - if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise) && - !Call(cx, staticReject, CVal, rejectArgs, &nextPromise)) - return false; - - - RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllSettledResolveElementFunction, - 1, nullptr, - gc::AllocKind::FUNCTION_EXTENDED, - GenericObject)); - - RootedFunction rejectFunc(cx, NewNativeFunction(cx, PromiseAllSettledRejectElementFunction, - 1, nullptr, - gc::AllocKind::FUNCTION_EXTENDED, - GenericObject)); - if (!resolveFunc && !rejectFunc) { - return false; - } - - resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); - resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, - Int32Value(index)); - rejectFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); - rejectFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, - Int32Value(index)); - - dataHolder->increaseRemainingCount(); - - RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc)); - RootedValue rejectFunVal(cx, ObjectValue(*rejectFunc)); - if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal, true)) - return false; - - index++; - MOZ_ASSERT(index > 0); + // Steps 6.d.iii-iv. + if (remainingCount == 0) { + return RunResolutionFunction(cx, resultCapability.resolve(), valuesArrayVal, ResolveMode, + resultCapability.promise()); } + + return true; } // ES2016, 25.4.4.1.2. @@ -2655,232 +2708,27 @@ PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) return true; } -// ES2020. -static bool -PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - RootedFunction resolve(cx, &args.callee().as()); - RootedValue xVal(cx, args.get(0)); - RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data)); - - if (dataVal.isUndefined()) { - args.rval().setUndefined(); - return true; - } - - Rooted data(cx, &dataVal.toObject().as()); - - resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue()); - - int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex) - .toInt32(); - - RootedValue valuesVal(cx, data->valuesArray()); - RootedObject valuesObj(cx, &valuesVal.toObject()); - if (IsProxy(valuesObj)) { - // See comment for PerformPromiseAll, step 3 for why we unwrap here. - valuesObj = UncheckedUnwrap(valuesObj); - - if (JS_IsDeadWrapper(valuesObj)) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); - return false; - } - - AutoCompartment ac(cx, valuesObj); - if (!cx->compartment()->wrap(cx, &xVal)) - return false; - } - NativeObject* values = &valuesObj->as(); - - // The index is guaranteed to be initialized to `undefined`. - MOZ_ASSERT(values->getDenseElement(index).isUndefined()); - - RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); - if (!obj) { - return false; - } - RootedId id(cx, NameToId(cx->names().status)); - RootedValue statusValue(cx); - statusValue.setString(cx->names().fulfilled); - if (!::JS_DefinePropertyById(cx, obj, id, statusValue, JSPROP_ENUMERATE)) { - return false; - } - id = NameToId(cx->names().value); - if (!::JS_DefinePropertyById(cx, obj, id, xVal, JSPROP_ENUMERATE)) { - return false; - } - - RootedValue objVal(cx, ObjectValue(*obj)); -/* if (needsWrapping) { - AutoRealm ar(cx, valuesObj); - if (!cx->compartment()->wrap(cx, &objVal)) { - return false; - } - } */ - values->setDenseElement(index, objVal); - - uint32_t remainingCount = data->decreaseRemainingCount(); - - if (remainingCount == 0) { - RootedObject resolveAllFun(cx, data->resolveObj()); - RootedObject promiseObj(cx, data->promiseObj()); - if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj)) - return false; - } - - args.rval().setUndefined(); - return true; -} - -static bool -PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - RootedFunction resolve(cx, &args.callee().as()); - RootedValue xVal(cx, args.get(0)); - RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data)); - - if (dataVal.isUndefined()) { - args.rval().setUndefined(); - return true; - } - - Rooted data(cx, &dataVal.toObject().as()); - - resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue()); - - int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex) - .toInt32(); - - RootedValue valuesVal(cx, data->valuesArray()); - RootedObject valuesObj(cx, &valuesVal.toObject()); - if (IsProxy(valuesObj)) { - // See comment for PerformPromiseAll, step 3 for why we unwrap here. - valuesObj = UncheckedUnwrap(valuesObj); - - if (JS_IsDeadWrapper(valuesObj)) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); - return false; - } - - AutoCompartment ac(cx, valuesObj); - if (!cx->compartment()->wrap(cx, &xVal)) - return false; - } - NativeObject* values = &valuesObj->as(); - - // The index is guaranteed to be initialized to `undefined`. - MOZ_ASSERT(values->getDenseElement(index).isUndefined()); - - RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); - if (!obj) { - return false; - } - RootedId id(cx, NameToId(cx->names().status)); - RootedValue statusValue(cx); - statusValue.setString(cx->names().rejected); - if (!::JS_DefinePropertyById(cx, obj, id, statusValue, JSPROP_ENUMERATE)) { - return false; - } - RootedValue resultValue(cx); - id = NameToId(cx->names().reason); - if (!::JS_DefinePropertyById(cx, obj, id, xVal, JSPROP_ENUMERATE)) { - return false; - } - - RootedValue objVal(cx, ObjectValue(*obj)); -/* if (needsWrapping) { - AutoRealm ar(cx, valuesObj); - if (!cx->compartment()->wrap(cx, &objVal)) { - return false; - } - } */ - values->setDenseElement(index, objVal); - - - uint32_t remainingCount = data->decreaseRemainingCount(); - - if (remainingCount == 0) { - RootedObject resolveAllFun(cx, data->resolveObj()); - RootedObject promiseObj(cx, data->promiseObj()); - if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj)) - return false; - } - - args.rval().setUndefined(); - return true; -} - -static MOZ_MUST_USE bool -PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, - Handle resultCapability, bool* done); - -// ES2016, 25.4.4.3. +// ES2020 draft rev a09fc232c137800dbf51b6204f37fdede4ba1646 +// 25.6.4.3 Promise.race ( iterable ) static bool Promise_static_race(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - HandleValue iterable = args.get(0); - - // Step 2 (reordered). - HandleValue CVal = args.thisv(); - if (!CVal.isObject()) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, - "Receiver of Promise.race call"); - return false; - } - - // Step 1. - RootedObject C(cx, &CVal.toObject()); - - // Step 3. - Rooted promiseCapability(cx); - if (!NewPromiseCapability(cx, C, &promiseCapability, false)) - return false; - - // Steps 4-5. - JS::ForOfIterator iter(cx); - if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) - return AbruptRejectPromise(cx, args, promiseCapability); - - if (!iter.valueIsIterable()) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, - "Argument of Promise.race"); - return AbruptRejectPromise(cx, args, promiseCapability); - } - - // Step 6 (implicit). - - // Step 7. - bool done; - bool result = PerformPromiseRace(cx, iter, C, promiseCapability, &done); - - // Step 8. - if (!result) { - // Step 8.a. - if (!done) - iter.closeThrow(); - - // Step 8.b. - return AbruptRejectPromise(cx, args, promiseCapability); - } - - // Step 9. - args.rval().setObject(*promiseCapability.promise()); - return true; + return CommonStaticAllRace(cx, args, IterationMode::Race); } -// ES2016, 25.4.4.3.1. +// ES2020 draft rev a09fc232c137800dbf51b6204f37fdede4ba1646 +// 25.6.4.3.1 PerformPromiseRace (iteratorRecord, constructor, resultCapability) static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, Handle resultCapability, bool* done) { *done = false; + + // Step 1. MOZ_ASSERT(C->isConstructor()); - RootedValue CVal(cx, ObjectValue(*C)); + + // Step 2 (omitted). // BlockOnPromise fast path requires the passed onFulfilled function // doesn't return an object value, because otherwise the skipped promise @@ -2888,55 +2736,302 @@ PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, bool isDefaultResolveFn = IsNativeFunction(resultCapability.resolve(), ResolvePromiseFunction); - HandleObject promiseObj = resultCapability.promise(); - RootedValue resolveFunVal(cx, ObjectValue(*resultCapability.resolve())); - RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject())); + auto getResolveAndReject = [&resultCapability]( + MutableHandleValue resolveFunVal, + MutableHandleValue rejectFunVal) { + resolveFunVal.setObject(*resultCapability.resolve()); + rejectFunVal.setObject(*resultCapability.reject()); + return true; + }; - RootedValue nextValue(cx); - RootedValue nextPromise(cx); - RootedValue staticResolve(cx); - - while (true) { - // Steps a-c, e-g. - if (!iterator.next(&nextValue, done)) { - // Steps b, f. - *done = true; - - // Steps c, g. - return false; - } - - // Step d. - if (*done) { - // Step d.i (implicit). - - // Step d.ii. - return true; - } - - // Step h. - // Sadly, because someone could have overridden - // "resolve" on the canonical Promise constructor. - if (!GetProperty(cx, C, CVal, cx->names().resolve, &staticResolve)) - return false; - - FixedInvokeArgs<1> resolveArgs(cx); - resolveArgs[0].set(nextValue); - if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise)) - return false; - - // Step i. - if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal, - isDefaultResolveFn)) - { - return false; - } - } - - MOZ_ASSERT_UNREACHABLE("Shouldn't reach the end of PerformPromiseRace"); + // Step 3. + return CommonPerformPromiseAllRace(cx, iterator, C, + resultCapability.promise(), done, + isDefaultResolveFn, getResolveAndReject); } -// ES2016, Sub-steps of 25.4.4.4 and 25.4.4.5. +enum class PromiseAllSettledElementFunctionKind { Resolve, Reject }; + +// Promise.allSettled (Stage 3 proposal) +// https://tc39.github.io/proposal-promise-allSettled/ +// +// Promise.allSettled Resolve Element Functions +// Promise.allSettled Reject Element Functions +template +static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc, + Value* vp); + +// Promise.allSettled (Stage 3 proposal) +// https://tc39.github.io/proposal-promise-allSettled/ +// +// Promise.allSettled ( iterable ) +static bool Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CommonStaticAllRace(cx, args, IterationMode::AllSettled); +} + +// Promise.allSettled (Stage 3 proposal) +// https://tc39.github.io/proposal-promise-allSettled/ +// +// PerformPromiseAllSettled ( iteratorRecord, constructor, resultCapability ) +static MOZ_MUST_USE bool PerformPromiseAllSettled( + JSContext* cx, JS::ForOfIterator& iterator, HandleObject C, + Handle resultCapability, bool* done) { + *done = false; + + // Step 1. + MOZ_ASSERT(C->isConstructor()); + + // Step 2 (omitted). + + // Step 3. + // See the big comment in PerformPromiseAll about which objects should be + // created in which compartments. + RootedArrayObject valuesArray(cx); + RootedValue valuesArrayVal(cx); + if (IsWrapper(resultCapability.promise())) { + JSObject* unwrappedPromiseObj = CheckedUnwrap(resultCapability.promise()); + MOZ_ASSERT(unwrappedPromiseObj); + + { + AutoCompartment ac(cx, unwrappedPromiseObj); + valuesArray = NewDenseEmptyArray(cx); + if (!valuesArray) + return false; + } + + valuesArrayVal.setObject(*valuesArray); + if (!cx->compartment()->wrap(cx, &valuesArrayVal)) { + return false; + } + } else { + valuesArray = NewDenseEmptyArray(cx); + if (!valuesArray) + return false; + + valuesArrayVal.setObject(*valuesArray); + } + + // Step 4. + // Create our data holder that holds all the things shared across every step + // of the iterator. In particular, this holds the remainingElementsCount + // (as an integer reserved slot), the array of values, and the resolve + // function from our PromiseCapability. + Rooted dataHolder(cx); + dataHolder = + NewPromiseAllDataHolder(cx, resultCapability.promise(), valuesArrayVal, + resultCapability.resolve()); + if (!dataHolder) { + return false; + } + + // Step 5. + uint32_t index = 0; + + auto getResolveAndReject = [cx, &valuesArray, &dataHolder, &index]( + MutableHandleValue resolveFunVal, + MutableHandleValue rejectFunVal) { + // Step 6.h. + { // Scope for the AutoCompartment we need to work with valuesArray. We + // mostly do this for performance; we could go ahead and do the define via + // a cross-compartment proxy instead... + AutoCompartment ac(cx, valuesArray); + + if (!NewbornArrayPush(cx, valuesArray, UndefinedValue())) { + return false; + } + } + + auto PromiseAllSettledResolveElementFunction = + PromiseAllSettledElementFunction< + PromiseAllSettledElementFunctionKind::Resolve>; + auto PromiseAllSettledRejectElementFunction = + PromiseAllSettledElementFunction< + PromiseAllSettledElementFunctionKind::Reject>; + + // Steps 6.j-m. + JSFunction* resolveFunc = NewNativeFunction( + cx, PromiseAllSettledResolveElementFunction, 1, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject); + if (!resolveFunc) { + return false; + } + resolveFunVal.setObject(*resolveFunc); + + // Steps 6.o-q. + resolveFunc->setExtendedSlot(PromiseAllSettledElementFunctionSlot_Data, + ObjectValue(*dataHolder)); + + // Step 6.n. + resolveFunc->setExtendedSlot( + PromiseAllSettledElementFunctionSlot_ElementIndex, Int32Value(index)); + + // Steps 6.r-t. + JSFunction* rejectFunc = NewNativeFunction( + cx, PromiseAllSettledRejectElementFunction, 1, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject); + if (!rejectFunc) { + return false; + } + rejectFunVal.setObject(*rejectFunc); + + // Steps 6.v-x. + rejectFunc->setExtendedSlot(PromiseAllSettledElementFunctionSlot_Data, + ObjectValue(*dataHolder)); + + // Step 6.u. + rejectFunc->setExtendedSlot( + PromiseAllSettledElementFunctionSlot_ElementIndex, Int32Value(index)); + + // Step 6.y. + dataHolder->increaseRemainingCount(); + + // Step 6.aa. + index++; + MOZ_ASSERT(index > 0); + + return true; + }; + + // Step 6. + if (!CommonPerformPromiseAllRace(cx, iterator, C, resultCapability.promise(), + done, true, getResolveAndReject)) { + return false; + } + + // Step 6.d.ii. + int32_t remainingCount = dataHolder->decreaseRemainingCount(); + + // Steps 6.d.iii-iv. + if (remainingCount == 0) { + return RunResolutionFunction(cx, resultCapability.resolve(), valuesArrayVal, + ResolveMode, resultCapability.promise()); + } + + return true; +} + +// Promise.allSettled (Stage 3 proposal) +// https://tc39.github.io/proposal-promise-allSettled/ +// +// Promise.allSettled Resolve Element Functions +// Promise.allSettled Reject Element Functions +template +static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + HandleValue valueOrReason = args.get(0); + + // Step 1. + JSFunction* resolve = &args.callee().as(); + Rooted data( + cx, &resolve->getExtendedSlot(PromiseAllSettledElementFunctionSlot_Data) + .toObject() + .as()); + + // Steps 2-4 (moved below). + + // Step 5. + int32_t index = + resolve + ->getExtendedSlot(PromiseAllSettledElementFunctionSlot_ElementIndex) + .toInt32(); + + // Step 6. + RootedValue valuesVal(cx, data->valuesArray()); + RootedObject valuesObj(cx, &valuesVal.toObject()); + bool needsWrapping = false; + if (IsProxy(valuesObj)) { + // See comment for PerformPromiseAllSettled, step 3 for why we unwrap here. + valuesObj = UncheckedUnwrap(valuesObj); + + if (JS_IsDeadWrapper(valuesObj)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_DEAD_OBJECT); + return false; + } + + needsWrapping = true; + } + HandleNativeObject values = valuesObj.as(); + + // Steps 2-3. + // We use the element value as a signal for whether the Promise was already + // fulfilled. Upon resolution, it's set to the result object created below. + if (!values->getDenseElement(index).isUndefined()) { + args.rval().setUndefined(); + return true; + } + + // Steps 7-8 (moved below). + + // Step 9. + RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); + if (!obj) { + return false; + } + + // Step 10. + RootedId id(cx, NameToId(cx->names().status)); + RootedValue statusValue(cx); + if (Kind == PromiseAllSettledElementFunctionKind::Resolve) { + statusValue.setString(cx->names().fulfilled); + } else { + statusValue.setString(cx->names().rejected); + } + if (!::JS_DefinePropertyById(cx, obj, id, statusValue, JSPROP_ENUMERATE)) { + return false; + } + + // Step 11. + if (Kind == PromiseAllSettledElementFunctionKind::Resolve) { + id = NameToId(cx->names().value); + } else { + id = NameToId(cx->names().reason); + } + if (!::JS_DefinePropertyById(cx, obj, id, valueOrReason, JSPROP_ENUMERATE)) { + return false; + } + + RootedValue objVal(cx, ObjectValue(*obj)); + if (needsWrapping) { + AutoCompartment ac(cx, valuesObj); + if (!cx->compartment()->wrap(cx, &objVal)) { + return false; + } + } + + // Steps 4, 12. + values->setDenseElement(index, objVal); + + // Steps 8, 13. + uint32_t remainingCount = data->decreaseRemainingCount(); + + // Step 14. + if (remainingCount == 0) { + // Step 14.a. (Omitted, happened in PerformPromiseAllSettled.) + // Step 14.b. + + // Step 7 (Adapted to work with PromiseAllDataHolder's layout). + RootedObject resolveAllFun(cx, data->resolveObj()); + RootedObject promiseObj(cx, data->promiseObj()); + if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, + promiseObj)) { + return false; + } + } + + // Step 15. + args.rval().setUndefined(); + return true; +} + +// https://tc39.github.io/ecma262/#sec-promise.reject +// +// Unified implementation of +// 25.6.4.4 Promise.reject ( r ) +// 25.6.4.5 Promise.resolve ( x ) +// 25.6.4.5.1 PromiseResolve ( C, x ) static MOZ_MUST_USE JSObject* CommonStaticResolveRejectImpl(JSContext* cx, HandleValue thisVal, HandleValue argVal, ResolutionMode mode) @@ -3966,128 +4061,6 @@ PerformPromiseThenWithReaction(JSContext* cx, Handle promise, return true; } -/** - * Calls |promise.then| with the provided hooks and adds |blockedPromise| to - * its list of dependent promises. Used by |Promise.all| and |Promise.race|. - * - * If |promise.then| is the original |Promise.prototype.then| function and - * the call to |promise.then| would use the original |Promise| constructor to - * create the resulting promise, this function skips the call to |promise.then| - * and thus creating a new promise that would not be observable by content. - */ -static MOZ_MUST_USE bool -BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromise_, - HandleValue onFulfilled, HandleValue onRejected, bool onFulfilledReturnsUndefined) -{ - RootedObject promiseObj(cx, ToObject(cx, promiseVal)); - if (!promiseObj) - return false; - - RootedValue thenVal(cx); - if (!GetProperty(cx, promiseObj, promiseVal, cx->names().then, &thenVal)) - return false; - - if (promiseObj->is() && IsNativeFunction(thenVal, Promise_then)) { - // |promise| is an unwrapped Promise, and |then| is the original - // |Promise.prototype.then|, inline it here. - // 25.4.5.3., step 3. - RootedObject PromiseCtor(cx); - if (!GetBuiltinConstructor(cx, JSProto_Promise, &PromiseCtor)) - return false; - - RootedObject C(cx, SpeciesConstructor(cx, PromiseCtor, JSProto_Promise, IsPromiseSpecies)); - if (!C) - return false; - - Rooted resultCapability(cx); - - // By default, the blocked promise is added as an extra entry to the - // rejected promises list. - bool addToDependent = true; - - // Skip the creation of a built-in Promise object if: - // 1. `C` is the built-in Promise constructor. - // 2. The `onFulfilled` handler doesn't return an object, which - // ensures no side-effects take place in ResolvePromiseInternal. - // 3. The blocked promise is a built-in Promise object. - // 4. The blocked promise doesn't use the default resolving functions, - // which in turn means RunResolutionFunction when called from - // PromiseRectionJob won't try to resolve the promise. - if (C == PromiseCtor && - onFulfilledReturnsUndefined && - blockedPromise_->is() && - !PromiseHasAnyFlag(blockedPromise_->as(), - PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) - { - resultCapability.promise().set(blockedPromise_); - addToDependent = false; - } else { - // 25.4.5.3., step 4. - if (!NewPromiseCapability(cx, C, &resultCapability, true)) - return false; - } - - // 25.4.5.3., step 5. - Handle promise = promiseObj.as(); - if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultCapability)) - return false; - - if (!addToDependent) - return true; - } else { - // Optimization failed, do the normal call. - RootedValue rval(cx); - if (!Call(cx, thenVal, promiseVal, onFulfilled, onRejected, &rval)) - return false; - } - - // In case the value to depend on isn't an object at all, there's nothing - // more to do here: we can only add reactions to Promise objects - // (potentially after unwrapping them), and non-object values can't be - // Promise objects. This can happen if Promise.all is called on an object - // with a `resolve` method that returns primitives. - if (!promiseVal.isObject()) - return true; - - // The object created by the |promise.then| call or the inlined version - // of it above is visible to content (either because |promise.then| was - // overridden by content and could leak it, or because a constructor - // other than the original value of |Promise| was used to create it). - // To have both that object and |blockedPromise| show up as dependent - // promises in the debugger, add a dummy reaction to the list of reject - // reactions that contains |blockedPromise|, but otherwise does nothing. - RootedObject unwrappedPromiseObj(cx, promiseObj); - RootedObject blockedPromise(cx, blockedPromise_); - - mozilla::Maybe ac; - if (IsProxy(promiseObj)) { - unwrappedPromiseObj = CheckedUnwrap(promiseObj); - if (!unwrappedPromiseObj) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_ACCESS_DENIED); - return false; - } - if (JS_IsDeadWrapper(unwrappedPromiseObj)) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); - return false; - } - ac.emplace(cx, unwrappedPromiseObj); - if (!cx->compartment()->wrap(cx, &blockedPromise)) - return false; - } - - // If either the object to depend on or the object that gets blocked isn't - // a, maybe-wrapped, Promise instance, we ignore it. All this does is lose - // some small amount of debug information in scenarios that are highly - // unlikely to occur in useful code. - if (!unwrappedPromiseObj->is()) - return true; - if (!blockedPromise_->is()) - return true; - - Handle promise = unwrappedPromiseObj.as(); - return AddDummyPromiseReactionForDebugger(cx, promise, blockedPromise); -} - static MOZ_MUST_USE bool AddPromiseReaction(JSContext* cx, Handle promise, Handle reaction) From 078daa705affc2e72fff741d6ad9134310a8c4f8 Mon Sep 17 00:00:00 2001 From: Martok Date: Fri, 27 Jan 2023 00:09:15 +0100 Subject: [PATCH 18/21] Issue #2089 - Further runtime perf improvements for PromiseState - Reduce slot size of PromiseReactionRecord to fit into OBJECT8 alloc kind - Use the Call() helper from Interpreter.h to reduce code duplication - Clear the incumbent global slot when it's no longer used Based-on: m-c 1475678/{12-14} --- js/src/builtin/Promise.cpp | 90 +++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 0d4ef98237..ffb778696e 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -471,8 +471,14 @@ enum ReactionRecordSlots { // the |PromiseHandler| enum, or null. If the value is null, either the // REACTION_FLAG_DEBUGGER_DUMMY or the // REACTION_FLAG_DEFAULT_RESOLVING_HANDLER flag must be set. + // + // After setting the target state for a PromiseReaction, the slot of the + // no longer used handler gets reused to store the argument of the active + // handler. ReactionRecordSlot_OnFulfilled, + ReactionRecordSlot_OnRejectedArg = ReactionRecordSlot_OnFulfilled, ReactionRecordSlot_OnRejected, + ReactionRecordSlot_OnFulfilledArg = ReactionRecordSlot_OnRejected, // The functions to resolve or reject the promise. Matches the // [[Capability]].[[Resolve]] and [[Capability]].[[Reject]] fields from @@ -490,9 +496,6 @@ enum ReactionRecordSlots { // Bitmask of the REACTION_FLAG values. ReactionRecordSlot_Flags, - // Argument when calling the function stored in OnFulfilled or OnRejected. - ReactionRecordSlot_HandlerArg, - // Additional slot to store extra data for specific reaction record types. // // - When the REACTION_FLAG_ASYNC_GENERATOR flag is set, this slot store @@ -522,6 +525,20 @@ class PromiseReactionRecord : public NativeObject setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); } + uint32_t handlerSlot() { + MOZ_ASSERT(targetState() != JS::PromiseState::Pending); + return targetState() == JS::PromiseState::Fulfilled + ? ReactionRecordSlot_OnFulfilled + : ReactionRecordSlot_OnRejected; + } + + uint32_t handlerArgSlot() { + MOZ_ASSERT(targetState() != JS::PromiseState::Pending); + return targetState() == JS::PromiseState::Fulfilled + ? ReactionRecordSlot_OnFulfilledArg + : ReactionRecordSlot_OnRejectedArg; + } + public: static const Class class_; @@ -535,14 +552,17 @@ class PromiseReactionRecord : public NativeObject ? JS::PromiseState::Fulfilled : JS::PromiseState::Rejected; } - void setTargetState(JS::PromiseState state) { - int32_t flags = this->flags(); - MOZ_ASSERT(!(flags & REACTION_FLAG_RESOLVED)); + void setTargetStateAndHandlerArg(JS::PromiseState state, const Value& arg) { + MOZ_ASSERT(targetState() == JS::PromiseState::Pending); MOZ_ASSERT(state != JS::PromiseState::Pending, "Can't revert a reaction to pending."); + + int32_t flags = this->flags(); flags |= REACTION_FLAG_RESOLVED; if (state == JS::PromiseState::Fulfilled) flags |= REACTION_FLAG_FULFILLED; + setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); + setFixedSlot(handlerArgSlot(), arg); } void setIsDefaultResolvingHandler(PromiseObject* promiseToResolve) { setFlagOnInitialState(REACTION_FLAG_DEFAULT_RESOLVING_HANDLER); @@ -586,21 +606,16 @@ class PromiseReactionRecord : public NativeObject } Value handler() { MOZ_ASSERT(targetState() != JS::PromiseState::Pending); - uint32_t slot = targetState() == JS::PromiseState::Fulfilled - ? ReactionRecordSlot_OnFulfilled - : ReactionRecordSlot_OnRejected; - return getFixedSlot(slot); + return getFixedSlot(handlerSlot()); } Value handlerArg() { MOZ_ASSERT(targetState() != JS::PromiseState::Pending); - return getFixedSlot(ReactionRecordSlot_HandlerArg); + return getFixedSlot(handlerArgSlot()); } - void setHandlerArg(Value& arg) { - MOZ_ASSERT(targetState() == JS::PromiseState::Pending); - setFixedSlot(ReactionRecordSlot_HandlerArg, arg); - } - JSObject* incumbentGlobalObject() { - return getFixedSlot(ReactionRecordSlot_IncumbentGlobalObject).toObjectOrNull(); + JSObject* getAndClearIncumbentGlobalObject() { + JSObject* obj = getFixedSlot(ReactionRecordSlot_IncumbentGlobalObject).toObjectOrNull(); + setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject, UndefinedValue()); + return obj; } }; @@ -908,11 +923,9 @@ EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj, MOZ_ASSERT(reaction->targetState() == JS::PromiseState::Pending); assertSameCompartment(cx, handlerArg); - reaction->setHandlerArg(handlerArg.get()); + reaction->setTargetStateAndHandlerArg(targetState, handlerArg); RootedValue reactionVal(cx, ObjectValue(*reaction)); - - reaction->setTargetState(targetState); RootedValue handler(cx, reaction->handler()); // If we have a handler callback, we enter that handler's compartment so @@ -968,7 +981,7 @@ EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj, // much better than having to store the original global as a private value // because we couldn't wrap it to store it as a normal JS value. RootedObject global(cx); - if (JSObject* objectFromIncumbentGlobal = reaction->incumbentGlobalObject()) { + if (JSObject* objectFromIncumbentGlobal = reaction->getAndClearIncumbentGlobalObject()) { objectFromIncumbentGlobal = CheckedUnwrap(objectFromIncumbentGlobal); MOZ_ASSERT(objectFromIncumbentGlobal); global = &objectFromIncumbentGlobal->global(); @@ -1502,9 +1515,7 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) MOZ_ASSERT(IsCallable(handlerVal)); // Step 6. - FixedInvokeArgs<1> args2(cx); - args2[0].set(argument); - if (!Call(cx, handlerVal, UndefinedHandleValue, args2, &handlerResult)) { + if (!Call(cx, handlerVal, UndefinedHandleValue, argument, &handlerResult)) { resolutionMode = RejectMode; if (!MaybeGetAndClearException(cx, &handlerResult)) return false; @@ -1573,11 +1584,8 @@ PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp) if (!MaybeGetAndClearException(cx, &rval)) return false; - FixedInvokeArgs<1> rejectArgs(cx); - rejectArgs[0].set(rval); - RootedValue rejectVal(cx, ObjectValue(*rejectFn)); - return Call(cx, rejectVal, UndefinedHandleValue, rejectArgs, &rval); + return Call(cx, rejectVal, UndefinedHandleValue, rval, &rval); } static MOZ_MUST_USE bool @@ -1989,7 +1997,6 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto / bool success; { FixedInvokeArgs<2> args(cx); - args[0].setObject(*resolveFn); args[1].setObject(*rejectFn); @@ -2003,12 +2010,8 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto / if (!MaybeGetAndClearException(cx, &exceptionVal)) return nullptr; - FixedInvokeArgs<1> args(cx); - - args[0].set(exceptionVal); - RootedValue calleeOrRval(cx, ObjectValue(*rejectFn)); - if (!Call(cx, calleeOrRval, UndefinedHandleValue, args, &calleeOrRval)) + if (!Call(cx, calleeOrRval, UndefinedHandleValue, exceptionVal, &calleeOrRval)) return nullptr; } @@ -2293,9 +2296,7 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res assertSameCompartment(cx, promiseObj); if (resolutionFun) { RootedValue calleeOrRval(cx, ObjectValue(*resolutionFun)); - FixedInvokeArgs<1> resolveArgs(cx); - resolveArgs[0].set(result); - return Call(cx, calleeOrRval, UndefinedHandleValue, resolveArgs, &calleeOrRval); + return Call(cx, calleeOrRval, UndefinedHandleValue, result, &calleeOrRval); } if (!promiseObj) @@ -3516,11 +3517,8 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co // 11.1.3.2.1 steps 5-6 (partially). // 11.1.3.2.2, 11.1.3.2.3 steps 8-9. RootedValue iterVal(cx, ObjectValue(*iter)); - FixedInvokeArgs<1> args2(cx); - args2[0].set(args.get(0)); - RootedValue resultVal(cx); - if (!js::Call(cx, func, iterVal, args2, &resultVal)) + if (!Call(cx, func, iterVal, args.get(0), &resultVal)) return AbruptRejectPromise(cx, args, resultPromise, nullptr); // 11.1.3.2.1 steps 5-6 (partially). @@ -4245,11 +4243,8 @@ PromiseObject::resolve(JSContext* cx, Handle promise, HandleValu if (!cx->compartment()->wrap(cx, &funVal)) return false; - FixedInvokeArgs<1> args(cx); - args[0].set(resolutionValue); - RootedValue dummy(cx); - return Call(cx, funVal, UndefinedHandleValue, args, &dummy); + return Call(cx, funVal, UndefinedHandleValue, resolutionValue, &dummy); } /* static */ bool @@ -4265,11 +4260,8 @@ PromiseObject::reject(JSContext* cx, Handle promise, HandleValue RootedValue funVal(cx, promise->getFixedSlot(PromiseSlot_RejectFunction)); MOZ_ASSERT(IsCallable(funVal)); - FixedInvokeArgs<1> args(cx); - args[0].set(rejectionValue); - RootedValue dummy(cx); - return Call(cx, funVal, UndefinedHandleValue, args, &dummy); + return Call(cx, funVal, UndefinedHandleValue, rejectionValue, &dummy); } /* static */ void From a215929a31280c205c9c3b68964c862551becf1f Mon Sep 17 00:00:00 2001 From: Martok Date: Fri, 27 Jan 2023 19:31:17 +0100 Subject: [PATCH 19/21] Issue #2089 - Report unhandled rejection for optimized away promise Based-on: m-c 1498775 --- js/src/builtin/Promise.cpp | 19 ++++++++++- .../promise-rejection-tracking-optimized.js | 34 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 js/src/tests/non262/Promise/promise-rejection-tracking-optimized.js diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index ffb778696e..8447a97881 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -2299,8 +2299,25 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res return Call(cx, calleeOrRval, UndefinedHandleValue, result, &calleeOrRval); } - if (!promiseObj) + if (!promiseObj) { + if (mode == RejectMode) { + // The rejection will never be handled, given the returned promise + // is known to be unused, and already optimized away. + // + // Create temporary Promise object and reject it, in order to + // report the unhandled rejection. + // + // Allocation time points wrong time, but won't matter much. + Rooted temporaryPromise(cx); + temporaryPromise = CreatePromiseObjectWithoutResolutionFunctions(cx); + if (!temporaryPromise) { + cx->clearPendingException(); + return true; + } + return RejectPromiseInternal(cx, temporaryPromise, result); + } return true; + } Handle promise = promiseObj.as(); if (promise->state() != JS::PromiseState::Pending) diff --git a/js/src/tests/non262/Promise/promise-rejection-tracking-optimized.js b/js/src/tests/non262/Promise/promise-rejection-tracking-optimized.js new file mode 100644 index 0000000000..c085b9dba8 --- /dev/null +++ b/js/src/tests/non262/Promise/promise-rejection-tracking-optimized.js @@ -0,0 +1,34 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs setPromiseRejectionTrackerCallback + +const UNHANDLED = 0; +const HANDLED = 1; + +let rejections = new Map(); +function rejectionTracker(promise, state) { + rejections.set(promise, state); +} +setPromiseRejectionTrackerCallback(rejectionTracker); + +// If the return value of then is not used, the promise object is optimized +// away, but if a rejection happens, the rejection should be notified. +Promise.resolve().then(() => { throw 1; }); +drainJobQueue(); + +assertEq(rejections.size, 1); + +let [[promise, state]] = rejections; +assertEq(state, UNHANDLED); + +let exc; +promise.catch(x => { exc = x; }); +drainJobQueue(); + +// we handled it after all +assertEq(rejections.get(promise), HANDLED); + +// the right exception was reported +assertEq(exc, 1); + +if (this.reportCompare) { + reportCompare(true,true); +} From 22dc363c3a17ca58612736c21603bfa5baa577e1 Mon Sep 17 00:00:00 2001 From: Job Bautista Date: Sat, 28 Jan 2023 19:32:24 +0800 Subject: [PATCH 20/21] Issue #2083 - Follow-up: RegExpShared::source should be a GCPtr. A hangover from before RegExpShared was a GC thing, as noted by m-c 1399928. --- js/src/vm/RegExpObject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 28adbb36fc..4f35908087 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -128,7 +128,7 @@ class RegExpShared : public gc::TenuredCell }; /* Source to the RegExp, for lazy compilation. */ - HeapPtr source; + GCPtr source; RegExpFlag flags; bool canStringMatch; From 10ef0da5a09d876e544d8b297589a492e8c4f6aa Mon Sep 17 00:00:00 2001 From: Job Bautista Date: Tue, 31 Jan 2023 20:03:09 +0800 Subject: [PATCH 21/21] Issue #2099 - Remove unnecessary cast in GeneratorObject::suspend's do loop. This resolves build bustage with --enable-debug. --- js/src/vm/GeneratorObject.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/vm/GeneratorObject.cpp b/js/src/vm/GeneratorObject.cpp index aad29b910d..be6a30a60a 100644 --- a/js/src/vm/GeneratorObject.cpp +++ b/js/src/vm/GeneratorObject.cpp @@ -86,8 +86,8 @@ GeneratorObject::suspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame if (genObj->hasExpressionStack()) { MOZ_ASSERT(genObj->expressionStack().getDenseInitializedLength() == 0); auto result = SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, - &genObj->expressionStack().as(), - 0, vp, nvalues, ShouldUpdateTypes::DontUpdate); + &genObj->expressionStack(), 0, vp, nvalues, + ShouldUpdateTypes::DontUpdate); if (result == DenseElementResult::Success) { MOZ_ASSERT(genObj->expressionStack().getDenseInitializedLength() == nvalues); break;