summaryrefslogtreecommitdiffstats
path: root/js/src/vm
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:34:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:34:42 +0000
commitda4c7e7ed675c3bf405668739c3012d140856109 (patch)
treecdd868dba063fecba609a1d819de271f0d51b23e /js/src/vm
parentAdding upstream version 125.0.3. (diff)
downloadfirefox-da4c7e7ed675c3bf405668739c3012d140856109.tar.xz
firefox-da4c7e7ed675c3bf405668739c3012d140856109.zip
Adding upstream version 126.0.upstream/126.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/vm')
-rw-r--r--js/src/vm/ArrayBufferObject.cpp50
-rw-r--r--js/src/vm/ArrayBufferObject.h19
-rw-r--r--js/src/vm/ArrayBufferViewObject.cpp1
-rw-r--r--js/src/vm/AsyncFunction.cpp2
-rw-r--r--js/src/vm/AsyncIteration.cpp2
-rw-r--r--js/src/vm/AtomsTable.h76
-rw-r--r--js/src/vm/BigIntType.h2
-rw-r--r--js/src/vm/CommonPropertyNames.h12
-rw-r--r--js/src/vm/FrameIter.cpp7
-rw-r--r--js/src/vm/FunctionFlags.h41
-rw-r--r--js/src/vm/GeckoProfiler.cpp8
-rw-r--r--js/src/vm/GeneratorObject.cpp14
-rw-r--r--js/src/vm/GeneratorObject.h10
-rw-r--r--js/src/vm/GlobalObject.h5
-rw-r--r--js/src/vm/Interpreter.cpp4
-rw-r--r--js/src/vm/InvalidatingFuse.cpp20
-rw-r--r--js/src/vm/InvalidatingFuse.h24
-rw-r--r--js/src/vm/Iteration.cpp118
-rw-r--r--js/src/vm/Iteration.h2
-rw-r--r--js/src/vm/JSAtomUtils.cpp159
-rw-r--r--js/src/vm/JSContext.cpp19
-rw-r--r--js/src/vm/JSContext.h14
-rw-r--r--js/src/vm/JSFunction.h22
-rw-r--r--js/src/vm/JSONParser.cpp198
-rw-r--r--js/src/vm/JSONParser.h153
-rw-r--r--js/src/vm/JSONPrinter.cpp4
-rw-r--r--js/src/vm/JSONPrinter.h2
-rw-r--r--js/src/vm/JSObject-inl.h5
-rw-r--r--js/src/vm/JSObject.cpp44
-rw-r--r--js/src/vm/Modules.cpp223
-rw-r--r--js/src/vm/Modules.h34
-rw-r--r--js/src/vm/NativeObject.cpp26
-rw-r--r--js/src/vm/NativeObject.h4
-rw-r--r--js/src/vm/PortableBaselineInterpret.cpp6
-rw-r--r--js/src/vm/RealmFuses.cpp21
-rw-r--r--js/src/vm/RealmFuses.h17
-rw-r--r--js/src/vm/Runtime.cpp2
-rw-r--r--js/src/vm/SelfHosting.cpp5
-rw-r--r--js/src/vm/Stack.cpp6
-rw-r--r--js/src/vm/StringType-inl.h28
-rw-r--r--js/src/vm/StringType.cpp60
-rw-r--r--js/src/vm/StringType.h40
-rw-r--r--js/src/vm/StructuredClone.cpp2
-rw-r--r--js/src/vm/TypedArrayObject-inl.h20
-rw-r--r--js/src/vm/TypedArrayObject.cpp1135
-rw-r--r--js/src/vm/TypedArrayObject.h1
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, &REGS.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 {