diff options
Diffstat (limited to '')
46 files changed, 2273 insertions, 394 deletions
diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 2fe4f01f8d..14039af574 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -2830,7 +2830,7 @@ bool InnerViewTable::addView(JSContext* cx, ArrayBufferObject* buffer, if (isNurseryView && !hadNurseryViews && nurseryKeysValid) { #ifdef DEBUG if (nurseryKeys.length() < 100) { - for (auto* key : nurseryKeys) { + for (const auto& key : nurseryKeys) { MOZ_ASSERT(key != buffer); } } @@ -2859,31 +2859,53 @@ void InnerViewTable::removeViews(ArrayBufferObject* buffer) { map.remove(ptr); } -bool InnerViewTable::traceWeak(JSTracer* trc) { return map.traceWeak(trc); } +bool InnerViewTable::traceWeak(JSTracer* trc) { + nurseryKeys.traceWeak(trc); + map.traceWeak(trc); + return true; +} void InnerViewTable::sweepAfterMinorGC(JSTracer* trc) { MOZ_ASSERT(needsSweepAfterMinorGC()); - if (nurseryKeysValid) { - for (size_t i = 0; i < nurseryKeys.length(); i++) { - ArrayBufferObject* buffer = nurseryKeys[i]; + NurseryKeysVector keys; + bool valid = true; + std::swap(nurseryKeys, keys); + std::swap(nurseryKeysValid, valid); + + // Use nursery keys vector if possible. + if (valid) { + for (ArrayBufferObject* buffer : keys) { MOZ_ASSERT(!gc::IsInsideNursery(buffer)); auto ptr = map.lookup(buffer); - if (ptr && !ptr->value().sweepAfterMinorGC(trc)) { + if (ptr && !sweepViewsAfterMinorGC(trc, buffer, ptr->value())) { map.remove(ptr); } } - } else { - for (ArrayBufferViewMap::Enum e(map); !e.empty(); e.popFront()) { - MOZ_ASSERT(!gc::IsInsideNursery(e.front().key())); - if (!e.front().value().sweepAfterMinorGC(trc)) { - e.removeFront(); - } + return; + } + + // Otherwise look at every map entry. + for (ArrayBufferViewMap::Enum e(map); !e.empty(); e.popFront()) { + MOZ_ASSERT(!gc::IsInsideNursery(e.front().key())); + if (!sweepViewsAfterMinorGC(trc, e.front().key(), e.front().value())) { + e.removeFront(); } } +} + +bool InnerViewTable::sweepViewsAfterMinorGC(JSTracer* trc, + ArrayBufferObject* buffer, + Views& views) { + if (!views.sweepAfterMinorGC(trc)) { + return false; // No more views. + } - nurseryKeys.clear(); - nurseryKeysValid = true; + if (views.hasNurseryViews() && !nurseryKeys.append(buffer)) { + nurseryKeysValid = false; + } + + return true; } size_t InnerViewTable::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h index 5aa96bf887..ce78b26cb2 100644 --- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -760,16 +760,20 @@ class InnerViewTable { StableCellHasher<JSObject*>, ZoneAllocPolicy>; ArrayBufferViewMap map; - // List of keys from innerViews where either the source or at least one - // target is in the nursery. The raw pointer to a JSObject is allowed here - // because this vector is cleared after every minor collection. Users in - // sweepAfterMinorCollection must be careful to use MaybeForwarded before - // touching these pointers. - Vector<ArrayBufferObject*, 0, SystemAllocPolicy> nurseryKeys; + // List of keys from map where either the source or at least one target is in + // the nursery. The raw pointer to a JSObject is allowed here because this + // vector is cleared after every minor collection. Users in sweepAfterMinorGC + // must be careful to use MaybeForwarded before touching these pointers. + using NurseryKeysVector = + GCVector<UnsafeBarePtr<ArrayBufferObject*>, 0, SystemAllocPolicy>; + NurseryKeysVector nurseryKeys; // Whether nurseryKeys is a complete list. bool nurseryKeysValid = true; + bool sweepMapEntryAfterMinorGC(UnsafeBarePtr<JSObject*>& buffer, + ViewVector& views); + public: explicit InnerViewTable(Zone* zone) : map(zone) {} @@ -793,6 +797,9 @@ class InnerViewTable { ArrayBufferViewObject* view); ViewVector* maybeViewsUnbarriered(ArrayBufferObject* buffer); void removeViews(ArrayBufferObject* buffer); + + bool sweepViewsAfterMinorGC(JSTracer* trc, ArrayBufferObject* buffer, + Views& views); }; template <typename Wrapper> diff --git a/js/src/vm/ArrayBufferViewObject.cpp b/js/src/vm/ArrayBufferViewObject.cpp index 27004f3e2a..37272a94af 100644 --- a/js/src/vm/ArrayBufferViewObject.cpp +++ b/js/src/vm/ArrayBufferViewObject.cpp @@ -221,7 +221,6 @@ void ArrayBufferViewObject::computeResizableLengthAndByteOffset( size_t bytesPerElement) { MOZ_ASSERT(!isSharedMemory()); MOZ_ASSERT(hasBuffer()); - MOZ_ASSERT(!bufferUnshared()->isLengthPinned()); MOZ_ASSERT(bufferUnshared()->isResizable()); size_t byteOffsetStart = initialByteOffset(); diff --git a/js/src/vm/AsyncFunction.cpp b/js/src/vm/AsyncFunction.cpp index ce523fe409..f6ed33c738 100644 --- a/js/src/vm/AsyncFunction.cpp +++ b/js/src/vm/AsyncFunction.cpp @@ -151,7 +151,7 @@ static bool AsyncFunctionResume(JSContext* cx, if (!CallSelfHostedFunction(cx, funName, generatorOrValue, args, &generatorOrValue)) { if (!generator->isClosed()) { - generator->setClosed(); + generator->setClosed(cx); } // Handle the OOM case mentioned above. diff --git a/js/src/vm/AsyncIteration.cpp b/js/src/vm/AsyncIteration.cpp index 8f8902a64a..33546dc9dd 100644 --- a/js/src/vm/AsyncIteration.cpp +++ b/js/src/vm/AsyncIteration.cpp @@ -1071,7 +1071,7 @@ bool js::AsyncGeneratorThrow(JSContext* cx, unsigned argc, Value* vp) { if (!CallSelfHostedFunction(cx, funName, thisOrRval, args, &thisOrRval)) { // 25.5.3.2, steps 5.f, 5.g. if (!generator->isClosed()) { - generator->setClosed(); + generator->setClosed(cx); } return AsyncGeneratorThrown(cx, generator); } diff --git a/js/src/vm/AtomsTable.h b/js/src/vm/AtomsTable.h index aae7728fe5..28e448ce77 100644 --- a/js/src/vm/AtomsTable.h +++ b/js/src/vm/AtomsTable.h @@ -35,6 +35,82 @@ struct AtomHasher { } }; +struct js::AtomHasher::Lookup { + union { + const JS::Latin1Char* latin1Chars; + const char16_t* twoByteChars; + const char* utf8Bytes; + }; + enum { TwoByteChar, Latin1, UTF8 } type; + size_t length; + size_t byteLength; + const JSAtom* atom; /* Optional. */ + JS::AutoCheckCannotGC nogc; + + HashNumber hash; + + MOZ_ALWAYS_INLINE Lookup(const char* utf8Bytes, size_t byteLen, size_t length, + HashNumber hash) + : utf8Bytes(utf8Bytes), + type(UTF8), + length(length), + byteLength(byteLen), + atom(nullptr), + hash(hash) {} + + MOZ_ALWAYS_INLINE Lookup(const char16_t* chars, size_t length) + : twoByteChars(chars), + type(TwoByteChar), + length(length), + atom(nullptr), + hash(mozilla::HashString(chars, length)) {} + + MOZ_ALWAYS_INLINE Lookup(const JS::Latin1Char* chars, size_t length) + : latin1Chars(chars), + type(Latin1), + length(length), + atom(nullptr), + hash(mozilla::HashString(chars, length)) {} + + MOZ_ALWAYS_INLINE Lookup(HashNumber hash, const char16_t* chars, + size_t length) + : twoByteChars(chars), + type(TwoByteChar), + length(length), + atom(nullptr), + hash(hash) { + MOZ_ASSERT(hash == mozilla::HashString(chars, length)); + } + + MOZ_ALWAYS_INLINE Lookup(HashNumber hash, const JS::Latin1Char* chars, + size_t length) + : latin1Chars(chars), + type(Latin1), + length(length), + atom(nullptr), + hash(hash) { + MOZ_ASSERT(hash == mozilla::HashString(chars, length)); + } + + inline explicit Lookup(const JSAtom* atom) + : type(atom->hasLatin1Chars() ? Latin1 : TwoByteChar), + length(atom->length()), + atom(atom), + hash(atom->hash()) { + if (type == Latin1) { + latin1Chars = atom->latin1Chars(nogc); + MOZ_ASSERT(mozilla::HashString(latin1Chars, length) == hash); + } else { + MOZ_ASSERT(type == TwoByteChar); + twoByteChars = atom->twoByteChars(nogc); + MOZ_ASSERT(mozilla::HashString(twoByteChars, length) == hash); + } + } + + // Return: true iff the string in |atom| matches the string in this Lookup. + bool StringsMatch(const JSAtom& atom) const; +}; + // Note: Use a 'class' here to make forward declarations easier to use. class AtomSet : public JS::GCHashSet<WeakHeapPtr<JSAtom*>, AtomHasher, SystemAllocPolicy> { diff --git a/js/src/vm/BigIntType.h b/js/src/vm/BigIntType.h index fb9f4085e6..8959f93c77 100644 --- a/js/src/vm/BigIntType.h +++ b/js/src/vm/BigIntType.h @@ -419,7 +419,7 @@ class BigInt final : public js::gc::CellWithLengthAndFlags { static JSLinearString* toStringGeneric(JSContext* cx, Handle<BigInt*>, unsigned radix); - friend struct ::JSStructuredCloneReader; // So it can call the following: + friend struct ::JSStructuredCloneReader; // So it can call the following: static BigInt* destructivelyTrimHighZeroDigits(JSContext* cx, BigInt* x); bool absFitsInUint64() const { return digitLength() <= 64 / DigitBits; } diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index d4376ec6a4..5fa3f2b633 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -29,6 +29,7 @@ MACRO_(allowContentIter, "allowContentIter") \ MACRO_(allowContentIterWith, "allowContentIterWith") \ MACRO_(allowContentIterWithNext, "allowContentIterWithNext") \ + MACRO_(alphabet, "alphabet") \ MACRO_(ambiguous, "ambiguous") \ MACRO_(anonymous, "anonymous") \ MACRO_(Any, "Any") \ @@ -214,6 +215,8 @@ MACRO_(frame, "frame") \ MACRO_(from, "from") \ MACRO_(fromAsync, "fromAsync") \ + MACRO_(fromBase64, "fromBase64") \ + MACRO_(fromHex, "fromHex") \ MACRO_(fulfilled, "fulfilled") \ MACRO_(GatherAsyncParentCompletions, "GatherAsyncParentCompletions") \ MACRO_(gcCycleNumber, "gcCycleNumber") \ @@ -319,6 +322,7 @@ MACRO_(isoMinute, "isoMinute") \ MACRO_(isoMonth, "isoMonth") \ MACRO_(isoNanosecond, "isoNanosecond") \ + MACRO_(isRawJSON, "isRawJSON") \ MACRO_(isoSecond, "isoSecond") \ MACRO_(isoYear, "isoYear") \ MACRO_(isStepStart, "isStepStart") \ @@ -338,6 +342,7 @@ MACRO_(label, "label") \ MACRO_(language, "language") \ MACRO_(largestUnit, "largestUnit") \ + MACRO_(lastChunkHandling, "lastChunkHandling") \ MACRO_(lastIndex, "lastIndex") \ MACRO_(length, "length") \ MACRO_(let, "let") \ @@ -467,6 +472,8 @@ MACRO_(pull, "pull") \ MACRO_(quarter, "quarter") \ MACRO_(raw, "raw") \ + MACRO_(rawJSON, "rawJSON") \ + MACRO_(read, "read") \ MACRO_(reason, "reason") \ MACRO_(RegExp_String_Iterator_, "RegExp String Iterator") \ MACRO_(RegExp_prototype_Exec, "RegExp_prototype_Exec") \ @@ -503,6 +510,8 @@ MACRO_(SetConstructorInit, "SetConstructorInit") \ MACRO_(SetIsInlinableLargeFunction, "SetIsInlinableLargeFunction") \ MACRO_(Set_Iterator_, "Set Iterator") \ + MACRO_(setFromBase64, "setFromBase64") \ + MACRO_(setFromHex, "setFromHex") \ MACRO_(setPrototypeOf, "setPrototypeOf") \ MACRO_(shape, "shape") \ MACRO_(shared, "shared") \ @@ -540,7 +549,9 @@ MACRO_(timeStyle, "timeStyle") \ MACRO_(timeZone, "timeZone") \ MACRO_(timeZoneName, "timeZoneName") \ + MACRO_(toBase64, "toBase64") \ MACRO_(toGMTString, "toGMTString") \ + MACRO_(toHex, "toHex") \ MACRO_(toISOString, "toISOString") \ MACRO_(toJSON, "toJSON") \ MACRO_(toLocaleString, "toLocaleString") \ @@ -612,6 +623,7 @@ MACRO_(weeks, "weeks") \ MACRO_(while_, "while") \ MACRO_(with, "with") \ + MACRO_(written, "written") \ MACRO_(toReversed, "toReversed") \ MACRO_(toSorted, "toSorted") \ MACRO_(toSpliced, "toSpliced") \ diff --git a/js/src/vm/FrameIter.cpp b/js/src/vm/FrameIter.cpp index 3dd50c2fcf..9a665e6b9d 100644 --- a/js/src/vm/FrameIter.cpp +++ b/js/src/vm/FrameIter.cpp @@ -124,7 +124,12 @@ JS::Realm* JitFrameIter::realm() const { return asWasm().instance()->realm(); } - return asJSJit().script()->realm(); + if (asJSJit().isScripted()) { + return asJSJit().script()->realm(); + } + + MOZ_RELEASE_ASSERT(asJSJit().isTrampolineNative()); + return asJSJit().callee()->realm(); } uint8_t* JitFrameIter::resumePCinCurrentFrame() const { diff --git a/js/src/vm/FunctionFlags.h b/js/src/vm/FunctionFlags.h index d927056230..27d51c214a 100644 --- a/js/src/vm/FunctionFlags.h +++ b/js/src/vm/FunctionFlags.h @@ -116,9 +116,10 @@ class FunctionFlags { // This flag is used only by scripted functions and AsmJS. LAMBDA = 1 << 9, - // The WASM function has a JIT entry which emulates the - // js::BaseScript::jitCodeRaw mechanism. - WASM_JIT_ENTRY = 1 << 10, + // This Native function has a JIT entry which emulates the + // js::BaseScript::jitCodeRaw mechanism. Used for Wasm functions and + // TrampolineNative builtins. + NATIVE_JIT_ENTRY = 1 << 10, // Function had no explicit name, but a name was set by SetFunctionName at // compile time or SetFunctionName at runtime. @@ -238,7 +239,7 @@ class FunctionFlags { switch (kind()) { case FunctionKind::NormalFunction: MOZ_ASSERT(!hasFlags(LAZY_ACCESSOR_NAME)); - MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY)); + MOZ_ASSERT(!hasFlags(NATIVE_JIT_ENTRY)); break; case FunctionKind::Arrow: @@ -246,38 +247,38 @@ class FunctionFlags { MOZ_ASSERT(!hasFlags(CONSTRUCTOR)); MOZ_ASSERT(!hasFlags(LAZY_ACCESSOR_NAME)); MOZ_ASSERT(hasFlags(LAMBDA)); - MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY)); + MOZ_ASSERT(!hasFlags(NATIVE_JIT_ENTRY)); break; case FunctionKind::Method: MOZ_ASSERT(hasFlags(BASESCRIPT) || hasFlags(SELFHOSTLAZY)); MOZ_ASSERT(!hasFlags(CONSTRUCTOR)); MOZ_ASSERT(!hasFlags(LAZY_ACCESSOR_NAME)); MOZ_ASSERT(!hasFlags(LAMBDA)); - MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY)); + MOZ_ASSERT(!hasFlags(NATIVE_JIT_ENTRY)); break; case FunctionKind::ClassConstructor: MOZ_ASSERT(hasFlags(BASESCRIPT) || hasFlags(SELFHOSTLAZY)); MOZ_ASSERT(hasFlags(CONSTRUCTOR)); MOZ_ASSERT(!hasFlags(LAZY_ACCESSOR_NAME)); MOZ_ASSERT(!hasFlags(LAMBDA)); - MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY)); + MOZ_ASSERT(!hasFlags(NATIVE_JIT_ENTRY)); break; case FunctionKind::Getter: MOZ_ASSERT(!hasFlags(CONSTRUCTOR)); MOZ_ASSERT(!hasFlags(LAMBDA)); - MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY)); + MOZ_ASSERT(!hasFlags(NATIVE_JIT_ENTRY)); break; case FunctionKind::Setter: MOZ_ASSERT(!hasFlags(CONSTRUCTOR)); MOZ_ASSERT(!hasFlags(LAMBDA)); - MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY)); + MOZ_ASSERT(!hasFlags(NATIVE_JIT_ENTRY)); break; case FunctionKind::AsmJS: MOZ_ASSERT(!hasFlags(BASESCRIPT)); MOZ_ASSERT(!hasFlags(SELFHOSTLAZY)); MOZ_ASSERT(!hasFlags(LAZY_ACCESSOR_NAME)); - MOZ_ASSERT(!hasFlags(WASM_JIT_ENTRY)); + MOZ_ASSERT(!hasFlags(NATIVE_JIT_ENTRY)); break; case FunctionKind::Wasm: MOZ_ASSERT(!hasFlags(BASESCRIPT)); @@ -316,10 +317,11 @@ class FunctionFlags { MOZ_ASSERT_IF(kind() == Wasm, isNativeFun()); return kind() == Wasm; } - bool isWasmWithJitEntry() const { - MOZ_ASSERT_IF(hasFlags(WASM_JIT_ENTRY), isWasm()); - return hasFlags(WASM_JIT_ENTRY); + bool isNativeWithJitEntry() const { + MOZ_ASSERT_IF(hasFlags(NATIVE_JIT_ENTRY), isNativeFun()); + return hasFlags(NATIVE_JIT_ENTRY); } + bool isWasmWithJitEntry() const { return isWasm() && isNativeWithJitEntry(); } bool isNativeWithoutJitEntry() const { MOZ_ASSERT_IF(!hasJitEntry(), isNativeFun()); return !hasJitEntry(); @@ -328,7 +330,14 @@ class FunctionFlags { return isNativeFun() && !isAsmJSNative() && !isWasm(); } bool hasJitEntry() const { - return hasBaseScript() || hasSelfHostedLazyScript() || isWasmWithJitEntry(); + return hasBaseScript() || hasSelfHostedLazyScript() || + isNativeWithJitEntry(); + } + + bool canHaveJitInfo() const { + // A native builtin can have a pointer to either its JitEntry or JSJitInfo, + // but not both. + return isBuiltinNative() && !isNativeWithJitEntry(); } /* Possible attributes of an interpreted function: */ @@ -417,7 +426,7 @@ class FunctionFlags { return clearFlags(LAZY_ACCESSOR_NAME); } - FunctionFlags& setWasmJitEntry() { return setFlags(WASM_JIT_ENTRY); } + FunctionFlags& setNativeJitEntry() { return setFlags(NATIVE_JIT_ENTRY); } bool isExtended() const { return hasFlags(EXTENDED); } FunctionFlags& setIsExtended() { return setFlags(EXTENDED); } @@ -430,7 +439,7 @@ class FunctionFlags { static uint16_t HasJitEntryFlags(bool isConstructing) { uint16_t flags = BASESCRIPT | SELFHOSTLAZY; if (!isConstructing) { - flags |= WASM_JIT_ENTRY; + flags |= NATIVE_JIT_ENTRY; } return flags; } diff --git a/js/src/vm/GeckoProfiler.cpp b/js/src/vm/GeckoProfiler.cpp index 330bcd1fa6..dbf1eb9081 100644 --- a/js/src/vm/GeckoProfiler.cpp +++ b/js/src/vm/GeckoProfiler.cpp @@ -57,9 +57,15 @@ static jit::JitFrameLayout* GetTopProfilingJitFrame(jit::JitActivation* act) { return nullptr; } + // Skip if the activation has no JS frames. This can happen if there's only a + // TrampolineNative frame because these are skipped by the profiling frame + // iterator. jit::JSJitProfilingFrameIterator jitIter( (jit::CommonFrameLayout*)iter.frame().fp()); - MOZ_ASSERT(!jitIter.done()); + if (jitIter.done()) { + return nullptr; + } + return jitIter.framePtr(); } diff --git a/js/src/vm/GeneratorObject.cpp b/js/src/vm/GeneratorObject.cpp index 4f8d807df5..4a23c4ca9e 100644 --- a/js/src/vm/GeneratorObject.cpp +++ b/js/src/vm/GeneratorObject.cpp @@ -194,10 +194,10 @@ void AbstractGeneratorObject::dump() const { } #endif -void AbstractGeneratorObject::finalSuspend(HandleObject obj) { +void AbstractGeneratorObject::finalSuspend(JSContext* cx, HandleObject obj) { auto* genObj = &obj->as<AbstractGeneratorObject>(); MOZ_ASSERT(genObj->isRunning()); - genObj->setClosed(); + genObj->setClosed(cx); } static AbstractGeneratorObject* GetGeneratorObjectForCall(JSContext* cx, @@ -442,6 +442,16 @@ void AbstractGeneratorObject::setUnaliasedLocal(uint32_t slot, return stackStorage().setDenseElement(slot, value); } +void AbstractGeneratorObject::setClosed(JSContext* cx) { + setFixedSlot(CALLEE_SLOT, NullValue()); + setFixedSlot(ENV_CHAIN_SLOT, NullValue()); + setFixedSlot(ARGS_OBJ_SLOT, NullValue()); + setFixedSlot(STACK_STORAGE_SLOT, NullValue()); + setFixedSlot(RESUME_INDEX_SLOT, NullValue()); + + DebugAPI::onGeneratorClosed(cx, this); +} + bool AbstractGeneratorObject::isAfterYield() { return isAfterYieldOrAwait(JSOp::Yield); } diff --git a/js/src/vm/GeneratorObject.h b/js/src/vm/GeneratorObject.h index ddc11ba781..b952ebd533 100644 --- a/js/src/vm/GeneratorObject.h +++ b/js/src/vm/GeneratorObject.h @@ -57,7 +57,7 @@ class AbstractGeneratorObject : public NativeObject { static bool suspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame, const jsbytecode* pc, unsigned nvalues); - static void finalSuspend(HandleObject obj); + static void finalSuspend(JSContext* cx, HandleObject obj); JSFunction& callee() const { return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>(); @@ -149,13 +149,7 @@ class AbstractGeneratorObject : public NativeObject { return getFixedSlot(RESUME_INDEX_SLOT).toInt32(); } bool isClosed() const { return getFixedSlot(CALLEE_SLOT).isNull(); } - void setClosed() { - setFixedSlot(CALLEE_SLOT, NullValue()); - setFixedSlot(ENV_CHAIN_SLOT, NullValue()); - setFixedSlot(ARGS_OBJ_SLOT, NullValue()); - setFixedSlot(STACK_STORAGE_SLOT, NullValue()); - setFixedSlot(RESUME_INDEX_SLOT, NullValue()); - } + void setClosed(JSContext* cx); bool isAfterYield(); bool isAfterAwait(); diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index a296336385..97286fde51 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -1056,10 +1056,7 @@ class GlobalObject : public NativeObject { void setSourceURLsHolder(ArrayObject* holder) { data().sourceURLsHolder = holder; } - void clearSourceURLSHolder() { - // This is called at the start of shrinking GCs, so avoids barriers. - data().sourceURLsHolder.unbarrieredSet(nullptr); - } + void clearSourceURLSHolder() { setSourceURLsHolder(nullptr); } SharedShape* maybeArrayShapeWithDefaultProto() const { return data().arrayShapeWithDefaultProto; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 9eec4b81dd..f4cdc86f18 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1273,7 +1273,7 @@ bool js::HandleClosingGeneratorReturn(JSContext* cx, AbstractFramePtr frame, cx->clearPendingException(); ok = true; auto* genObj = GetGeneratorObjectForFrame(cx, frame); - genObj->setClosed(); + genObj->setClosed(cx); } return ok; } @@ -4164,7 +4164,7 @@ bool MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER js::Interpret(JSContext* cx, CASE(FinalYieldRval) { ReservedRooted<JSObject*> gen(&rootObject0, ®S.sp[-1].toObject()); REGS.sp--; - AbstractGeneratorObject::finalSuspend(gen); + AbstractGeneratorObject::finalSuspend(cx, gen); goto successful_return_continuation; } diff --git a/js/src/vm/InvalidatingFuse.cpp b/js/src/vm/InvalidatingFuse.cpp index 3e14d72fcc..cae8875a19 100644 --- a/js/src/vm/InvalidatingFuse.cpp +++ b/js/src/vm/InvalidatingFuse.cpp @@ -22,7 +22,8 @@ js::DependentScriptSet::DependentScriptSet(JSContext* cx, bool js::InvalidatingRuntimeFuse::addFuseDependency(JSContext* cx, Handle<JSScript*> script) { auto* zone = script->zone(); - DependentScriptSet* dss = zone->getOrCreateDependentScriptSet(cx, this); + DependentScriptSet* dss = + zone->fuseDependencies.getOrCreateDependentScriptSet(cx, this); if (!dss) { return false; } @@ -80,3 +81,20 @@ bool js::DependentScriptSet::addScriptForFuse(InvalidatingFuse* fuse, // Script is already in the set, no need to re-add. return true; } + +js::DependentScriptSet* js::DependentScriptGroup::getOrCreateDependentScriptSet( + JSContext* cx, js::InvalidatingFuse* fuse) { + for (auto& dss : dependencies) { + if (dss.associatedFuse == fuse) { + return &dss; + } + } + + if (!dependencies.emplaceBack(cx, fuse)) { + return nullptr; + } + + auto& dss = dependencies.back(); + MOZ_ASSERT(dss.associatedFuse == fuse); + return &dss; +} diff --git a/js/src/vm/InvalidatingFuse.h b/js/src/vm/InvalidatingFuse.h index 6fc458ee79..b8760e8e0f 100644 --- a/js/src/vm/InvalidatingFuse.h +++ b/js/src/vm/InvalidatingFuse.h @@ -10,7 +10,7 @@ #include "gc/Barrier.h" #include "gc/SweepingAPI.h" #include "vm/GuardFuse.h" -#include "vm/JSScript.h" +class JSScript; namespace js { @@ -40,10 +40,6 @@ class InvalidatingRuntimeFuse : public InvalidatingFuse { // A (weak) set of scripts which are dependent on an associated fuse. // -// These are typically stored in a vector at the moment, due to the low number -// of invalidating fuses, and so the associated fuse is stored along with the -// set. -// // Because it uses JS::WeakCache, GC tracing is taken care of without any need // for tracing in this class. class DependentScriptSet { @@ -61,6 +57,24 @@ class DependentScriptSet { js::WeakCache<WeakScriptSet> weakScripts; }; +class DependentScriptGroup { + // A dependent script set pairs a fuse with a set of scripts which depend + // on said fuse; this is a vector of script sets because the expectation for + // now is that the number of runtime wide invalidating fuses will be small. + // This will need to be revisited (convert to HashMap?) should that no + // longer be the case + // + // Note: This isn't traced through the zone, but rather through the use + // of JS::WeakCache. + Vector<DependentScriptSet, 1, SystemAllocPolicy> dependencies; + + public: + DependentScriptSet* getOrCreateDependentScriptSet(JSContext* cx, + InvalidatingFuse* fuse); + DependentScriptSet* begin() { return dependencies.begin(); } + DependentScriptSet* end() { return dependencies.end(); } +}; + } // namespace js #endif // vm_InvalidatingFuse_h diff --git a/js/src/vm/Iteration.cpp b/js/src/vm/Iteration.cpp index d02f9de8cf..e36ea8b555 100644 --- a/js/src/vm/Iteration.cpp +++ b/js/src/vm/Iteration.cpp @@ -1940,10 +1940,113 @@ static const JSFunctionSpec iterator_methods_with_helpers[] = { JS_FS_END, }; +// https://tc39.es/proposal-iterator-helpers/#sec-SetterThatIgnoresPrototypeProperties +static bool SetterThatIgnoresPrototypeProperties(JSContext* cx, + Handle<Value> thisv, + Handle<PropertyKey> prop, + Handle<Value> value) { + // Step 1. + Rooted<JSObject*> thisObj(cx, + RequireObject(cx, JSMSG_OBJECT_REQUIRED, thisv)); + if (!thisObj) { + return false; + } + + // Step 2. + Rooted<JSObject*> home( + cx, GlobalObject::getOrCreateIteratorPrototype(cx, cx->global())); + if (!home) { + return false; + } + if (thisObj == home) { + UniqueChars propName = + IdToPrintableUTF8(cx, prop, IdToPrintableBehavior::IdIsPropertyKey); + if (!propName) { + return false; + } + + // Step 2.b. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READ_ONLY, + propName.get()); + return false; + } + + // Step 3. + Rooted<Maybe<PropertyDescriptor>> desc(cx); + if (!GetOwnPropertyDescriptor(cx, thisObj, prop, &desc)) { + return false; + } + + // Step 4. + if (desc.isNothing()) { + // Step 4.a. + return DefineDataProperty(cx, thisObj, prop, value, JSPROP_ENUMERATE); + } + + // Step 5. + return SetProperty(cx, thisObj, prop, value); +} + +// https://tc39.es/proposal-iterator-helpers/#sec-get-iteratorprototype-@@tostringtag +static bool toStringTagGetter(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + args.rval().setString(cx->names().Iterator); + return true; +} + +// https://tc39.es/proposal-iterator-helpers/#sec-set-iteratorprototype-@@tostringtag +static bool toStringTagSetter(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + Rooted<PropertyKey> prop( + cx, PropertyKey::Symbol(cx->wellKnownSymbols().toStringTag)); + if (!SetterThatIgnoresPrototypeProperties(cx, args.thisv(), prop, + args.get(0))) { + return false; + } + + // Step 2. + args.rval().setUndefined(); + return true; +} + +// https://tc39.es/proposal-iterator-helpers/#sec-get-iteratorprototype-constructor +static bool constructorGetter(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + Rooted<JSObject*> constructor( + cx, GlobalObject::getOrCreateConstructor(cx, JSProto_Iterator)); + if (!constructor) { + return false; + } + args.rval().setObject(*constructor); + return true; +} + +// https://tc39.es/proposal-iterator-helpers/#sec-set-iteratorprototype-constructor +static bool constructorSetter(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + Rooted<PropertyKey> prop(cx, NameToId(cx->names().constructor)); + if (!SetterThatIgnoresPrototypeProperties(cx, args.thisv(), prop, + args.get(0))) { + return false; + } + + // Step 2. + args.rval().setUndefined(); + return true; +} + static const JSPropertySpec iterator_properties[] = { - // NOTE: Contrary to most other @@toStringTag properties, this property is - // writable. - JS_STRING_SYM_PS(toStringTag, "Iterator", 0), + // NOTE: Contrary to most other @@toStringTag properties, this property + // has a special setter (and a getter). + JS_SYM_GETSET(toStringTag, toStringTagGetter, toStringTagSetter, 0), JS_PS_END, }; @@ -2081,7 +2184,7 @@ static const ClassSpec IteratorObjectClassSpec = { nullptr, iterator_methods_with_helpers, iterator_properties, - nullptr, + IteratorObject::finishInit, }; const JSClass IteratorObject::class_ = { @@ -2098,6 +2201,13 @@ const JSClass IteratorObject::protoClass_ = { &IteratorObjectClassSpec, }; +/* static */ bool IteratorObject::finishInit(JSContext* cx, HandleObject ctor, + HandleObject proto) { + Rooted<PropertyKey> id(cx, NameToId(cx->names().constructor)); + return JS_DefinePropertyById(cx, proto, id, constructorGetter, + constructorSetter, 0); +} + // Set up WrapForValidIteratorObject class and its prototype. static const JSFunctionSpec wrap_for_valid_iterator_methods[] = { JS_SELF_HOSTED_FN("next", "WrapForValidIteratorNext", 0, 0), diff --git a/js/src/vm/Iteration.h b/js/src/vm/Iteration.h index 92edaccd65..7f1047eec0 100644 --- a/js/src/vm/Iteration.h +++ b/js/src/vm/Iteration.h @@ -751,6 +751,8 @@ class IteratorObject : public NativeObject { public: static const JSClass class_; static const JSClass protoClass_; + + static bool finishInit(JSContext* cx, HandleObject ctor, HandleObject proto); }; /* diff --git a/js/src/vm/JSAtomUtils.cpp b/js/src/vm/JSAtomUtils.cpp index ab8e4b2e30..2f8b066f0c 100644 --- a/js/src/vm/JSAtomUtils.cpp +++ b/js/src/vm/JSAtomUtils.cpp @@ -62,113 +62,30 @@ extern bool GetUTF8AtomizationData(JSContext* cx, const JS::UTF8Chars& utf8, JS::SmallestEncoding* encoding, HashNumber* hashNum); -struct js::AtomHasher::Lookup { - union { - const JS::Latin1Char* latin1Chars; - const char16_t* twoByteChars; - const char* utf8Bytes; - }; - enum { TwoByteChar, Latin1, UTF8 } type; - size_t length; - size_t byteLength; - const JSAtom* atom; /* Optional. */ - JS::AutoCheckCannotGC nogc; - - HashNumber hash; - - MOZ_ALWAYS_INLINE Lookup(const char* utf8Bytes, size_t byteLen, size_t length, - HashNumber hash) - : utf8Bytes(utf8Bytes), - type(UTF8), - length(length), - byteLength(byteLen), - atom(nullptr), - hash(hash) {} - - MOZ_ALWAYS_INLINE Lookup(const char16_t* chars, size_t length) - : twoByteChars(chars), - type(TwoByteChar), - length(length), - atom(nullptr), - hash(mozilla::HashString(chars, length)) {} - - MOZ_ALWAYS_INLINE Lookup(const JS::Latin1Char* chars, size_t length) - : latin1Chars(chars), - type(Latin1), - length(length), - atom(nullptr), - hash(mozilla::HashString(chars, length)) {} - - MOZ_ALWAYS_INLINE Lookup(HashNumber hash, const char16_t* chars, - size_t length) - : twoByteChars(chars), - type(TwoByteChar), - length(length), - atom(nullptr), - hash(hash) { - MOZ_ASSERT(hash == mozilla::HashString(chars, length)); - } - - MOZ_ALWAYS_INLINE Lookup(HashNumber hash, const JS::Latin1Char* chars, - size_t length) - : latin1Chars(chars), - type(Latin1), - length(length), - atom(nullptr), - hash(hash) { - MOZ_ASSERT(hash == mozilla::HashString(chars, length)); - } - - inline explicit Lookup(const JSAtom* atom) - : type(atom->hasLatin1Chars() ? Latin1 : TwoByteChar), - length(atom->length()), - atom(atom), - hash(atom->hash()) { - if (type == Latin1) { - latin1Chars = atom->latin1Chars(nogc); - MOZ_ASSERT(mozilla::HashString(latin1Chars, length) == hash); - } else { - MOZ_ASSERT(type == TwoByteChar); - twoByteChars = atom->twoByteChars(nogc); - MOZ_ASSERT(mozilla::HashString(twoByteChars, length) == hash); - } - } -}; - -inline HashNumber js::AtomHasher::hash(const Lookup& l) { return l.hash; } - -MOZ_ALWAYS_INLINE bool js::AtomHasher::match(const WeakHeapPtr<JSAtom*>& entry, - const Lookup& lookup) { - JSAtom* key = entry.unbarrieredGet(); - if (lookup.atom) { - return lookup.atom == key; - } - if (key->length() != lookup.length || key->hash() != lookup.hash) { - return false; - } - - if (key->hasLatin1Chars()) { - const Latin1Char* keyChars = key->latin1Chars(lookup.nogc); - switch (lookup.type) { +MOZ_ALWAYS_INLINE bool js::AtomHasher::Lookup::StringsMatch( + const JSAtom& atom) const { + if (atom.hasLatin1Chars()) { + const Latin1Char* keyChars = atom.latin1Chars(nogc); + switch (type) { case Lookup::Latin1: - return EqualChars(keyChars, lookup.latin1Chars, lookup.length); + return EqualChars(keyChars, latin1Chars, length); case Lookup::TwoByteChar: - return EqualChars(keyChars, lookup.twoByteChars, lookup.length); + return EqualChars(keyChars, twoByteChars, length); case Lookup::UTF8: { - JS::UTF8Chars utf8(lookup.utf8Bytes, lookup.byteLength); + JS::UTF8Chars utf8(utf8Bytes, byteLength); return UTF8EqualsChars(utf8, keyChars); } } } - const char16_t* keyChars = key->twoByteChars(lookup.nogc); - switch (lookup.type) { + const char16_t* keyChars = atom.twoByteChars(nogc); + switch (type) { case Lookup::Latin1: - return EqualChars(lookup.latin1Chars, keyChars, lookup.length); + return EqualChars(latin1Chars, keyChars, length); case Lookup::TwoByteChar: - return EqualChars(keyChars, lookup.twoByteChars, lookup.length); + return EqualChars(keyChars, twoByteChars, length); case Lookup::UTF8: { - JS::UTF8Chars utf8(lookup.utf8Bytes, lookup.byteLength); + JS::UTF8Chars utf8(utf8Bytes, byteLength); return UTF8EqualsChars(utf8, keyChars); } } @@ -177,6 +94,21 @@ MOZ_ALWAYS_INLINE bool js::AtomHasher::match(const WeakHeapPtr<JSAtom*>& entry, return false; } +inline HashNumber js::AtomHasher::hash(const Lookup& l) { return l.hash; } + +MOZ_ALWAYS_INLINE bool js::AtomHasher::match(const WeakHeapPtr<JSAtom*>& entry, + const Lookup& lookup) { + JSAtom* key = entry.unbarrieredGet(); + if (lookup.atom) { + return lookup.atom == key; + } + if (key->length() != lookup.length || key->hash() != lookup.hash) { + return false; + } + + return lookup.StringsMatch(*key); +} + UniqueChars js::AtomToPrintableString(JSContext* cx, JSAtom* atom) { return QuoteString(cx, atom); } @@ -450,18 +382,21 @@ static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyCharsNonStaticValidLengthFromLookup( JSContext* cx, const CharT* chars, size_t length, const AtomHasher::Lookup& lookup, const Maybe<uint32_t>& indexValue) { - // Try the per-Zone cache first. If we find the atom there we can avoid the - // markAtom call, and the multiple HashSet lookups below. Zone* zone = cx->zone(); MOZ_ASSERT(zone); - AtomSet::AddPtr zonePtr = zone->atomCache().lookupForAdd(lookup); - if (zonePtr) { - // The cache is purged on GC so if we're in the middle of an - // incremental GC we should have barriered the atom when we put - // it in the cache. - JSAtom* atom = zonePtr->unbarrieredGet(); - MOZ_ASSERT(AtomIsMarked(zone, atom)); - return atom; + + AtomCacheHashTable* atomCache = zone->atomCache(); + + // Try the per-Zone cache first. If we find the atom there we can avoid the + // markAtom call, and the multiple HashSet lookups below. + if (MOZ_LIKELY(atomCache)) { + JSAtom* const cachedAtom = atomCache->lookupForAdd(lookup); + if (cachedAtom) { + // The cache is purged on GC so if we're in the middle of an incremental + // GC we should have barriered the atom when we put it in the cache. + MOZ_ASSERT(AtomIsMarked(zone, cachedAtom)); + return cachedAtom; + } } MOZ_ASSERT(cx->permanentAtomsPopulated()); @@ -469,11 +404,9 @@ AtomizeAndCopyCharsNonStaticValidLengthFromLookup( AtomSet::Ptr pp = cx->permanentAtoms().readonlyThreadsafeLookup(lookup); if (pp) { JSAtom* atom = pp->get(); - if (MOZ_UNLIKELY(!zone->atomCache().add(zonePtr, atom))) { - ReportOutOfMemory(cx); - return nullptr; + if (MOZ_LIKELY(atomCache)) { + atomCache->add(lookup.hash, atom); } - return atom; } @@ -488,11 +421,9 @@ AtomizeAndCopyCharsNonStaticValidLengthFromLookup( return nullptr; } - if (MOZ_UNLIKELY(!zone->atomCache().add(zonePtr, atom))) { - ReportOutOfMemory(cx); - return nullptr; + if (MOZ_LIKELY(atomCache)) { + atomCache->add(lookup.hash, atom); } - return atom; } diff --git a/js/src/vm/JSContext.cpp b/js/src/vm/JSContext.cpp index 5a4bfa86cd..3cc2c4807c 100644 --- a/js/src/vm/JSContext.cpp +++ b/js/src/vm/JSContext.cpp @@ -799,8 +799,14 @@ JS_PUBLIC_API void js::StopDrainingJobQueue(JSContext* cx) { cx->internalJobQueue->interrupt(); } +JS_PUBLIC_API void js::RestartDrainingJobQueue(JSContext* cx) { + MOZ_ASSERT(cx->internalJobQueue.ref()); + cx->internalJobQueue->uninterrupt(); +} + JS_PUBLIC_API void js::RunJobs(JSContext* cx) { MOZ_ASSERT(cx->jobQueue); + MOZ_ASSERT(cx->isEvaluatingModule == 0); cx->jobQueue->runJobs(cx); JS::ClearKeptObjects(cx); } @@ -887,7 +893,6 @@ void InternalJobQueue::runJobs(JSContext* cx) { draining_ = false; if (interrupted_) { - interrupted_ = false; break; } @@ -969,6 +974,7 @@ JSContext::JSContext(JSRuntime* runtime, const JS::ContextOptions& options) #ifdef DEBUG inUnsafeCallWithABI(this, false), hasAutoUnsafeCallWithABI(this, false), + liveArraySortDataInstances(this, 0), #endif #ifdef JS_SIMULATOR simulator_(this, nullptr), @@ -994,6 +1000,7 @@ JSContext::JSContext(JSRuntime* runtime, const JS::ContextOptions& options) #else regExpSearcherLastLimit(this, 0), #endif + isEvaluatingModule(this, 0), frontendCollectionPool_(this), suppressProfilerSampling(false), tempLifoAlloc_(this, (size_t)TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), @@ -1041,6 +1048,9 @@ JSContext::~JSContext() { /* Free the stuff hanging off of cx. */ MOZ_ASSERT(!resolvingList); + // Ensure we didn't leak memory for the ArraySortData vector. + MOZ_ASSERT(liveArraySortDataInstances == 0); + if (dtoaState) { DestroyDtoaState(dtoaState); } @@ -1189,6 +1199,13 @@ SavedFrame* JSContext::getPendingExceptionStack() { return unwrappedExceptionStack(); } +#ifdef DEBUG +const JS::Value& JSContext::getPendingExceptionUnwrapped() { + MOZ_ASSERT(isExceptionPending()); + return unwrappedException(); +} +#endif + bool JSContext::isClosingGenerator() { return isExceptionPending() && unwrappedException().isMagic(JS_GENERATOR_CLOSING); diff --git a/js/src/vm/JSContext.h b/js/src/vm/JSContext.h index 57aa236801..ba665d6c1a 100644 --- a/js/src/vm/JSContext.h +++ b/js/src/vm/JSContext.h @@ -89,12 +89,15 @@ class InternalJobQueue : public JS::JobQueue { JS::HandleObject incumbentGlobal) override; void runJobs(JSContext* cx) override; bool empty() const override; + bool isDrainingStopped() const override { return interrupted_; } // If we are currently in a call to runJobs(), make that call stop processing // jobs once the current one finishes, and return. If we are not currently in // a call to runJobs, make all future calls return immediately. void interrupt() { interrupted_ = true; } + void uninterrupt() { interrupted_ = false; } + // Return the front element of the queue, or nullptr if the queue is empty. // This is only used by shell testing functions. JSObject* maybeFront() const; @@ -425,6 +428,7 @@ struct JS_PUBLIC_API JSContext : public JS::RootingContext, #ifdef DEBUG js::ContextData<uint32_t> inUnsafeCallWithABI; js::ContextData<bool> hasAutoUnsafeCallWithABI; + js::ContextData<uint32_t> liveArraySortDataInstances; #endif #ifdef JS_SIMULATOR @@ -506,6 +510,9 @@ struct JS_PUBLIC_API JSContext : public JS::RootingContext, return offsetof(JSContext, regExpSearcherLastLimit); } + // Whether we are currently executing the top level of a module. + js::ContextData<uint32_t> isEvaluatingModule; + private: // Pools used for recycling name maps and vectors when parsing and // emitting bytecode. Purged on GC when there are no active script @@ -719,6 +726,13 @@ struct JS_PUBLIC_API JSContext : public JS::RootingContext, */ js::SavedFrame* getPendingExceptionStack(); +#ifdef DEBUG + /** + * Return the pending exception (without wrapping). + */ + const JS::Value& getPendingExceptionUnwrapped(); +#endif + bool isThrowingDebuggeeWouldRun(); bool isClosingGenerator(); diff --git a/js/src/vm/JSFunction.h b/js/src/vm/JSFunction.h index 451b63e11a..afd54c8f13 100644 --- a/js/src/vm/JSFunction.h +++ b/js/src/vm/JSFunction.h @@ -86,7 +86,7 @@ class JSFunction : public js::NativeObject { * the low bit set to ensure it's never identical to a BaseScript* * pointer * - * - a wasm JIT entry + * - a native JIT entry (used for Wasm and TrampolineNative functions) * * The JIT depends on none of the above being a valid BaseScript pointer. * @@ -199,6 +199,7 @@ class JSFunction : public js::NativeObject { bool isWasm() const { return flags().isWasm(); } bool isWasmWithJitEntry() const { return flags().isWasmWithJitEntry(); } + bool isNativeWithJitEntry() const { return flags().isNativeWithJitEntry(); } bool isNativeWithoutJitEntry() const { return flags().isNativeWithoutJitEntry(); } @@ -637,7 +638,9 @@ class JSFunction : public js::NativeObject { JS::PrivateValue(reinterpret_cast<void*>(native))); setNativeJitInfoOrInterpretedScript(const_cast<JSJitInfo*>(jitInfo)); } - bool hasJitInfo() const { return isBuiltinNative() && jitInfoUnchecked(); } + bool hasJitInfo() const { + return flags().canHaveJitInfo() && jitInfoUnchecked(); + } const JSJitInfo* jitInfo() const { MOZ_ASSERT(hasJitInfo()); return jitInfoUnchecked(); @@ -677,12 +680,25 @@ class JSFunction : public js::NativeObject { MOZ_ASSERT(*entry); MOZ_ASSERT(isWasm()); MOZ_ASSERT(!isWasmWithJitEntry()); - setFlags(flags().setWasmJitEntry()); + setFlags(flags().setNativeJitEntry()); setNativeJitInfoOrInterpretedScript(entry); MOZ_ASSERT(isWasmWithJitEntry()); } + void setTrampolineNativeJitEntry(void** entry) { + MOZ_ASSERT(*entry); + MOZ_ASSERT(isBuiltinNative()); + MOZ_ASSERT(!hasJitEntry()); + MOZ_ASSERT(!hasJitInfo(), "shouldn't clobber JSJitInfo"); + setFlags(flags().setNativeJitEntry()); + setNativeJitInfoOrInterpretedScript(entry); + MOZ_ASSERT(isNativeWithJitEntry()); + } void** wasmJitEntry() const { MOZ_ASSERT(isWasmWithJitEntry()); + return nativeJitEntry(); + } + void** nativeJitEntry() const { + MOZ_ASSERT(isNativeWithJitEntry()); return static_cast<void**>(nativeJitInfoOrInterpretedScript()); } inline js::wasm::Instance& wasmInstance() const; diff --git a/js/src/vm/JSONParser.cpp b/js/src/vm/JSONParser.cpp index f151f261b5..7a440e3090 100644 --- a/js/src/vm/JSONParser.cpp +++ b/js/src/vm/JSONParser.cpp @@ -644,7 +644,7 @@ inline bool JSONFullParseHandlerAnyChar::objectOpen( return false; } } - if (!stack.append(*properties)) { + if (!stack.append(StackEntry(cx, *properties))) { js_delete(*properties); return false; } @@ -676,11 +676,12 @@ inline bool JSONFullParseHandlerAnyChar::objectPropertyName( return true; } -inline void JSONFullParseHandlerAnyChar::finishObjectMember( +inline bool JSONFullParseHandlerAnyChar::finishObjectMember( Vector<StackEntry, 10>& stack, JS::Handle<JS::Value> value, PropertyVector** properties) { *properties = &stack.back().properties(); (*properties)->back().value = value; + return true; } inline bool JSONFullParseHandlerAnyChar::finishObject( @@ -718,7 +719,7 @@ inline bool JSONFullParseHandlerAnyChar::arrayOpen( return false; } } - if (!stack.append(*elements)) { + if (!stack.append(StackEntry(cx, *elements))) { js_delete(*elements); return false; } @@ -794,7 +795,7 @@ inline bool JSONFullParseHandler<CharT>::setStringValue( return false; } v = JS::StringValue(str); - return createJSONParseRecord(v, source); + return true; } template <typename CharT> @@ -812,26 +813,26 @@ inline bool JSONFullParseHandler<CharT>::setStringValue( return false; } v = JS::StringValue(str); - return createJSONParseRecord(v, source); + return true; } template <typename CharT> inline bool JSONFullParseHandler<CharT>::setNumberValue( double d, mozilla::Span<const CharT>&& source) { v = JS::NumberValue(d); - return createJSONParseRecord(v, source); + return true; } template <typename CharT> inline bool JSONFullParseHandler<CharT>::setBooleanValue( bool value, mozilla::Span<const CharT>&& source) { - return createJSONParseRecord(JS::BooleanValue(value), source); + return true; } template <typename CharT> inline bool JSONFullParseHandler<CharT>::setNullValue( mozilla::Span<const CharT>&& source) { - return createJSONParseRecord(JS::NullValue(), source); + return true; } template <typename CharT> @@ -847,29 +848,6 @@ void JSONFullParseHandler<CharT>::reportError(const char* msg, uint32_t line, msg, lineString, columnString); } -template <typename CharT> -void JSONFullParseHandler<CharT>::trace(JSTracer* trc) { - Base::trace(trc); - parseRecord.trace(trc); -} - -template <typename CharT> -inline bool JSONFullParseHandler<CharT>::createJSONParseRecord( - const Value& value, mozilla::Span<const CharT>& source) { -#ifdef ENABLE_JSON_PARSE_WITH_SOURCE - if (cx->realm()->creationOptions().getJSONParseWithSource()) { - MOZ_ASSERT(!source.IsEmpty()); - Rooted<JSONParseNode*> parseNode(cx, - NewStringCopy<CanGC, CharT>(cx, source)); - if (!parseNode) { - return false; - } - parseRecord = ParseRecordObject(parseNode, value); - } -#endif - return true; -} - template <typename CharT, typename HandlerT> JSONPerHandlerParser<CharT, HandlerT>::~JSONPerHandlerParser() { for (size_t i = 0; i < stack.length(); i++) { @@ -889,7 +867,9 @@ bool JSONPerHandlerParser<CharT, HandlerT>::parseImpl(TempValueT& value, switch (state) { case JSONParserState::FinishObjectMember: { typename HandlerT::PropertyVector* properties; - handler.finishObjectMember(stack, value, &properties); + if (!handler.finishObjectMember(stack, value, &properties)) { + return false; + } token = tokenizer.advanceAfterProperty(); if (token == JSONToken::ObjectClose) { @@ -1069,6 +1049,13 @@ template class js::JSONPerHandlerParser<Latin1Char, template class js::JSONPerHandlerParser<char16_t, js::JSONFullParseHandler<char16_t>>; +#ifdef ENABLE_JSON_PARSE_WITH_SOURCE +template class js::JSONPerHandlerParser<Latin1Char, + js::JSONReviveHandler<Latin1Char>>; +template class js::JSONPerHandlerParser<char16_t, + js::JSONReviveHandler<char16_t>>; +#endif + template class js::JSONPerHandlerParser<Latin1Char, js::JSONSyntaxParseHandler<Latin1Char>>; template class js::JSONPerHandlerParser<char16_t, @@ -1085,20 +1072,148 @@ bool JSONParser<CharT>::parse(JS::MutableHandle<JS::Value> vp) { } template <typename CharT> -bool JSONParser<CharT>::parse(JS::MutableHandle<JS::Value> vp, - JS::MutableHandle<ParseRecordObject> pro) { +void JSONParser<CharT>::trace(JSTracer* trc) { + this->handler.trace(trc); + + for (auto& elem : this->stack) { + if (elem.state == JSONParserState::FinishArrayElement) { + elem.elements().trace(trc); + } else { + elem.properties().trace(trc); + } + } +} + +template class js::JSONParser<Latin1Char>; +template class js::JSONParser<char16_t>; + +#ifdef ENABLE_JSON_PARSE_WITH_SOURCE +template <typename CharT> +inline bool JSONReviveHandler<CharT>::objectOpen(Vector<StackEntry, 10>& stack, + PropertyVector** properties) { + if (!parseRecordStack.append(ParseRecordEntry{context()})) { + return false; + } + + return Base::objectOpen(stack, properties); +} + +template <typename CharT> +inline bool JSONReviveHandler<CharT>::finishObjectMember( + Vector<StackEntry, 10>& stack, JS::Handle<JS::Value> value, + PropertyVector** properties) { + if (!Base::finishObjectMember(stack, value, properties)) { + return false; + } + parseRecord.value = value; + return finishMemberParseRecord((*properties)->back().id, + parseRecordStack.back()); +} + +template <typename CharT> +inline bool JSONReviveHandler<CharT>::finishObject( + Vector<StackEntry, 10>& stack, JS::MutableHandle<JS::Value> vp, + PropertyVector* properties) { + if (!Base::finishObject(stack, vp, properties)) { + return false; + } + if (!finishCompoundParseRecord(vp, parseRecordStack.back())) { + return false; + } + parseRecordStack.popBack(); + + return true; +} + +template <typename CharT> +inline bool JSONReviveHandler<CharT>::arrayOpen(Vector<StackEntry, 10>& stack, + ElementVector** elements) { + if (!parseRecordStack.append(ParseRecordEntry{context()})) { + return false; + } + + return Base::arrayOpen(stack, elements); +} + +template <typename CharT> +inline bool JSONReviveHandler<CharT>::arrayElement( + Vector<StackEntry, 10>& stack, JS::Handle<JS::Value> value, + ElementVector** elements) { + if (!Base::arrayElement(stack, value, elements)) { + return false; + } + size_t index = (*elements)->length() - 1; + JS::PropertyKey key = js::PropertyKey::Int(index); + return finishMemberParseRecord(key, parseRecordStack.back()); +} + +template <typename CharT> +inline bool JSONReviveHandler<CharT>::finishArray( + Vector<StackEntry, 10>& stack, JS::MutableHandle<JS::Value> vp, + ElementVector* elements) { + if (!Base::finishArray(stack, vp, elements)) { + return false; + } + if (!finishCompoundParseRecord(vp, parseRecordStack.back())) { + return false; + } + parseRecordStack.popBack(); + + return true; +} + +template <typename CharT> +inline bool JSONReviveHandler<CharT>::finishMemberParseRecord( + JS::PropertyKey& key, ParseRecordEntry& objectEntry) { + parseRecord.key = key; + return objectEntry.put(key, std::move(parseRecord)); +} + +template <typename CharT> +inline bool JSONReviveHandler<CharT>::finishCompoundParseRecord( + const Value& value, ParseRecordEntry& objectEntry) { + Rooted<JSONParseNode*> parseNode(context()); + parseRecord = ParseRecordObject(parseNode, value); + return parseRecord.addEntries(context(), std::move(objectEntry)); +} + +template <typename CharT> +inline bool JSONReviveHandler<CharT>::finishPrimitiveParseRecord( + const Value& value, SourceT source) { + MOZ_ASSERT(!source.IsEmpty()); // Empty source is for objects and arrays + Rooted<JSONParseNode*> parseNode( + context(), NewStringCopy<CanGC, CharT>(context(), source)); + if (!parseNode) { + return false; + } + parseRecord = ParseRecordObject(parseNode, value); + return true; +} + +template <typename CharT> +void JSONReviveHandler<CharT>::trace(JSTracer* trc) { + Base::trace(trc); + parseRecord.trace(trc); + for (auto& entry : this->parseRecordStack) { + entry.trace(trc); + } +} + +template <typename CharT> +bool JSONReviveParser<CharT>::parse(JS::MutableHandle<JS::Value> vp, + JS::MutableHandle<ParseRecordObject> pro) { JS::Rooted<JS::Value> tempValue(this->handler.cx); vp.setUndefined(); bool result = this->parseImpl( tempValue, [&](JS::Handle<JS::Value> value) { vp.set(value); }); - pro.get() = std::move(this->handler.parseRecord); + pro.set(std::move(this->handler.parseRecord)); return result; } template <typename CharT> -void JSONParser<CharT>::trace(JSTracer* trc) { +void JSONReviveParser<CharT>::trace(JSTracer* trc) { this->handler.trace(trc); for (auto& elem : this->stack) { @@ -1110,8 +1225,9 @@ void JSONParser<CharT>::trace(JSTracer* trc) { } } -template class js::JSONParser<Latin1Char>; -template class js::JSONParser<char16_t>; +template class js::JSONReviveParser<Latin1Char>; +template class js::JSONReviveParser<char16_t>; +#endif // ENABLE_JSON_PARSE_WITH_SOURCE template <typename CharT> inline bool JSONSyntaxParseHandler<CharT>::objectOpen( @@ -1359,9 +1475,11 @@ class MOZ_STACK_CLASS DelegateHandler { *isProtoInEval = false; return true; } - inline void finishObjectMember(Vector<StackEntry, 10>& stack, + inline bool finishObjectMember(Vector<StackEntry, 10>& stack, DummyValue& value, - PropertyVector** properties) {} + PropertyVector** properties) { + return true; + } inline bool finishObject(Vector<StackEntry, 10>& stack, DummyValue* vp, PropertyVector* properties) { if (hadHandlerError_) { diff --git a/js/src/vm/JSONParser.h b/js/src/vm/JSONParser.h index 91e33c02b3..51b90e003c 100644 --- a/js/src/vm/JSONParser.h +++ b/js/src/vm/JSONParser.h @@ -190,10 +190,10 @@ class MOZ_STACK_CLASS JSONFullParseHandlerAnyChar { return *static_cast<PropertyVector*>(vector); } - explicit StackEntry(ElementVector* elements) + explicit StackEntry(JSContext* cx, ElementVector* elements) : state(JSONParserState::FinishArrayElement), vector(elements) {} - explicit StackEntry(PropertyVector* properties) + explicit StackEntry(JSContext* cx, PropertyVector* properties) : state(JSONParserState::FinishObjectMember), vector(properties) {} JSONParserState state; @@ -255,7 +255,7 @@ class MOZ_STACK_CLASS JSONFullParseHandlerAnyChar { PropertyVector** properties); inline bool objectPropertyName(Vector<StackEntry, 10>& stack, bool* isProtoInEval); - inline void finishObjectMember(Vector<StackEntry, 10>& stack, + inline bool finishObjectMember(Vector<StackEntry, 10>& stack, JS::Handle<JS::Value> value, PropertyVector** properties); inline bool finishObject(Vector<StackEntry, 10>& stack, @@ -303,8 +303,6 @@ class MOZ_STACK_CLASS JSONFullParseHandler bool append(const CharT* begin, const CharT* end); }; - ParseRecordObject parseRecord; - explicit JSONFullParseHandler(JSContext* cx) : Base(cx) {} JSONFullParseHandler(JSONFullParseHandler&& other) noexcept @@ -324,13 +322,101 @@ class MOZ_STACK_CLASS JSONFullParseHandler inline bool setNullValue(mozilla::Span<const CharT>&& source); void reportError(const char* msg, uint32_t line, uint32_t column); +}; + +#ifdef ENABLE_JSON_PARSE_WITH_SOURCE +template <typename CharT> +class MOZ_STACK_CLASS JSONReviveHandler : public JSONFullParseHandler<CharT> { + using CharPtr = mozilla::RangedPtr<const CharT>; + using Base = JSONFullParseHandler<CharT>; + + public: + using SourceT = mozilla::Span<const CharT>; + using ParseRecordEntry = ParseRecordObject::EntryMap; + + using StringBuilder = typename Base::StringBuilder; + using StackEntry = typename Base::StackEntry; + using PropertyVector = typename Base::PropertyVector; + using ElementVector = typename Base::ElementVector; + + public: + explicit JSONReviveHandler(JSContext* cx) : Base(cx), parseRecordStack(cx) {} + + JSONReviveHandler(JSONReviveHandler&& other) noexcept + : Base(std::move(other)), + parseRecordStack(std::move(other.parseRecordStack)), + parseRecord(std::move(other.parseRecord)) {} + + JSONReviveHandler(const JSONReviveHandler& other) = delete; + void operator=(const JSONReviveHandler& other) = delete; + + JSContext* context() { return this->cx; } + + template <JSONStringType ST> + inline bool setStringValue(CharPtr start, size_t length, SourceT&& source) { + if (!Base::template setStringValue<ST>(start, length, + std::forward<SourceT&&>(source))) { + return false; + } + return finishPrimitiveParseRecord(this->v, source); + } + + template <JSONStringType ST> + inline bool setStringValue(StringBuilder& builder, SourceT&& source) { + if (!Base::template setStringValue<ST>(builder, + std::forward<SourceT&&>(source))) { + return false; + } + return finishPrimitiveParseRecord(this->v, source); + } + + inline bool setNumberValue(double d, SourceT&& source) { + if (!Base::setNumberValue(d, std::forward<SourceT&&>(source))) { + return false; + } + return finishPrimitiveParseRecord(this->v, source); + } + + inline bool setBooleanValue(bool value, SourceT&& source) { + return finishPrimitiveParseRecord(JS::BooleanValue(value), source); + } + inline bool setNullValue(SourceT&& source) { + return finishPrimitiveParseRecord(JS::NullValue(), source); + } + + inline bool objectOpen(Vector<StackEntry, 10>& stack, + PropertyVector** properties); + inline bool finishObjectMember(Vector<StackEntry, 10>& stack, + JS::Handle<JS::Value> value, + PropertyVector** properties); + inline bool finishObject(Vector<StackEntry, 10>& stack, + JS::MutableHandle<JS::Value> vp, + PropertyVector* properties); + + inline bool arrayOpen(Vector<StackEntry, 10>& stack, + ElementVector** elements); + inline bool arrayElement(Vector<StackEntry, 10>& stack, + JS::Handle<JS::Value> value, + ElementVector** elements); + inline bool finishArray(Vector<StackEntry, 10>& stack, + JS::MutableHandle<JS::Value> vp, + ElementVector* elements); void trace(JSTracer* trc); - protected: - inline bool createJSONParseRecord(const Value& value, - mozilla::Span<const CharT>& source); + private: + inline bool finishMemberParseRecord(JS::PropertyKey& key, + ParseRecordEntry& objectEntry); + inline bool finishCompoundParseRecord(const Value& value, + ParseRecordEntry& objectEntry); + inline bool finishPrimitiveParseRecord(const Value& value, SourceT source); + + Vector<ParseRecordEntry, 10> parseRecordStack; + + public: + ParseRecordObject parseRecord; }; +#endif // ENABLE_JSON_PARSE_WITH_SOURCE template <typename CharT> class MOZ_STACK_CLASS JSONSyntaxParseHandler { @@ -409,9 +495,11 @@ class MOZ_STACK_CLASS JSONSyntaxParseHandler { *isProtoInEval = false; return true; } - inline void finishObjectMember(Vector<StackEntry, 10>& stack, + inline bool finishObjectMember(Vector<StackEntry, 10>& stack, DummyValue& value, - PropertyVector** properties) {} + PropertyVector** properties) { + return true; + } inline bool finishObject(Vector<StackEntry, 10>& stack, DummyValue* vp, PropertyVector* properties); @@ -510,11 +598,52 @@ class MOZ_STACK_CLASS JSONParser * represent |undefined|, so the JSON data couldn't have specified it.) */ bool parse(JS::MutableHandle<JS::Value> vp); + + void trace(JSTracer* trc); +}; + +#ifdef ENABLE_JSON_PARSE_WITH_SOURCE +template <typename CharT> +class MOZ_STACK_CLASS JSONReviveParser + : JSONPerHandlerParser<CharT, JSONReviveHandler<CharT>> { + using Base = JSONPerHandlerParser<CharT, JSONReviveHandler<CharT>>; + + public: + using ParseType = JSONFullParseHandlerAnyChar::ParseType; + + /* Public API */ + + /* Create a parser for the provided JSON data. */ + JSONReviveParser(JSContext* cx, mozilla::Range<const CharT> data) + : Base(cx, data) {} + + /* Allow move construction for use with Rooted. */ + JSONReviveParser(JSONReviveParser&& other) noexcept + : Base(std::move(other)) {} + + JSONReviveParser(const JSONReviveParser& other) = delete; + void operator=(const JSONReviveParser& other) = delete; + + /* + * Parse the JSON data specified at construction time. If it parses + * successfully, store the prescribed value in *vp and return true. If an + * internal error (e.g. OOM) occurs during parsing, return false. + * Otherwise, if invalid input was specifed but no internal error occurred, + * behavior depends upon the error handling specified at construction: if + * error handling is RaiseError then throw a SyntaxError and return false, + * otherwise return true and set *vp to |undefined|. (JSON syntax can't + * represent |undefined|, so the JSON data couldn't have specified it.) + * + * If it parses successfully, parse information for calling the reviver + * function is stored in *pro. If this function returns false, *pro will be + * set to |undefined|. + */ bool parse(JS::MutableHandle<JS::Value> vp, JS::MutableHandle<ParseRecordObject> pro); void trace(JSTracer* trc); }; +#endif // ENABLE_JSON_PARSE_WITH_SOURCE template <typename CharT, typename Wrapper> class MutableWrappedPtrOperations<JSONParser<CharT>, Wrapper> @@ -523,10 +652,6 @@ class MutableWrappedPtrOperations<JSONParser<CharT>, Wrapper> bool parse(JS::MutableHandle<JS::Value> vp) { return static_cast<Wrapper*>(this)->get().parse(vp); } - bool parse(JS::MutableHandle<JS::Value> vp, - JS::MutableHandle<ParseRecordObject> pro) { - return static_cast<Wrapper*>(this)->get().parse(vp, pro); - } }; template <typename CharT> diff --git a/js/src/vm/JSONPrinter.cpp b/js/src/vm/JSONPrinter.cpp index 5b5183d9fb..53ab4be67c 100644 --- a/js/src/vm/JSONPrinter.cpp +++ b/js/src/vm/JSONPrinter.cpp @@ -154,8 +154,8 @@ void JSONPrinter::formatProperty(const char* name, const char* format, ...) { va_end(ap); } -void JSONPrinter::formatProperty(const char* name, const char* format, - va_list ap) { +void JSONPrinter::formatPropertyVA(const char* name, const char* format, + va_list ap) { beginStringProperty(name); out_.vprintf(format, ap); endStringProperty(); diff --git a/js/src/vm/JSONPrinter.h b/js/src/vm/JSONPrinter.h index b90696a2b4..61536a6c87 100644 --- a/js/src/vm/JSONPrinter.h +++ b/js/src/vm/JSONPrinter.h @@ -62,7 +62,7 @@ class JSONPrinter { void formatProperty(const char* name, const char* format, ...) MOZ_FORMAT_PRINTF(3, 4); - void formatProperty(const char* name, const char* format, va_list ap); + void formatPropertyVA(const char* name, const char* format, va_list ap); void propertyName(const char* name); diff --git a/js/src/vm/JSObject-inl.h b/js/src/vm/JSObject-inl.h index 4ca0946878..a493d7624a 100644 --- a/js/src/vm/JSObject-inl.h +++ b/js/src/vm/JSObject-inl.h @@ -189,8 +189,9 @@ template <typename T> MOZ_ASSERT(!cx->realm()->hasObjectPendingMetadata()); // The metadata builder is invoked for each object created on the main thread, - // except when it's suppressed. - if (!cx->zone()->suppressAllocationMetadataBuilder) { + // except when it's suppressed or we're throwing over-recursion error. + if (!cx->zone()->suppressAllocationMetadataBuilder && + !cx->isThrowingOverRecursed()) { // Don't collect metadata on objects that represent metadata, to avoid // recursion. AutoSuppressAllocationMetadataBuilder suppressMetadata(cx); diff --git a/js/src/vm/JSObject.cpp b/js/src/vm/JSObject.cpp index ea4dfeb6f7..8bc8bc0d52 100644 --- a/js/src/vm/JSObject.cpp +++ b/js/src/vm/JSObject.cpp @@ -2215,9 +2215,7 @@ JS_PUBLIC_API bool js::ShouldIgnorePropertyDefinition(JSContext* cx, id == NameToId(cx->names().symmetricDifference))) { return true; } -#endif -#ifdef NIGHTLY_BUILD if (key == JSProto_ArrayBuffer && !JS::Prefs::arraybuffer_transfer() && (id == NameToId(cx->names().transfer) || id == NameToId(cx->names().transferToFixedLength) || @@ -2240,6 +2238,33 @@ JS_PUBLIC_API bool js::ShouldIgnorePropertyDefinition(JSContext* cx, id == NameToId(cx->names().grow))) { return true; } + + if (key == JSProto_Uint8Array && + !JS::Prefs::experimental_uint8array_base64() && + (id == NameToId(cx->names().setFromBase64) || + id == NameToId(cx->names().setFromHex) || + id == NameToId(cx->names().toBase64) || + id == NameToId(cx->names().toHex))) { + return true; + } + + // It's gently surprising that this is JSProto_Function, but the trick + // to realize is that this is a -constructor function-, not a function + // on the prototype; and the proto of the constructor is JSProto_Function. + if (key == JSProto_Function && !JS::Prefs::experimental_uint8array_base64() && + (id == NameToId(cx->names().fromBase64) || + id == NameToId(cx->names().fromHex))) { + return true; + } +#endif + +#ifdef ENABLE_JSON_PARSE_WITH_SOURCE + if (key == JSProto_JSON && + !JS::Prefs::experimental_json_parse_with_source() && + (id == NameToId(cx->names().isRawJSON) || + id == NameToId(cx->names().rawJSON))) { + return true; + } #endif return false; @@ -3165,19 +3190,8 @@ js::gc::AllocKind JSObject::allocKindForTenure( return as<JSFunction>().getAllocKind(); } - // Fixed length typed arrays in the nursery may have a lazily allocated - // buffer, make sure there is room for the array's fixed data when moving - // the array. - if (is<FixedLengthTypedArrayObject>() && - !as<FixedLengthTypedArrayObject>().hasBuffer()) { - gc::AllocKind allocKind; - if (as<FixedLengthTypedArrayObject>().hasInlineElements()) { - size_t nbytes = as<FixedLengthTypedArrayObject>().byteLength(); - allocKind = FixedLengthTypedArrayObject::AllocKindForLazyBuffer(nbytes); - } else { - allocKind = GetGCObjectKind(getClass()); - } - return ForegroundToBackgroundAllocKind(allocKind); + if (is<FixedLengthTypedArrayObject>()) { + return as<FixedLengthTypedArrayObject>().allocKindForTenure(); } return as<NativeObject>().allocKindForTenure(); diff --git a/js/src/vm/Modules.cpp b/js/src/vm/Modules.cpp index f461e7bec1..917083a238 100644 --- a/js/src/vm/Modules.cpp +++ b/js/src/vm/Modules.cpp @@ -9,7 +9,8 @@ #include "vm/Modules.h" #include "mozilla/Assertions.h" // MOZ_ASSERT -#include "mozilla/Utf8.h" // mozilla::Utf8Unit +#include "mozilla/ScopeExit.h" +#include "mozilla/Utf8.h" // mozilla::Utf8Unit #include <stdint.h> // uint32_t @@ -42,6 +43,12 @@ using namespace js; using mozilla::Utf8Unit; +static bool ModuleLink(JSContext* cx, Handle<ModuleObject*> module); +static bool ModuleEvaluate(JSContext* cx, Handle<ModuleObject*> module, + MutableHandle<Value> result); +static bool SyntheticModuleEvaluate(JSContext* cx, Handle<ModuleObject*> module, + MutableHandle<Value> result); + //////////////////////////////////////////////////////////////////////////////// // Public API JS_PUBLIC_API JS::ModuleResolveHook JS::GetModuleResolveHook(JSRuntime* rt) { @@ -184,7 +191,7 @@ JS_PUBLIC_API bool JS::ModuleLink(JSContext* cx, Handle<JSObject*> moduleArg) { CHECK_THREAD(cx); cx->releaseCheck(moduleArg); - return js::ModuleLink(cx, moduleArg.as<ModuleObject>()); + return ::ModuleLink(cx, moduleArg.as<ModuleObject>()); } JS_PUBLIC_API bool JS::ModuleEvaluate(JSContext* cx, @@ -194,11 +201,17 @@ JS_PUBLIC_API bool JS::ModuleEvaluate(JSContext* cx, CHECK_THREAD(cx); cx->releaseCheck(moduleRecord); + cx->isEvaluatingModule++; + auto guard = mozilla::MakeScopeExit([cx] { + MOZ_ASSERT(cx->isEvaluatingModule != 0); + cx->isEvaluatingModule--; + }); + if (moduleRecord.as<ModuleObject>()->hasSyntheticModuleFields()) { return SyntheticModuleEvaluate(cx, moduleRecord.as<ModuleObject>(), rval); } - return js::ModuleEvaluate(cx, moduleRecord.as<ModuleObject>(), rval); + return ::ModuleEvaluate(cx, moduleRecord.as<ModuleObject>(), rval); } JS_PUBLIC_API bool JS::ThrowOnModuleEvaluationFailure( @@ -327,6 +340,11 @@ JS_PUBLIC_API void JS::ClearModuleEnvironment(JSObject* moduleObj) { } } +JS_PUBLIC_API bool JS::ModuleIsLinked(JSObject* moduleObj) { + AssertHeapIsIdle(); + return moduleObj->as<ModuleObject>().status() != ModuleStatus::Unlinked; +} + //////////////////////////////////////////////////////////////////////////////// // Internal implementation @@ -356,14 +374,17 @@ static ModuleObject* HostResolveImportedModule( JSContext* cx, Handle<ModuleObject*> module, Handle<ModuleRequestObject*> moduleRequest, ModuleStatus expectedMinimumStatus); -static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, - Handle<JSAtom*> exportName, - MutableHandle<ResolveSet> resolveSet, - MutableHandle<Value> result); +static bool CyclicModuleResolveExport(JSContext* cx, + Handle<ModuleObject*> module, + Handle<JSAtom*> exportName, + MutableHandle<ResolveSet> resolveSet, + MutableHandle<Value> result, + ModuleErrorInfo* errorInfoOut = nullptr); static bool SyntheticModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, Handle<JSAtom*> exportName, - MutableHandle<Value> result); + MutableHandle<Value> result, + ModuleErrorInfo* errorInfoOut); static ModuleNamespaceObject* ModuleNamespaceCreate( JSContext* cx, Handle<ModuleObject*> module, MutableHandle<UniquePtr<ExportNameVector>> exports); @@ -575,17 +596,20 @@ static ModuleObject* HostResolveImportedModule( // - If the request is found to be ambiguous, the string `"ambiguous"` is // returned. // -bool js::ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, - Handle<JSAtom*> exportName, - MutableHandle<Value> result) { +static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, + Handle<JSAtom*> exportName, + MutableHandle<Value> result, + ModuleErrorInfo* errorInfoOut = nullptr) { if (module->hasSyntheticModuleFields()) { - return ::SyntheticModuleResolveExport(cx, module, exportName, result); + return SyntheticModuleResolveExport(cx, module, exportName, result, + errorInfoOut); } // Step 1. If resolveSet is not present, set resolveSet to a new empty List. Rooted<ResolveSet> resolveSet(cx); - return ::ModuleResolveExport(cx, module, exportName, &resolveSet, result); + return CyclicModuleResolveExport(cx, module, exportName, &resolveSet, result, + errorInfoOut); } static bool CreateResolvedBindingObject(JSContext* cx, @@ -602,10 +626,12 @@ static bool CreateResolvedBindingObject(JSContext* cx, return true; } -static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, - Handle<JSAtom*> exportName, - MutableHandle<ResolveSet> resolveSet, - MutableHandle<Value> result) { +static bool CyclicModuleResolveExport(JSContext* cx, + Handle<ModuleObject*> module, + Handle<JSAtom*> exportName, + MutableHandle<ResolveSet> resolveSet, + MutableHandle<Value> result, + ModuleErrorInfo* errorInfoOut) { // Step 2. For each Record { [[Module]], [[ExportName]] } r of resolveSet, do: for (const auto& entry : resolveSet) { // Step 2.a. If module and r.[[Module]] are the same Module Record and @@ -614,6 +640,9 @@ static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, // Step 2.a.i. Assert: This is a circular import request. // Step 2.a.ii. Return null. result.setNull(); + if (errorInfoOut) { + errorInfoOut->setCircularImport(cx, module); + } return true; } } @@ -669,8 +698,8 @@ static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, // importedModule.ResolveExport(e.[[ImportName]], // resolveSet). name = e.importName(); - return ModuleResolveExport(cx, importedModule, name, resolveSet, - result); + return CyclicModuleResolveExport(cx, importedModule, name, resolveSet, + result, errorInfoOut); } } } @@ -683,6 +712,9 @@ static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, // Step 6.c. NOTE: A default export cannot be provided by an export * from // "mod" declaration. result.setNull(); + if (errorInfoOut) { + errorInfoOut->setImportedModule(cx, module); + } return true; } @@ -704,8 +736,8 @@ static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, // Step 8.b. Let resolution be ? importedModule.ResolveExport(exportName, // resolveSet). - if (!ModuleResolveExport(cx, importedModule, exportName, resolveSet, - &resolution)) { + if (!CyclicModuleResolveExport(cx, importedModule, exportName, resolveSet, + &resolution, errorInfoOut)) { return false; } @@ -744,6 +776,12 @@ static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, if (binding->module() != starResolution->module() || binding->bindingName() != starResolution->bindingName()) { result.set(StringValue(cx->names().ambiguous)); + + if (errorInfoOut) { + Rooted<ModuleObject*> module1(cx, starResolution->module()); + Rooted<ModuleObject*> module2(cx, binding->module()); + errorInfoOut->setForAmbiguousImport(cx, module, module1, module2); + } return true; } } @@ -752,6 +790,9 @@ static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, // Step 9. Return starResolution. result.setObjectOrNull(starResolution); + if (!starResolution && errorInfoOut) { + errorInfoOut->setImportedModule(cx, module); + } return true; } @@ -759,10 +800,14 @@ static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, static bool SyntheticModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, Handle<JSAtom*> exportName, - MutableHandle<Value> result) { + MutableHandle<Value> result, + ModuleErrorInfo* errorInfoOut) { // Step 2. If module.[[ExportNames]] does not contain exportName, return null. if (!ContainsElement(module->syntheticExportNames(), exportName)) { result.setNull(); + if (errorInfoOut) { + errorInfoOut->setImportedModule(cx, module); + } return true; } @@ -923,63 +968,93 @@ static ModuleNamespaceObject* ModuleNamespaceCreate( return ns; } -static void ThrowResolutionError(JSContext* cx, Handle<ModuleObject*> module, - Handle<Value> resolution, bool isDirectImport, - Handle<JSAtom*> name, uint32_t line, - JS::ColumnNumberOneOrigin column) { - MOZ_ASSERT(line != 0); +void ModuleErrorInfo::setImportedModule(JSContext* cx, + ModuleObject* importedModule) { + imported = importedModule->filename(); +} - bool isAmbiguous = resolution == StringValue(cx->names().ambiguous); +void ModuleErrorInfo::setCircularImport(JSContext* cx, + ModuleObject* importedModule) { + setImportedModule(cx, importedModule); + isCircular = true; +} - // ErrorNumbers: - // | MISSING | AMBIGUOUS | - // ---------+---------+-----------+ - // INDIRECT | - // DIRECT | - static constexpr unsigned ErrorNumbers[2][2] = { - {JSMSG_MISSING_INDIRECT_EXPORT, JSMSG_AMBIGUOUS_INDIRECT_EXPORT}, - {JSMSG_MISSING_IMPORT, JSMSG_AMBIGUOUS_IMPORT}}; - unsigned errorNumber = ErrorNumbers[isDirectImport][isAmbiguous]; - - const JSErrorFormatString* errorString = - GetErrorMessage(nullptr, errorNumber); - MOZ_ASSERT(errorString); - - MOZ_ASSERT(errorString->argCount == 0); - Rooted<JSString*> message(cx, JS_NewStringCopyZ(cx, errorString->format)); - if (!message) { +void ModuleErrorInfo::setForAmbiguousImport(JSContext* cx, + ModuleObject* importedModule, + ModuleObject* module1, + ModuleObject* module2) { + setImportedModule(cx, importedModule); + entry1 = module1->filename(); + entry2 = module2->filename(); +} + +static void CreateErrorNumberMessageUTF8(JSContext* cx, unsigned errorNumber, + JSErrorReport* reportOut, ...) { + va_list ap; + va_start(ap, reportOut); + AutoReportFrontendContext fc(cx); + if (!ExpandErrorArgumentsVA(&fc, GetErrorMessage, nullptr, errorNumber, + ArgumentsAreUTF8, reportOut, ap)) { + ReportOutOfMemory(cx); return; } - Rooted<JSString*> separator(cx, JS_NewStringCopyZ(cx, ": ")); - if (!separator) { + va_end(ap); +} + +static void ThrowResolutionError(JSContext* cx, Handle<ModuleObject*> module, + Handle<Value> resolution, Handle<JSAtom*> name, + ModuleErrorInfo* errorInfo) { + MOZ_ASSERT(errorInfo); + auto chars = StringToNewUTF8CharsZ(cx, *name); + if (!chars) { + ReportOutOfMemory(cx); return; } - message = ConcatStrings<CanGC>(cx, message, separator); - if (!message) { - return; + bool isAmbiguous = resolution == StringValue(cx->names().ambiguous); + + unsigned errorNumber; + if (errorInfo->isCircular) { + errorNumber = JSMSG_MODULE_CIRCULAR_IMPORT; + } else if (isAmbiguous) { + errorNumber = JSMSG_MODULE_AMBIGUOUS; + } else { + errorNumber = JSMSG_MODULE_NO_EXPORT; + } + + JSErrorReport report; + report.isWarning_ = false; + report.errorNumber = errorNumber; + + if (errorNumber == JSMSG_MODULE_AMBIGUOUS) { + CreateErrorNumberMessageUTF8(cx, errorNumber, &report, errorInfo->imported, + chars.get(), errorInfo->entry1, + errorInfo->entry2); + } else { + CreateErrorNumberMessageUTF8(cx, errorNumber, &report, errorInfo->imported, + chars.get()); } - message = ConcatStrings<CanGC>(cx, message, name); + Rooted<JSString*> message(cx, report.newMessageString(cx)); if (!message) { + ReportOutOfMemory(cx); return; } - RootedString filename(cx); - if (const char* chars = module->script()->filename()) { - filename = - JS_NewStringCopyUTF8Z(cx, JS::ConstUTF8CharsZ(chars, strlen(chars))); - } else { - filename = cx->names().empty_; - } + const char* file = module->filename(); + RootedString filename( + cx, JS_NewStringCopyUTF8Z(cx, JS::ConstUTF8CharsZ(file, strlen(file)))); if (!filename) { + ReportOutOfMemory(cx); return; } RootedValue error(cx); - if (!JS::CreateError(cx, JSEXN_SYNTAXERR, nullptr, filename, line, column, - nullptr, message, JS::NothingHandleValue, &error)) { + if (!JS::CreateError(cx, JSEXN_SYNTAXERR, nullptr, filename, + errorInfo->lineNumber, errorInfo->columnNumber, nullptr, + message, JS::NothingHandleValue, &error)) { + ReportOutOfMemory(cx); return; } @@ -988,8 +1063,8 @@ static void ThrowResolutionError(JSContext* cx, Handle<ModuleObject*> module, // https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment // ES2023 16.2.1.6.4 InitializeEnvironment -bool js::ModuleInitializeEnvironment(JSContext* cx, - Handle<ModuleObject*> module) { +static bool ModuleInitializeEnvironment(JSContext* cx, + Handle<ModuleObject*> module) { MOZ_ASSERT(module->status() == ModuleStatus::Linking); // Step 1. For each ExportEntry Record e of module.[[IndirectExportEntries]], @@ -1002,15 +1077,15 @@ bool js::ModuleInitializeEnvironment(JSContext* cx, // Step 1.b. Let resolution be ? module.ResolveExport(e.[[ExportName]]). exportName = e.exportName(); - if (!ModuleResolveExport(cx, module, exportName, &resolution)) { + ModuleErrorInfo errorInfo{e.lineNumber(), e.columnNumber()}; + if (!ModuleResolveExport(cx, module, exportName, &resolution, &errorInfo)) { return false; } // Step 1.c. If resolution is either null or AMBIGUOUS, throw a SyntaxError // exception. if (!IsResolvedBinding(cx, resolution)) { - ThrowResolutionError(cx, module, resolution, false, exportName, - e.lineNumber(), e.columnNumber()); + ThrowResolutionError(cx, module, resolution, exportName, &errorInfo); return false; } } @@ -1059,15 +1134,16 @@ bool js::ModuleInitializeEnvironment(JSContext* cx, // Step 7.d. Else: // Step 7.d.i. Let resolution be ? // importedModule.ResolveExport(in.[[ImportName]]). - if (!ModuleResolveExport(cx, importedModule, importName, &resolution)) { + ModuleErrorInfo errorInfo{in.lineNumber(), in.columnNumber()}; + if (!ModuleResolveExport(cx, importedModule, importName, &resolution, + &errorInfo)) { return false; } // Step 7.d.ii. If resolution is null or ambiguous, throw a SyntaxError // exception. if (!IsResolvedBinding(cx, resolution)) { - ThrowResolutionError(cx, module, resolution, true, importName, - in.lineNumber(), in.columnNumber()); + ThrowResolutionError(cx, module, resolution, importName, &errorInfo); return false; } @@ -1134,7 +1210,7 @@ bool js::ModuleInitializeEnvironment(JSContext* cx, // https://tc39.es/ecma262/#sec-moduledeclarationlinking // ES2023 16.2.1.5.1 Link -bool js::ModuleLink(JSContext* cx, Handle<ModuleObject*> module) { +static bool ModuleLink(JSContext* cx, Handle<ModuleObject*> module) { // Step 1. Assert: module.[[Status]] is not linking or evaluating. ModuleStatus status = module->status(); if (status == ModuleStatus::Linking || status == ModuleStatus::Evaluating) { @@ -1313,8 +1389,9 @@ static bool InnerModuleLinking(JSContext* cx, Handle<ModuleObject*> module, return true; } -bool js::SyntheticModuleEvaluate(JSContext* cx, Handle<ModuleObject*> moduleArg, - MutableHandle<Value> result) { +static bool SyntheticModuleEvaluate(JSContext* cx, + Handle<ModuleObject*> moduleArg, + MutableHandle<Value> result) { // Steps 1-12 happens elsewhere in the engine. // Step 13. Let pc be ! NewPromiseCapability(%Promise%). @@ -1337,8 +1414,8 @@ bool js::SyntheticModuleEvaluate(JSContext* cx, Handle<ModuleObject*> moduleArg, // https://tc39.es/ecma262/#sec-moduleevaluation // ES2023 16.2.1.5.2 Evaluate -bool js::ModuleEvaluate(JSContext* cx, Handle<ModuleObject*> moduleArg, - MutableHandle<Value> result) { +static bool ModuleEvaluate(JSContext* cx, Handle<ModuleObject*> moduleArg, + MutableHandle<Value> result) { Rooted<ModuleObject*> module(cx, moduleArg); // Step 2. Assert: module.[[Status]] is linked, evaluating-async, or diff --git a/js/src/vm/Modules.h b/js/src/vm/Modules.h index 21aff46b90..d59c9db552 100644 --- a/js/src/vm/Modules.h +++ b/js/src/vm/Modules.h @@ -20,22 +20,32 @@ namespace js { using ModuleVector = GCVector<ModuleObject*, 0, SystemAllocPolicy>; -bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module, - Handle<JSAtom*> exportName, - MutableHandle<Value> result); +// A struct with detailed error information when import/export failed. +struct ModuleErrorInfo { + ModuleErrorInfo(uint32_t lineNumber_, JS::ColumnNumberOneOrigin columnNumber_) + : lineNumber(lineNumber_), columnNumber(columnNumber_) {} -ModuleNamespaceObject* GetOrCreateModuleNamespace(JSContext* cx, - Handle<ModuleObject*> module); + void setImportedModule(JSContext* cx, ModuleObject* importedModule); + void setCircularImport(JSContext* cx, ModuleObject* importedModule); + void setForAmbiguousImport(JSContext* cx, ModuleObject* importedModule, + ModuleObject* module1, ModuleObject* module2); + + uint32_t lineNumber; + JS::ColumnNumberOneOrigin columnNumber; -bool ModuleInitializeEnvironment(JSContext* cx, Handle<ModuleObject*> module); + // The filename of the imported module. + const char* imported; -bool ModuleLink(JSContext* cx, Handle<ModuleObject*> module); + // The filenames of the ambiguous entries. + const char* entry1; + const char* entry2; -// Start evaluating the module. If TLA is enabled, result will be a promise. -bool ModuleEvaluate(JSContext* cx, Handle<ModuleObject*> module, - MutableHandle<Value> result); -bool SyntheticModuleEvaluate(JSContext* cx, Handle<ModuleObject*> module, - MutableHandle<Value> result); + // A bool to indicate the error is a circular import when it's true. + bool isCircular = false; +}; + +ModuleNamespaceObject* GetOrCreateModuleNamespace(JSContext* cx, + Handle<ModuleObject*> module); void AsyncModuleExecutionFulfilled(JSContext* cx, Handle<ModuleObject*> module); diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index 640a185981..19f8b94bb6 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -307,20 +307,26 @@ mozilla::Maybe<PropertyInfo> js::NativeObject::lookupPure(jsid id) { return mozilla::Nothing(); } -bool NativeObject::setUniqueId(JSContext* cx, uint64_t uid) { +bool NativeObject::setUniqueId(JSRuntime* runtime, uint64_t uid) { MOZ_ASSERT(!hasUniqueId()); MOZ_ASSERT(!gc::HasUniqueId(this)); - return setOrUpdateUniqueId(cx, uid); + Nursery& nursery = runtime->gc.nursery(); + if (!hasDynamicSlots() && !allocateSlots(nursery, 0)) { + return false; + } + + getSlotsHeader()->setUniqueId(uid); + return true; } bool NativeObject::setOrUpdateUniqueId(JSContext* cx, uint64_t uid) { - if (!hasDynamicSlots() && !allocateSlots(cx, 0)) { + if (!hasDynamicSlots() && !allocateSlots(cx->nursery(), 0)) { + ReportOutOfMemory(cx); return false; } getSlotsHeader()->setUniqueId(uid); - return true; } @@ -337,7 +343,12 @@ bool NativeObject::growSlots(JSContext* cx, uint32_t oldCapacity, MOZ_ASSERT(newCapacity <= MAX_SLOTS_COUNT); if (!hasDynamicSlots()) { - return allocateSlots(cx, newCapacity); + if (!allocateSlots(cx->nursery(), newCapacity)) { + ReportOutOfMemory(cx); + return false; + } + + return true; } uint64_t uid = maybeUniqueId(); @@ -415,7 +426,7 @@ bool NativeObject::allocateInitialSlots(JSContext* cx, uint32_t capacity) { return true; } -bool NativeObject::allocateSlots(JSContext* cx, uint32_t newCapacity) { +bool NativeObject::allocateSlots(Nursery& nursery, uint32_t newCapacity) { MOZ_ASSERT(!hasUniqueId()); MOZ_ASSERT(!hasDynamicSlots()); @@ -423,7 +434,8 @@ bool NativeObject::allocateSlots(JSContext* cx, uint32_t newCapacity) { uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan(); - HeapSlot* allocation = AllocateCellBuffer<HeapSlot>(cx, this, newAllocated); + HeapSlot* allocation = + AllocateCellBuffer<HeapSlot>(nursery, this, newAllocated); if (!allocation) { return false; } diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index 8312310219..18ca0b656e 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -987,7 +987,7 @@ class NativeObject : public JSObject { bool growSlotsForNewSlot(JSContext* cx, uint32_t numFixed, uint32_t slot); void shrinkSlots(JSContext* cx, uint32_t oldCapacity, uint32_t newCapacity); - bool allocateSlots(JSContext* cx, uint32_t newCapacity); + bool allocateSlots(Nursery& nursery, uint32_t newCapacity); /* * This method is static because it's called from JIT code. On OOM, returns @@ -1257,7 +1257,7 @@ class NativeObject : public JSObject { return UndefinedValue(); } - [[nodiscard]] bool setUniqueId(JSContext* cx, uint64_t uid); + [[nodiscard]] bool setUniqueId(JSRuntime* runtime, uint64_t uid); inline bool hasUniqueId() const { return getSlotsHeader()->hasUniqueId(); } inline uint64_t uniqueId() const { return getSlotsHeader()->uniqueId(); } inline uint64_t maybeUniqueId() const { diff --git a/js/src/vm/PortableBaselineInterpret.cpp b/js/src/vm/PortableBaselineInterpret.cpp index 2990942dc6..2588f12009 100644 --- a/js/src/vm/PortableBaselineInterpret.cpp +++ b/js/src/vm/PortableBaselineInterpret.cpp @@ -1373,9 +1373,13 @@ ICInterpretOps(BaselineFrame* frame, VMFrameManager& frameMgr, State& state, CACHEOP_CASE(LoadWrapperTarget) { ObjOperandId objId = icregs.cacheIRReader.objOperandId(); ObjOperandId resultId = icregs.cacheIRReader.objOperandId(); + bool fallible = icregs.cacheIRReader.readBool(); BOUNDSCHECK(resultId); JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]); - JSObject* target = &obj->as<ProxyObject>().private_().toObject(); + JSObject* target = obj->as<ProxyObject>().private_().toObjectOrNull(); + if (fallible && !target) { + return ICInterpretOpResult::NextIC; + } icregs.icVals[resultId.id()] = reinterpret_cast<uintptr_t>(target); DISPATCH_CACHEOP(); } diff --git a/js/src/vm/RealmFuses.cpp b/js/src/vm/RealmFuses.cpp index 3ceac2dd25..8f7a7801cc 100644 --- a/js/src/vm/RealmFuses.cpp +++ b/js/src/vm/RealmFuses.cpp @@ -11,6 +11,27 @@ #include "vm/Realm.h" #include "vm/SelfHosting.h" +void js::InvalidatingRealmFuse::popFuse(JSContext* cx, RealmFuses& realmFuses) { + InvalidatingFuse::popFuse(cx); + + for (auto& fd : realmFuses.fuseDependencies) { + fd.invalidateForFuse(cx, this); + } +} + +bool js::InvalidatingRealmFuse::addFuseDependency(JSContext* cx, + Handle<JSScript*> script) { + MOZ_ASSERT(script->realm() == cx->realm()); + auto* dss = + cx->realm()->realmFuses.fuseDependencies.getOrCreateDependentScriptSet( + cx, this); + if (!dss) { + return false; + } + + return dss->addScriptForFuse(this, script); +} + void js::PopsOptimizedGetIteratorFuse::popFuse(JSContext* cx, RealmFuses& realmFuses) { // Pop Self. diff --git a/js/src/vm/RealmFuses.h b/js/src/vm/RealmFuses.h index 54fa7bc3bf..a14ff73c59 100644 --- a/js/src/vm/RealmFuses.h +++ b/js/src/vm/RealmFuses.h @@ -8,6 +8,7 @@ #define vm_RealmFuses_h #include "vm/GuardFuse.h" +#include "vm/InvalidatingFuse.h" namespace js { @@ -28,7 +29,19 @@ class RealmFuse : public GuardFuse { virtual void popFuse(JSContext* cx) override { GuardFuse::popFuse(cx); } }; -struct OptimizeGetIteratorFuse final : public RealmFuse { +class InvalidatingRealmFuse : public InvalidatingFuse { + public: + virtual void popFuse(JSContext* cx, RealmFuses& realmFuses); + virtual bool addFuseDependency(JSContext* cx, + Handle<JSScript*> script) override; + + protected: + virtual void popFuse(JSContext* cx) override { + InvalidatingFuse::popFuse(cx); + } +}; + +struct OptimizeGetIteratorFuse final : public InvalidatingRealmFuse { virtual const char* name() override { return "OptimizeGetIteratorFuse"; } virtual bool checkInvariant(JSContext* cx) override; }; @@ -144,6 +157,8 @@ struct RealmFuses { MOZ_CRASH("Fuse Not Found"); } + DependentScriptGroup fuseDependencies; + static int32_t fuseOffsets[]; static const char* fuseNames[]; diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 1c3611a6e5..d894bf57bf 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -332,7 +332,7 @@ void JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, rtSizes->uncompressedSourceCache += caches().uncompressedSourceCache.sizeOfExcludingThis(mallocSizeOf); - rtSizes->gc.nurseryCommitted += gc.nursery().committed(); + rtSizes->gc.nurseryCommitted += gc.nursery().totalCommitted(); rtSizes->gc.nurseryMallocedBuffers += gc.nursery().sizeOfMallocedBuffers(mallocSizeOf); gc.storeBuffer().addSizeOfExcludingThis(mallocSizeOf, &rtSizes->gc); diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 32cdd4a335..5170b072fb 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -50,6 +50,7 @@ #include "frontend/FrontendContext.h" // AutoReportFrontendContext #include "jit/AtomicOperations.h" #include "jit/InlinableNatives.h" +#include "jit/TrampolineNatives.h" #include "js/CompilationAndEvaluation.h" #include "js/Conversions.h" #include "js/ErrorReport.h" // JS::PrintError @@ -900,7 +901,7 @@ static bool intrinsic_GeneratorSetClosed(JSContext* cx, unsigned argc, MOZ_ASSERT(args[0].isObject()); GeneratorObject* genObj = &args[0].toObject().as<GeneratorObject>(); - genObj->setClosed(); + genObj->setClosed(cx); return true; } @@ -1962,7 +1963,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_INLINABLE_FN("ArrayIteratorPrototypeOptimizable", intrinsic_ArrayIteratorPrototypeOptimizable, 0, 0, IntrinsicArrayIteratorPrototypeOptimizable), - JS_FN("ArrayNativeSort", intrinsic_ArrayNativeSort, 1, 0), JS_FN("AssertionFailed", intrinsic_AssertionFailed, 1, 0), JS_FN("CallArrayBufferMethodIfWrapped", CallNonGenericSelfhostedMethod<Is<ArrayBufferObject>>, 2, 0), @@ -2336,6 +2336,7 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("std_Array_indexOf", array_indexOf, 1, 0), JS_FN("std_Array_lastIndexOf", array_lastIndexOf, 1, 0), JS_INLINABLE_FN("std_Array_pop", array_pop, 0, 0, ArrayPop), + JS_TRAMPOLINE_FN("std_Array_sort", array_sort, 1, 0, ArraySort), JS_FN("std_BigInt_valueOf", BigIntObject::valueOf, 0, 0), JS_FN("std_Date_now", date_now, 0, 0), JS_FN("std_Function_apply", fun_apply, 2, 0), diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index d222ddc51c..c8a8898d49 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -509,9 +509,8 @@ void JS::ProfilingFrameIterator::operator++() { void JS::ProfilingFrameIterator::settleFrames() { // Handle transition frames (see comment in JitFrameIter::operator++). - if (isJSJit() && !jsJitIter().done() && - jsJitIter().frameType() == jit::FrameType::WasmToJSJit) { - wasm::Frame* fp = (wasm::Frame*)jsJitIter().fp(); + if (isJSJit() && jsJitIter().done() && jsJitIter().wasmCallerFP()) { + wasm::Frame* fp = (wasm::Frame*)jsJitIter().wasmCallerFP(); iteratorDestroy(); new (storage()) wasm::ProfilingFrameIterator(fp); kind_ = Kind::Wasm; @@ -529,7 +528,6 @@ void JS::ProfilingFrameIterator::settleFrames() { new (storage()) jit::JSJitProfilingFrameIterator((jit::CommonFrameLayout*)fp); kind_ = Kind::JSJit; - MOZ_ASSERT(!jsJitIter().done()); maybeSetEndStackAddress(jsJitIter().endStackAddress()); return; } diff --git a/js/src/vm/StringType-inl.h b/js/src/vm/StringType-inl.h index 8954a46aac..4548e5d7ea 100644 --- a/js/src/vm/StringType-inl.h +++ b/js/src/vm/StringType-inl.h @@ -19,6 +19,7 @@ #include "vm/StaticStrings.h" #include "gc/GCContext-inl.h" +#include "gc/Marking-inl.h" #include "gc/StoreBuffer-inl.h" #include "vm/JSContext-inl.h" @@ -451,6 +452,20 @@ void JSLinearString::disownCharsBecauseError() { d.s.u2.nonInlineCharsLatin1 = nullptr; } +inline JSLinearString* JSDependentString::rootBaseDuringMinorGC() { + JSLinearString* root = this; + while (MaybeForwarded(root)->hasBase()) { + if (root->isForwarded()) { + root = js::gc::StringRelocationOverlay::fromCell(root) + ->savedNurseryBaseOrRelocOverlay(); + } else { + // Possibly nursery or tenured string (not an overlay). + root = root->nurseryBaseOrRelocOverlay(); + } + } + return root; +} + template <js::AllowGC allowGC, typename CharT> MOZ_ALWAYS_INLINE JSLinearString* JSLinearString::new_( JSContext* cx, JS::MutableHandle<JSString::OwnedChars<CharT>> chars, @@ -530,6 +545,19 @@ inline js::PropertyName* JSLinearString::toPropertyName(JSContext* cx) { return atom->asPropertyName(); } +// String characters are movable in the following cases: +// +// 1. Inline nursery strings (moved during promotion) +// 2. Nursery strings with nursery chars (moved during promotion) +// 3. Nursery strings that are deduplicated (moved during promotion) +// 4. Inline tenured strings (moved during compaction) +// +// This method does not consider #3, because if this method returns true and the +// caller does not want the characters to move, it can fix them in place by +// setting the nondeduplicatable bit. (If the bit were already taken into +// consideration, then the caller wouldn't know whether the movability is +// "fixable" or not. If it is *only* movable because of the lack of the bit +// being set, then it is fixable by setting the bit.) bool JSLinearString::hasMovableChars() const { const JSLinearString* topBase = this; while (topBase->hasBase()) { diff --git a/js/src/vm/StringType.cpp b/js/src/vm/StringType.cpp index 63afd8864b..b735b91b71 100644 --- a/js/src/vm/StringType.cpp +++ b/js/src/vm/StringType.cpp @@ -380,9 +380,13 @@ void ForEachStringFlag(const JSString* str, uint32_t flags, KnownF known, static_assert(JSString::LINEAR_IS_EXTENSIBLE_BIT == JSString::INLINE_IS_FAT_BIT); if (str->isLinear()) { - known("EXTENSIBLE"); - } else if (str->isInline()) { - known("FAT"); + if (str->isInline()) { + known("FAT"); + } else if (!str->isAtom()) { + known("EXTENSIBLE"); + } else { + unknown(i); + } } else { unknown(i); } @@ -407,9 +411,6 @@ void ForEachStringFlag(const JSString* str, uint32_t flags, KnownF known, case JSString::INDEX_VALUE_BIT: known("INDEX_VALUE_BIT"); break; - case JSString::NON_DEDUP_BIT: - known("NON_DEDUP_BIT"); - break; case JSString::IN_STRING_TO_ATOM_CACHE: known("IN_STRING_TO_ATOM_CACHE"); break; @@ -417,7 +418,7 @@ void ForEachStringFlag(const JSString* str, uint32_t flags, KnownF known, if (str->isRope()) { known("FLATTEN_VISIT_RIGHT"); } else { - unknown(i); + known("NON_DEDUP_BIT"); } break; case JSString::FLATTEN_FINISH_NODE: @@ -638,11 +639,20 @@ static MOZ_ALWAYS_INLINE JSString::OwnedChars<CharT> AllocChars(JSContext* cx, MOZ_ASSERT(cx->nursery().isEnabled()); auto [buffer, isMalloced] = cx->nursery().allocateBuffer( cx->zone(), length * sizeof(CharT), js::StringBufferArena); + if (!buffer) { + ReportOutOfMemory(cx); + return {nullptr, 0, false, false}; + } return {static_cast<CharT*>(buffer), length, isMalloced, isMalloced}; } auto buffer = cx->make_pod_arena_array<CharT>(js::StringBufferArena, length); + if (!buffer) { + ReportOutOfMemory(cx); + return {nullptr, 0, false, false}; + } + return {std::move(buffer), length, true}; } @@ -914,6 +924,14 @@ JSLinearString* JSRope::flattenInternal(JSRope* root) { * JSDependentStrings pointing to them already. Stealing the buffer doesn't * change its address, only its owning JSExtensibleString, so all chars() * pointers in the JSDependentStrings are still valid. + * + * This chain of dependent strings could be problematic if the base string + * moves, either because it was initially allocated in the nursery or it + * gets deduplicated, because you might have a dependent -> + * tenured dependent -> nursery base string, and the store buffer would + * only capture the latter edge. Prevent this case from happening by + * marking the root as nondeduplicatable if the extensible string + * optimization applied. */ const size_t wholeLength = root->length(); size_t wholeCapacity; @@ -1068,8 +1086,12 @@ finish_root: left.setLengthAndFlags(left.length(), StringFlagsForCharType<CharT>(flags)); left.d.s.u3.base = &root->asLinear(); if (left.isTenured() && !root->isTenured()) { - // leftmost child -> root is a tenured -> nursery edge. + // leftmost child -> root is a tenured -> nursery edge. Put the leftmost + // child in the store buffer and prevent the root's chars from moving or + // being freed (because the leftmost child may have a tenured dependent + // string that cannot be updated.) root->storeBuffer()->putWholeCell(&left); + root->setNonDeduplicatable(); } } @@ -1455,16 +1477,18 @@ uint32_t JSAtom::getIndexSlow() const { : AtomCharsToIndex(twoByteChars(nogc), len); } -static void MarkStringAndBasesNonDeduplicatable(JSLinearString* s) { - while (true) { - if (!s->isTenured()) { - s->setNonDeduplicatable(); - } - if (!s->hasBase()) { - break; - } +// Prevent the actual owner of the string's characters from being deduplicated +// (and thus freeing its characters, which would invalidate the ASSC's chars +// pointer). Intermediate dependent strings on the chain can be deduplicated, +// since the base will be updated to the root base during tenuring anyway and +// the intermediates won't matter. +void PreventRootBaseDeduplication(JSLinearString* s) { + while (s->hasBase()) { s = s->base(); } + if (!s->isTenured()) { + s->setNonDeduplicatable(); + } } bool AutoStableStringChars::init(JSContext* cx, JSString* s) { @@ -1492,7 +1516,7 @@ bool AutoStableStringChars::init(JSContext* cx, JSString* s) { twoByteChars_ = linearString->rawTwoByteChars(); } - MarkStringAndBasesNonDeduplicatable(linearString); + PreventRootBaseDeduplication(linearString); s_ = linearString; return true; @@ -1518,7 +1542,7 @@ bool AutoStableStringChars::initTwoByte(JSContext* cx, JSString* s) { state_ = TwoByte; twoByteChars_ = linearString->rawTwoByteChars(); - MarkStringAndBasesNonDeduplicatable(linearString); + PreventRootBaseDeduplication(linearString); s_ = linearString; return true; diff --git a/js/src/vm/StringType.h b/js/src/vm/StringType.h index f2850c33a4..38dea85c60 100644 --- a/js/src/vm/StringType.h +++ b/js/src/vm/StringType.h @@ -407,8 +407,10 @@ class JSString : public js::gc::CellWithLengthAndFlags { static const uint32_t INDEX_VALUE_BIT = js::Bit(11); static const uint32_t INDEX_VALUE_SHIFT = 16; - // NON_DEDUP_BIT is used in string deduplication during tenuring. - static const uint32_t NON_DEDUP_BIT = js::Bit(12); + // NON_DEDUP_BIT is used in string deduplication during tenuring. This bit is + // shared with both FLATTEN_FINISH_NODE and ATOM_IS_PERMANENT_BIT, since it + // only applies to linear non-atoms. + static const uint32_t NON_DEDUP_BIT = js::Bit(15); // If IN_STRING_TO_ATOM_CACHE is set, this string had an entry in the // StringToAtomCache at some point. Note that GC can purge the cache without @@ -627,13 +629,27 @@ class JSString : public js::gc::CellWithLengthAndFlags { } MOZ_ALWAYS_INLINE - void setNonDeduplicatable() { setFlagBit(NON_DEDUP_BIT); } + void setNonDeduplicatable() { + MOZ_ASSERT(isLinear()); + MOZ_ASSERT(!isAtom()); + setFlagBit(NON_DEDUP_BIT); + } + // After copying a string from the nursery to the tenured heap, adjust bits + // that no longer apply. MOZ_ALWAYS_INLINE - void clearNonDeduplicatable() { clearFlagBit(NON_DEDUP_BIT); } + void clearBitsOnTenure() { + MOZ_ASSERT(!isAtom()); + clearFlagBit(NON_DEDUP_BIT | IN_STRING_TO_ATOM_CACHE); + } + // NON_DEDUP_BIT is only valid for linear non-atoms. MOZ_ALWAYS_INLINE - bool isDeduplicatable() { return !(flags() & NON_DEDUP_BIT); } + bool isDeduplicatable() { + MOZ_ASSERT(isLinear()); + MOZ_ASSERT(!isAtom()); + return !(flags() & NON_DEDUP_BIT); + } void setInStringToAtomCache() { MOZ_ASSERT(!isAtom()); @@ -659,6 +675,7 @@ class JSString : public js::gc::CellWithLengthAndFlags { inline bool canOwnDependentChars() const; + // Only called by the GC during nursery collection. inline void setBase(JSLinearString* newBase); void traceBase(JSTracer* trc); @@ -781,6 +798,8 @@ class JSString : public js::gc::CellWithLengthAndFlags { void traceChildren(JSTracer* trc); + inline void traceBaseFromStoreBuffer(JSTracer* trc); + // Override base class implementation to tell GC about permanent atoms. bool isPermanentAndMayBeShared() const { return isPermanentAtom(); } @@ -930,6 +949,7 @@ class JSLinearString : public JSString { friend class JS::AutoStableStringChars; friend class js::gc::TenuringTracer; friend class js::gc::CellAllocator; + friend class JSDependentString; // To allow access when used as base. /* Vacuous and therefore unimplemented. */ JSLinearString* ensureLinear(JSContext* cx) = delete; @@ -1138,6 +1158,13 @@ class JSDependentString : public JSLinearString { setNonInlineChars(chars + offset); } + inline JSLinearString* rootBaseDuringMinorGC(); + + template <typename CharT> + inline void sweepTypedAfterMinorGC(); + + inline void sweepAfterMinorGC(); + #if defined(DEBUG) || defined(JS_JITSPEW) || defined(JS_CACHEIR_SPEW) void dumpOwnRepresentationFields(js::JSONPrinter& json) const; #endif @@ -2260,14 +2287,17 @@ class StringRelocationOverlay : public RelocationOverlay { MOZ_ALWAYS_INLINE const CharT* savedNurseryChars() const; const MOZ_ALWAYS_INLINE JS::Latin1Char* savedNurseryCharsLatin1() const { + MOZ_ASSERT(!forwardingAddress()->as<JSString>()->hasBase()); return nurseryCharsLatin1; } const MOZ_ALWAYS_INLINE char16_t* savedNurseryCharsTwoByte() const { + MOZ_ASSERT(!forwardingAddress()->as<JSString>()->hasBase()); return nurseryCharsTwoByte; } JSLinearString* savedNurseryBaseOrRelocOverlay() const { + MOZ_ASSERT(forwardingAddress()->as<JSString>()->hasBase()); return nurseryBaseOrRelocOverlay; } diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index e2e67a2ee3..405c18670b 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -893,7 +893,7 @@ bool SCInput::readArray(T* p, size_t nelems) { // To avoid any way in which uninitialized data could escape, zero the array // if filling it failed. std::uninitialized_fill_n(p, nelems, 0); - return false; + return reportTruncated(); } swapFromLittleEndianInPlace(p, nelems); diff --git a/js/src/vm/TypedArrayObject-inl.h b/js/src/vm/TypedArrayObject-inl.h index 95ba7b23d4..ffb9a3c9f6 100644 --- a/js/src/vm/TypedArrayObject-inl.h +++ b/js/src/vm/TypedArrayObject-inl.h @@ -760,6 +760,26 @@ class ElementSpecific { } }; +inline gc::AllocKind js::FixedLengthTypedArrayObject::allocKindForTenure() + const { + // Fixed length typed arrays in the nursery may have a lazily allocated + // buffer. Make sure there is room for the array's fixed data when moving the + // array. + + if (hasBuffer()) { + return NativeObject::allocKindForTenure(); + } + + gc::AllocKind allocKind; + if (hasInlineElements()) { + allocKind = AllocKindForLazyBuffer(byteLength()); + } else { + allocKind = gc::GetGCObjectKind(getClass()); + } + + return gc::ForegroundToBackgroundAllocKind(allocKind); +} + /* static */ gc::AllocKind js::FixedLengthTypedArrayObject::AllocKindForLazyBuffer(size_t nbytes) { MOZ_ASSERT(nbytes <= INLINE_BUFFER_LIMIT); diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 35a2237cd5..935a902abe 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -7,8 +7,10 @@ #include "vm/TypedArrayObject-inl.h" #include "vm/TypedArrayObject.h" +#include "mozilla/CheckedInt.h" #include "mozilla/FloatingPoint.h" #include "mozilla/IntegerTypeTraits.h" +#include "mozilla/Likely.h" #include "mozilla/PodOperations.h" #include "mozilla/TextUtils.h" @@ -39,6 +41,7 @@ #include "js/UniquePtr.h" #include "js/Wrapper.h" #include "util/DifferentialTesting.h" +#include "util/StringBuffer.h" #include "util/Text.h" #include "util/WindowsWrapper.h" #include "vm/ArrayBufferObject.h" @@ -114,6 +117,13 @@ static bool IsTypedArrayObject(HandleValue v) { return v.isObject() && v.toObject().is<TypedArrayObject>(); } +#ifdef NIGHTLY_BUILD +static bool IsUint8ArrayObject(HandleValue v) { + return IsTypedArrayObject(v) && + v.toObject().as<TypedArrayObject>().type() == Scalar::Uint8; +} +#endif + /* static */ bool TypedArrayObject::ensureHasBuffer(JSContext* cx, Handle<TypedArrayObject*> typedArray) { @@ -199,7 +209,6 @@ size_t FixedLengthTypedArrayObject::objectMoved(JSObject* obj, JSObject* old) { auto* newObj = &obj->as<FixedLengthTypedArrayObject>(); const auto* oldObj = &old->as<FixedLengthTypedArrayObject>(); MOZ_ASSERT(newObj->elementsRaw() == oldObj->elementsRaw()); - MOZ_ASSERT(obj->isTenured()); // Typed arrays with a buffer object do not need an update. if (oldObj->hasBuffer()) { @@ -233,13 +242,13 @@ size_t FixedLengthTypedArrayObject::objectMoved(JSObject* obj, JSObject* old) { constexpr size_t headerSize = dataOffset() + sizeof(HeapSlot); - // See AllocKindForLazyBuffer. - gc::AllocKind newAllocKind = obj->asTenured().getAllocKind(); + gc::AllocKind allocKind = oldObj->allocKindForTenure(); + MOZ_ASSERT_IF(obj->isTenured(), obj->asTenured().getAllocKind() == allocKind); MOZ_ASSERT_IF(nbytes == 0, - headerSize + sizeof(uint8_t) <= GetGCKindBytes(newAllocKind)); + headerSize + sizeof(uint8_t) <= GetGCKindBytes(allocKind)); if (nursery.isInside(buf) && - headerSize + nbytes <= GetGCKindBytes(newAllocKind)) { + headerSize + nbytes <= GetGCKindBytes(allocKind)) { MOZ_ASSERT(oldObj->hasInlineElements()); #ifdef DEBUG if (nbytes == 0) { @@ -2046,6 +2055,1083 @@ bool TypedArrayObject::copyWithin(JSContext* cx, unsigned argc, Value* vp) { TypedArrayObject::copyWithin_impl>(cx, args); } +#ifdef NIGHTLY_BUILD + +// Byte vector with large enough inline storage to allow constructing small +// typed arrays without extra heap allocations. +using ByteVector = + js::Vector<uint8_t, FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT>; + +static UniqueChars QuoteString(JSContext* cx, char16_t ch) { + Sprinter sprinter(cx); + if (!sprinter.init()) { + return nullptr; + } + + StringEscape esc{}; + js::EscapePrinter ep(sprinter, esc); + ep.putChar(ch); + + return sprinter.release(); +} + +/** + * FromHex ( string [ , maxLength ] ) + * + * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-fromhex + */ +static bool FromHex(JSContext* cx, Handle<JSString*> string, size_t maxLength, + ByteVector& bytes, size_t* readLength) { + // Step 1. (Not applicable in our implementation.) + + // Step 2. + size_t length = string->length(); + + // Step 3. + if (length % 2 != 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_BAD_HEX_STRING_LENGTH); + return false; + } + + JSLinearString* linear = string->ensureLinear(cx); + if (!linear) { + return false; + } + + // Step 4. (Not applicable in our implementation.) + MOZ_ASSERT(bytes.empty()); + + // Step 5. + size_t index = 0; + + // Step 6. + while (index < length && bytes.length() < maxLength) { + // Step 6.a. + char16_t c0 = linear->latin1OrTwoByteChar(index); + char16_t c1 = linear->latin1OrTwoByteChar(index + 1); + + // Step 6.b. + if (MOZ_UNLIKELY(!mozilla::IsAsciiHexDigit(c0) || + !mozilla::IsAsciiHexDigit(c1))) { + char16_t ch = !mozilla::IsAsciiHexDigit(c0) ? c0 : c1; + if (auto str = QuoteString(cx, ch)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_BAD_HEX_DIGIT, str.get()); + } + return false; + } + + // Step 6.c. + index += 2; + + // Step 6.d. + uint8_t byte = (mozilla::AsciiAlphanumericToNumber(c0) << 4) + + mozilla::AsciiAlphanumericToNumber(c1); + + // Step 6.e. + if (!bytes.append(byte)) { + return false; + } + } + + // Step 7. + *readLength = index; + return true; +} + +namespace Base64 { +static constexpr uint8_t InvalidChar = UINT8_MAX; + +static constexpr auto DecodeTable(const char (&alphabet)[65]) { + std::array<uint8_t, 128> result = {}; + + // Initialize all elements to InvalidChar. + for (auto& e : result) { + e = InvalidChar; + } + + // Map the base64 characters to their values. + for (uint8_t i = 0; i < 64; ++i) { + result[alphabet[i]] = i; + } + + return result; +} +} // namespace Base64 + +namespace Base64::Encode { +static constexpr const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static_assert(std::char_traits<char>::length(Base64) == 64); + +static constexpr const char Base64Url[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +static_assert(std::char_traits<char>::length(Base64Url) == 64); +} // namespace Base64::Encode + +namespace Base64::Decode { +static constexpr auto Base64 = DecodeTable(Base64::Encode::Base64); +static_assert(Base64.size() == 128, + "128 elements to allow access through ASCII characters"); + +static constexpr auto Base64Url = DecodeTable(Base64::Encode::Base64Url); +static_assert(Base64Url.size() == 128, + "128 elements to allow access through ASCII characters"); +} // namespace Base64::Decode + +enum class Alphabet { + /** + * Standard base64 alphabet. + */ + Base64, + + /** + * URL and filename safe base64 alphabet. + */ + Base64Url, +}; + +enum class LastChunkHandling { + /** + * Allow partial chunks at the end of the input. + */ + Loose, + + /** + * Disallow partial chunks at the end of the input. + */ + Strict, + + /** + * Stop before partial chunks at the end of the input. + */ + StopBeforePartial, +}; + +/** + * FromBase64 ( string, alphabet, lastChunkHandling [ , maxLength ] ) + * + * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-frombase64 + */ +static bool FromBase64(JSContext* cx, Handle<JSString*> string, + Alphabet alphabet, LastChunkHandling lastChunkHandling, + size_t maxLength, ByteVector& bytes, + size_t* readLength) { + // Steps 1-2. (Not applicable in our implementation.) + + // Step 3. + size_t remaining = maxLength; + if (remaining == 0) { + MOZ_ASSERT(bytes.empty()); + *readLength = 0; + return true; + } + + JSLinearString* linear = string->ensureLinear(cx); + if (!linear) { + return false; + } + + // DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] ) + // + // Encode a complete base64 chunk. + auto decodeChunk = [&](uint32_t chunk) { + MOZ_ASSERT(chunk <= 0xffffff); + MOZ_ASSERT(remaining >= 3); + + if (!bytes.reserve(bytes.length() + 3)) { + return false; + } + bytes.infallibleAppend(chunk >> 16); + bytes.infallibleAppend(chunk >> 8); + bytes.infallibleAppend(chunk); + return true; + }; + + // DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] ) + // + // Encode a three element partial base64 chunk. + auto decodeChunk3 = [&](uint32_t chunk, bool throwOnExtraBits) { + MOZ_ASSERT(chunk <= 0x3ffff); + MOZ_ASSERT(remaining >= 2); + + if (throwOnExtraBits && (chunk & 0x3) != 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_EXTRA_BASE64_BITS); + return false; + } + + if (!bytes.reserve(bytes.length() + 2)) { + return false; + } + bytes.infallibleAppend(chunk >> 10); + bytes.infallibleAppend(chunk >> 2); + return true; + }; + + // DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] ) + // + // Encode a two element partial base64 chunk. + auto decodeChunk2 = [&](uint32_t chunk, bool throwOnExtraBits) { + MOZ_ASSERT(chunk <= 0xfff); + MOZ_ASSERT(remaining >= 1); + + if (throwOnExtraBits && (chunk & 0xf) != 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_EXTRA_BASE64_BITS); + return false; + } + + if (!bytes.reserve(bytes.length() + 1)) { + return false; + } + bytes.infallibleAppend(chunk >> 4); + return true; + }; + + // DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] ) + // + // Encode a partial base64 chunk. + auto decodePartialChunk = [&](uint32_t chunk, uint32_t chunkLength, + bool throwOnExtraBits = false) { + MOZ_ASSERT(chunkLength == 2 || chunkLength == 3); + return chunkLength == 2 ? decodeChunk2(chunk, throwOnExtraBits) + : decodeChunk3(chunk, throwOnExtraBits); + }; + + // Step 4. + // + // String index after the last fully read base64 chunk. + size_t read = 0; + + // Step 5. + MOZ_ASSERT(bytes.empty()); + + // Step 6. + // + // Current base64 chunk, a uint24 number. + uint32_t chunk = 0; + + // Step 7. + // + // Current base64 chunk length, in the range [0..4]. + size_t chunkLength = 0; + + // Step 8. + // + // Current string index. + size_t index = 0; + + // Step 9. + size_t length = linear->length(); + + const auto& decode = alphabet == Alphabet::Base64 ? Base64::Decode::Base64 + : Base64::Decode::Base64Url; + + // Step 10. + for (; index < length; index++) { + // Step 10.c. (Reordered) + char16_t ch = linear->latin1OrTwoByteChar(index); + + // Step 10.a. + if (mozilla::IsAsciiWhitespace(ch)) { + continue; + } + + // Step 10.b. (Moved out of loop.) + + // Step 10.d. (Performed in for-loop step.) + + // Step 10.e. + if (ch == '=') { + break; + } + + // Steps 10.f-g. + uint8_t value = Base64::InvalidChar; + if (mozilla::IsAscii(ch)) { + value = decode[ch]; + } + if (MOZ_UNLIKELY(value == Base64::InvalidChar)) { + if (auto str = QuoteString(cx, ch)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_BAD_BASE64_CHAR, str.get()); + } + return false; + } + + // Step 10.h. (Not applicable in our implementation.) + + // Step 10.i. + if ((remaining == 1 && chunkLength == 2) || + (remaining == 2 && chunkLength == 3)) { + *readLength = read; + return true; + } + + // Step 10.j. + chunk = (chunk << 6) | value; + + // Step 10.k. + chunkLength += 1; + + // Step 10.l. + if (chunkLength == 4) { + // Step 10.l.i. + if (!decodeChunk(chunk)) { + return false; + } + + // Step 10.l.ii. + chunk = 0; + + // Step 10.l.iii. + chunkLength = 0; + + // Step 10.l.iv. + // + // NB: Add +1 to include the |index| update from step 10.d. + read = index + 1; + + // Step 10.l.v. + MOZ_ASSERT(remaining >= 3); + remaining -= 3; + if (remaining == 0) { + *readLength = read; + return true; + } + } + } + + // Step 10.b. + if (index == length) { + // Step 10.b.i. + if (chunkLength > 0) { + // Step 10.b.i.1. + if (lastChunkHandling == LastChunkHandling::StopBeforePartial) { + *readLength = read; + return true; + } + + // Steps 10.b.i.2-3. + if (lastChunkHandling == LastChunkHandling::Loose) { + // Step 10.b.i.2.a. + if (chunkLength == 1) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK); + return false; + } + MOZ_ASSERT(chunkLength == 2 || chunkLength == 3); + + // Step 10.b.i.2.b. + if (!decodePartialChunk(chunk, chunkLength)) { + return false; + } + } else { + // Step 10.b.i.3.a. + MOZ_ASSERT(lastChunkHandling == LastChunkHandling::Strict); + + // Step 10.b.i.3.b. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK); + return false; + } + } + + // Step 10.b.ii. + *readLength = length; + return true; + } + + // Step 10.e. + MOZ_ASSERT(index < length); + MOZ_ASSERT(linear->latin1OrTwoByteChar(index) == '='); + + // Step 10.e.i. + if (chunkLength < 2) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK); + return false; + } + MOZ_ASSERT(chunkLength == 2 || chunkLength == 3); + + // Step 10.e.ii. (Inlined SkipAsciiWhitespace) + while (++index < length) { + char16_t ch = linear->latin1OrTwoByteChar(index); + if (!mozilla::IsAsciiWhitespace(ch)) { + break; + } + } + + // Step 10.e.iii. + if (chunkLength == 2) { + // Step 10.e.iii.1. + if (index == length) { + // Step 10.e.iii.1.a. + if (lastChunkHandling == LastChunkHandling::StopBeforePartial) { + *readLength = read; + return true; + } + + // Step 10.e.iii.1.b. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_MISSING_BASE64_PADDING); + return false; + } + + // Step 10.e.iii.2. + char16_t ch = linear->latin1OrTwoByteChar(index); + + // Step 10.e.iii.3. + if (ch == '=') { + // Step 10.e.iii.3.a. (Inlined SkipAsciiWhitespace) + while (++index < length) { + char16_t ch = linear->latin1OrTwoByteChar(index); + if (!mozilla::IsAsciiWhitespace(ch)) { + break; + } + } + } + } + + // Step 10.e.iv. + if (index < length) { + char16_t ch = linear->latin1OrTwoByteChar(index); + if (auto str = QuoteString(cx, ch)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_BAD_BASE64_AFTER_PADDING, + str.get()); + } + return false; + } + + // Steps 10.e.v-vi. + bool throwOnExtraBits = lastChunkHandling == LastChunkHandling::Strict; + + // Step 10.e.vii. + if (!decodePartialChunk(chunk, chunkLength, throwOnExtraBits)) { + return false; + } + + // Step 10.e.viii. + *readLength = length; + return true; +} + +/** + * Uint8Array.fromBase64 ( string [ , options ] ) + * Uint8Array.prototype.setFromBase64 ( string [ , options ] ) + * Uint8Array.prototype.toBase64 ( [ options ] ) + * + * Helper to retrieve the "alphabet" option. + */ +static bool GetAlphabetOption(JSContext* cx, Handle<JSObject*> options, + Alphabet* result) { + Rooted<Value> value(cx); + if (!GetProperty(cx, options, options, cx->names().alphabet, &value)) { + return false; + } + + if (value.isUndefined()) { + *result = Alphabet::Base64; + return true; + } + + if (!value.isString()) { + return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, + value, nullptr, "not a string"); + } + + auto* linear = value.toString()->ensureLinear(cx); + if (!linear) { + return false; + } + + if (StringEqualsAscii(linear, "base64")) { + *result = Alphabet::Base64; + return true; + } + + if (StringEqualsAscii(linear, "base64url")) { + *result = Alphabet::Base64Url; + return true; + } + + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_BAD_BASE64_ALPHABET); + return false; +} + +/** + * Uint8Array.fromBase64 ( string [ , options ] ) + * Uint8Array.prototype.setFromBase64 ( string [ , options ] ) + * + * Helper to retrieve the "lastChunkHandling" option. + */ +static bool GetLastChunkHandlingOption(JSContext* cx, Handle<JSObject*> options, + LastChunkHandling* result) { + Rooted<Value> value(cx); + if (!GetProperty(cx, options, options, cx->names().lastChunkHandling, + &value)) { + return false; + } + + if (value.isUndefined()) { + *result = LastChunkHandling::Loose; + return true; + } + + if (!value.isString()) { + return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, + value, nullptr, "not a string"); + } + + auto* linear = value.toString()->ensureLinear(cx); + if (!linear) { + return false; + } + + if (StringEqualsAscii(linear, "loose")) { + *result = LastChunkHandling::Loose; + return true; + } + + if (StringEqualsAscii(linear, "strict")) { + *result = LastChunkHandling::Strict; + return true; + } + + if (StringEqualsAscii(linear, "stop-before-partial")) { + *result = LastChunkHandling::StopBeforePartial; + return true; + } + + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_BAD_BASE64_LAST_CHUNK_HANDLING); + return false; +} + +/** + * Uint8Array.fromBase64 ( string [ , options ] ) + * + * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.frombase64 + */ +static bool uint8array_fromBase64(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (!args.get(0).isString()) { + return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, + args.get(0), nullptr, "not a string"); + } + Rooted<JSString*> string(cx, args[0].toString()); + + // Steps 2-9. + auto alphabet = Alphabet::Base64; + auto lastChunkHandling = LastChunkHandling::Loose; + if (args.hasDefined(1)) { + // Step 2. (Inlined GetOptionsObject) + Rooted<JSObject*> options( + cx, RequireObjectArg(cx, "options", "fromBase64", args[1])); + if (!options) { + return false; + } + + // Steps 3-6. + if (!GetAlphabetOption(cx, options, &alphabet)) { + return false; + } + + // Steps 7-9. + if (!GetLastChunkHandlingOption(cx, options, &lastChunkHandling)) { + return false; + } + } + + // Step 10. + constexpr size_t maxLength = std::numeric_limits<size_t>::max(); + ByteVector bytes(cx); + size_t unusedReadLength; + if (!FromBase64(cx, string, alphabet, lastChunkHandling, maxLength, bytes, + &unusedReadLength)) { + return false; + } + + // Step 11. + size_t resultLength = bytes.length(); + + // Step 12. + auto* tarray = + TypedArrayObjectTemplate<uint8_t>::fromLength(cx, resultLength); + if (!tarray) { + return false; + } + + // Step 13. + auto target = SharedMem<uint8_t*>::unshared(tarray->dataPointerUnshared()); + auto source = SharedMem<uint8_t*>::unshared(bytes.begin()); + UnsharedOps::podCopy(target, source, resultLength); + + // Step 14. + args.rval().setObject(*tarray); + return true; +} + +/** + * Uint8Array.fromHex ( string ) + * + * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.fromhex + */ +static bool uint8array_fromHex(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (!args.get(0).isString()) { + return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, + args.get(0), nullptr, "not a string"); + } + Rooted<JSString*> string(cx, args[0].toString()); + + // Step 2. + constexpr size_t maxLength = std::numeric_limits<size_t>::max(); + ByteVector bytes(cx); + size_t unusedReadLength; + if (!FromHex(cx, string, maxLength, bytes, &unusedReadLength)) { + return false; + } + + // Step 3. + size_t resultLength = bytes.length(); + + // Step 4. + auto* tarray = + TypedArrayObjectTemplate<uint8_t>::fromLength(cx, resultLength); + if (!tarray) { + return false; + } + + // Step 5. + auto target = SharedMem<uint8_t*>::unshared(tarray->dataPointerUnshared()); + auto source = SharedMem<uint8_t*>::unshared(bytes.begin()); + UnsharedOps::podCopy(target, source, resultLength); + + // Step 6. + args.rval().setObject(*tarray); + return true; +} + +/** + * Uint8Array.prototype.setFromBase64 ( string [ , options ] ) + * + * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfrombase64 + */ +static bool uint8array_setFromBase64(JSContext* cx, const CallArgs& args) { + Rooted<TypedArrayObject*> tarray( + cx, &args.thisv().toObject().as<TypedArrayObject>()); + + // Step 3. + if (!args.get(0).isString()) { + return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, + args.get(0), nullptr, "not a string"); + } + Rooted<JSString*> string(cx, args[0].toString()); + + // Steps 4-11. + auto alphabet = Alphabet::Base64; + auto lastChunkHandling = LastChunkHandling::Loose; + if (args.hasDefined(1)) { + // Step 2. (Inlined GetOptionsObject) + Rooted<JSObject*> options( + cx, RequireObjectArg(cx, "options", "setFromBase64", args[1])); + if (!options) { + return false; + } + + // Steps 3-6. + if (!GetAlphabetOption(cx, options, &alphabet)) { + return false; + } + + // Steps 7-9. + if (!GetLastChunkHandlingOption(cx, options, &lastChunkHandling)) { + return false; + } + } + + // Steps 12-14. + auto length = tarray->length(); + if (!length) { + ReportOutOfBounds(cx, tarray); + return false; + } + + // Step 15. + size_t maxLength = *length; + + // Steps 16-17. + ByteVector bytes(cx); + size_t readLength; + if (!FromBase64(cx, string, alphabet, lastChunkHandling, maxLength, bytes, + &readLength)) { + return false; + } + + // Step 18. + size_t written = bytes.length(); + + // Step 19. + // + // The underlying buffer has neither been detached nor shrunk. (It may have + // been grown when it's a growable shared buffer and a concurrent thread + // resized the buffer.) + MOZ_ASSERT(!tarray->hasDetachedBuffer()); + MOZ_ASSERT(tarray->length().valueOr(0) >= *length); + + // Step 20. + MOZ_ASSERT(written <= *length); + + // Step 21. (Inlined SetUint8ArrayBytes) + auto target = tarray->dataPointerEither().cast<uint8_t*>(); + auto source = SharedMem<uint8_t*>::unshared(bytes.begin()); + if (tarray->isSharedMemory()) { + SharedOps::podCopy(target, source, written); + } else { + UnsharedOps::podCopy(target, source, written); + } + + // Step 22. + Rooted<PlainObject*> result(cx, NewPlainObject(cx)); + if (!result) { + return false; + } + + // Step 23. + Rooted<Value> readValue(cx, NumberValue(readLength)); + if (!DefineDataProperty(cx, result, cx->names().read, readValue)) { + return false; + } + + // Step 24. + Rooted<Value> writtenValue(cx, NumberValue(written)); + if (!DefineDataProperty(cx, result, cx->names().written, writtenValue)) { + return false; + } + + // Step 25. + args.rval().setObject(*result); + return true; +} + +/** + * Uint8Array.prototype.setFromBase64 ( string [ , options ] ) + * + * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfrombase64 + */ +static bool uint8array_setFromBase64(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-2. + return CallNonGenericMethod<IsUint8ArrayObject, uint8array_setFromBase64>( + cx, args); +} + +/** + * Uint8Array.prototype.setFromHex ( string ) + * + * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfromhex + */ +static bool uint8array_setFromHex(JSContext* cx, const CallArgs& args) { + Rooted<TypedArrayObject*> tarray( + cx, &args.thisv().toObject().as<TypedArrayObject>()); + + // Step 3. + if (!args.get(0).isString()) { + return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, + args.get(0), nullptr, "not a string"); + } + Rooted<JSString*> string(cx, args[0].toString()); + + // Steps 4-6. + auto length = tarray->length(); + if (!length) { + ReportOutOfBounds(cx, tarray); + return false; + } + + // Step 7. + size_t maxLength = *length; + + // Steps 8-9. + ByteVector bytes(cx); + size_t readLength; + if (!FromHex(cx, string, maxLength, bytes, &readLength)) { + return false; + } + + // Step 10. + size_t written = bytes.length(); + + // Step 11. + // + // The underlying buffer has neither been detached nor shrunk. (It may have + // been grown when it's a growable shared buffer and a concurrent thread + // resized the buffer.) + MOZ_ASSERT(!tarray->hasDetachedBuffer()); + MOZ_ASSERT(tarray->length().valueOr(0) >= *length); + + // Step 12. + MOZ_ASSERT(written <= *length); + + // Step 13. (Inlined SetUint8ArrayBytes) + auto target = tarray->dataPointerEither().cast<uint8_t*>(); + auto source = SharedMem<uint8_t*>::unshared(bytes.begin()); + if (tarray->isSharedMemory()) { + SharedOps::podCopy(target, source, written); + } else { + UnsharedOps::podCopy(target, source, written); + } + + // Step 14. + Rooted<PlainObject*> result(cx, NewPlainObject(cx)); + if (!result) { + return false; + } + + // Step 15. + Rooted<Value> readValue(cx, NumberValue(readLength)); + if (!DefineDataProperty(cx, result, cx->names().read, readValue)) { + return false; + } + + // Step 16. + Rooted<Value> writtenValue(cx, NumberValue(written)); + if (!DefineDataProperty(cx, result, cx->names().written, writtenValue)) { + return false; + } + + // Step 17. + args.rval().setObject(*result); + return true; +} + +/** + * Uint8Array.prototype.setFromHex ( string ) + * + * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfromhex + */ +static bool uint8array_setFromHex(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-2. + return CallNonGenericMethod<IsUint8ArrayObject, uint8array_setFromHex>(cx, + args); +} + +/** + * Uint8Array.prototype.toBase64 ( [ options ] ) + * + * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64 + */ +static bool uint8array_toBase64(JSContext* cx, const CallArgs& args) { + Rooted<TypedArrayObject*> tarray( + cx, &args.thisv().toObject().as<TypedArrayObject>()); + + // Steps 3-7. + auto alphabet = Alphabet::Base64; + if (args.hasDefined(0)) { + // Step 3. (Inlined GetOptionsObject) + Rooted<JSObject*> options( + cx, RequireObjectArg(cx, "options", "toBase64", args[0])); + if (!options) { + return false; + } + + // Steps 4-7. + if (!GetAlphabetOption(cx, options, &alphabet)) { + return false; + } + } + + // Step 8. (Partial) + auto length = tarray->length(); + if (!length) { + ReportOutOfBounds(cx, tarray); + return false; + } + + // Compute the output string length. Three input bytes are encoded as four + // characters, so the output length is ⌈length × 4/3⌉. + auto outLength = mozilla::CheckedInt<size_t>{*length}; + outLength += 2; + outLength /= 3; + outLength *= 4; + if (!outLength.isValid() || outLength.value() > JSString::MAX_LENGTH) { + ReportAllocationOverflow(cx); + return false; + } + + JSStringBuilder sb(cx); + if (!sb.reserve(outLength.value())) { + return false; + } + + // Steps 9-10. + const auto& base64Chars = alphabet == Alphabet::Base64 + ? Base64::Encode::Base64 + : Base64::Encode::Base64Url; + + auto encode = [&base64Chars](uint32_t value) { + return base64Chars[value & 0x3f]; + }; + + // Our implementation directly converts the bytes to their string + // representation instead of first collecting them into an intermediate list. + auto data = tarray->dataPointerEither().cast<uint8_t*>(); + auto toRead = *length; + for (; toRead >= 3; toRead -= 3) { + // Combine three input bytes into a single uint24 value. + auto byte0 = jit::AtomicOperations::loadSafeWhenRacy(data++); + auto byte1 = jit::AtomicOperations::loadSafeWhenRacy(data++); + auto byte2 = jit::AtomicOperations::loadSafeWhenRacy(data++); + auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8) | byte2; + + // Encode the uint24 value as base64. + sb.infallibleAppend(encode(u24 >> 18)); + sb.infallibleAppend(encode(u24 >> 12)); + sb.infallibleAppend(encode(u24 >> 6)); + sb.infallibleAppend(encode(u24 >> 0)); + } + + // Trailing two and one element bytes are padded with '='. + if (toRead == 2) { + // Combine two input bytes into a single uint24 value. + auto byte0 = jit::AtomicOperations::loadSafeWhenRacy(data++); + auto byte1 = jit::AtomicOperations::loadSafeWhenRacy(data++); + auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8); + + // Encode the uint24 value as base64, including padding. + sb.infallibleAppend(encode(u24 >> 18)); + sb.infallibleAppend(encode(u24 >> 12)); + sb.infallibleAppend(encode(u24 >> 6)); + sb.infallibleAppend('='); + } else if (toRead == 1) { + // Combine one input byte into a single uint24 value. + auto byte0 = jit::AtomicOperations::loadSafeWhenRacy(data++); + auto u24 = uint32_t(byte0) << 16; + + // Encode the uint24 value as base64, including padding. + sb.infallibleAppend(encode(u24 >> 18)); + sb.infallibleAppend(encode(u24 >> 12)); + sb.infallibleAppend('='); + sb.infallibleAppend('='); + } else { + MOZ_ASSERT(toRead == 0); + } + + MOZ_ASSERT(sb.length() == outLength.value(), "all characters were written"); + + // Step 11. + auto* str = sb.finishString(); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +/** + * Uint8Array.prototype.toBase64 ( [ options ] ) + * + * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64 + */ +static bool uint8array_toBase64(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-2. + return CallNonGenericMethod<IsUint8ArrayObject, uint8array_toBase64>(cx, + args); +} + +/** + * Uint8Array.prototype.toHex ( ) + * + * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tohex + */ +static bool uint8array_toHex(JSContext* cx, const CallArgs& args) { + Rooted<TypedArrayObject*> tarray( + cx, &args.thisv().toObject().as<TypedArrayObject>()); + + // Step 3. (Partial) + auto length = tarray->length(); + if (!length) { + ReportOutOfBounds(cx, tarray); + return false; + } + + // |length| is limited by |ByteLengthLimit|, which ensures that multiplying it + // by two won't overflow. + static_assert(TypedArrayObject::ByteLengthLimit <= + std::numeric_limits<size_t>::max() / 2); + MOZ_ASSERT(*length <= TypedArrayObject::ByteLengthLimit); + + // Compute the output string length. Each byte is encoded as two characters, + // so the output length is exactly twice as large as |length|. + size_t outLength = *length * 2; + if (outLength > JSString::MAX_LENGTH) { + ReportAllocationOverflow(cx); + return false; + } + + // Step 4. + JSStringBuilder sb(cx); + if (!sb.reserve(outLength)) { + return false; + } + + // NB: Lower case hex digits. + static constexpr char HexDigits[] = "0123456789abcdef"; + static_assert(std::char_traits<char>::length(HexDigits) == 16); + + // Steps 3 and 5. + // + // Our implementation directly converts the bytes to their string + // representation instead of first collecting them into an intermediate list. + auto data = tarray->dataPointerEither().cast<uint8_t*>(); + for (size_t index = 0; index < *length; index++) { + auto byte = jit::AtomicOperations::loadSafeWhenRacy(data + index); + + sb.infallibleAppend(HexDigits[byte >> 4]); + sb.infallibleAppend(HexDigits[byte & 0xf]); + } + + MOZ_ASSERT(sb.length() == outLength, "all characters were written"); + + // Step 6. + auto* str = sb.finishString(); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +/** + * Uint8Array.prototype.toHex ( ) + * + * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tohex + */ +static bool uint8array_toHex(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-2. + return CallNonGenericMethod<IsUint8ArrayObject, uint8array_toHex>(cx, args); +} + +#endif + /* static */ const JSFunctionSpec TypedArrayObject::protoFunctions[] = { JS_SELF_HOSTED_FN("subarray", "TypedArraySubarray", 2, 0), JS_FN("set", TypedArrayObject::set, 1, 0), @@ -2364,15 +3450,50 @@ static const JSPropertySpec #undef IMPL_TYPED_ARRAY_PROPERTIES }; +#ifdef NIGHTLY_BUILD +static const JSFunctionSpec uint8array_static_methods[] = { + JS_FN("fromBase64", uint8array_fromBase64, 1, 0), + JS_FN("fromHex", uint8array_fromHex, 1, 0), + JS_FS_END, +}; + +static const JSFunctionSpec uint8array_methods[] = { + JS_FN("setFromBase64", uint8array_setFromBase64, 1, 0), + JS_FN("setFromHex", uint8array_setFromHex, 1, 0), + JS_FN("toBase64", uint8array_toBase64, 0, 0), + JS_FN("toHex", uint8array_toHex, 0, 0), + JS_FS_END, +}; +#endif + +static constexpr const JSFunctionSpec* TypedArrayStaticMethods( + Scalar::Type type) { +#ifdef NIGHTLY_BUILD + if (type == Scalar::Uint8) { + return uint8array_static_methods; + } +#endif + return nullptr; +} + +static constexpr const JSFunctionSpec* TypedArrayMethods(Scalar::Type type) { +#ifdef NIGHTLY_BUILD + if (type == Scalar::Uint8) { + return uint8array_methods; + } +#endif + return nullptr; +} + static const ClassSpec TypedArrayObjectClassSpecs[Scalar::MaxTypedArrayViewType] = { #define IMPL_TYPED_ARRAY_CLASS_SPEC(ExternalType, NativeType, Name) \ { \ TypedArrayObjectTemplate<NativeType>::createConstructor, \ TypedArrayObjectTemplate<NativeType>::createPrototype, \ - nullptr, \ + TypedArrayStaticMethods(Scalar::Type::Name), \ static_prototype_properties[Scalar::Type::Name], \ - nullptr, \ + TypedArrayMethods(Scalar::Type::Name), \ static_prototype_properties[Scalar::Type::Name], \ nullptr, \ JSProto_TypedArray, \ diff --git a/js/src/vm/TypedArrayObject.h b/js/src/vm/TypedArrayObject.h index 46531ec4ee..6905e83600 100644 --- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -149,6 +149,7 @@ class FixedLengthTypedArrayObject : public TypedArrayObject { static constexpr uint32_t INLINE_BUFFER_LIMIT = (NativeObject::MAX_FIXED_SLOTS - FIXED_DATA_START) * sizeof(Value); + inline gc::AllocKind allocKindForTenure() const; static inline gc::AllocKind AllocKindForLazyBuffer(size_t nbytes); size_t byteOffset() const { |