mirror of
https://github.com/roytam1/UXP.git
synced 2026-05-26 05:38:39 +00:00
Merge remote-tracking branch 'origin/tracking' into custom
This commit is contained in:
@@ -301,7 +301,6 @@ LoadContextOptions(const char* aPrefName, void* /* aClosure */)
|
||||
.setAsyncStack(GetWorkerPref<bool>(NS_LITERAL_CSTRING("asyncstack")))
|
||||
.setWerror(GetWorkerPref<bool>(NS_LITERAL_CSTRING("werror")))
|
||||
.setStreams(GetWorkerPref<bool>(NS_LITERAL_CSTRING("streams")))
|
||||
.setWeakRefs(GetWorkerPref<bool>(NS_LITERAL_CSTRING("weakrefs")))
|
||||
.setExtraWarnings(GetWorkerPref<bool>(NS_LITERAL_CSTRING("strict")))
|
||||
.setArrayProtoValues(GetWorkerPref<bool>(
|
||||
NS_LITERAL_CSTRING("array_prototype_values")));
|
||||
|
||||
@@ -35,7 +35,6 @@ WORKER_SIMPLE_PREF("dom.serviceWorkers.openWindow.enabled", OpenWindowEnabled, O
|
||||
WORKER_SIMPLE_PREF("dom.storageManager.enabled", StorageManagerEnabled, STORAGEMANAGER_ENABLED)
|
||||
WORKER_SIMPLE_PREF("dom.push.enabled", PushEnabled, PUSH_ENABLED)
|
||||
WORKER_SIMPLE_PREF("dom.streams.enabled", StreamsEnabled, STREAMS_ENABLED)
|
||||
WORKER_SIMPLE_PREF("javascript.options.weakrefs", WeakRefsEnabled, WEAKREFS_ENABLED)
|
||||
WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTCONTEXT_ENABLED)
|
||||
WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED)
|
||||
WORKER_SIMPLE_PREF("dom.webkitBlink.dirPicker.enabled", WebkitBlinkDirectoryPickerEnabled, DOM_WEBKITBLINK_DIRPICKER_WEBKITBLINK)
|
||||
|
||||
@@ -446,7 +446,7 @@ for (k = 0; k < 2; k++) {
|
||||
while ((c = fgetc(f)) != '\n' && c != EOF);
|
||||
/* issue warning if not a comment */
|
||||
if (buf[0] != '%') {
|
||||
fprintf(stderr, "Warning: skipping too long pattern (more than %lu chars)\n", sizeof(buf));
|
||||
fprintf(stderr, "Warning: skipping too long pattern (more than %zu chars)\n", sizeof(buf));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -47,10 +47,12 @@
|
||||
#include "builtin/AtomicsObject.h"
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Casting.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include "builtin/Promise.h"
|
||||
#include "jsapi.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "jsnum.h"
|
||||
@@ -58,7 +60,9 @@
|
||||
#include "jit/AtomicOperations.h"
|
||||
#include "jit/InlinableNatives.h"
|
||||
#include "js/Class.h"
|
||||
#include "vm/BigIntType.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/HelperThreads.h"
|
||||
#include "vm/Time.h"
|
||||
#include "vm/TypedArrayObject.h"
|
||||
#include "wasm/WasmInstance.h"
|
||||
@@ -121,6 +125,68 @@ GetTypedArrayIndex(JSContext* cx, HandleValue v, Handle<TypedArrayObject*> view,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
IsWaitableTypedArray(Scalar::Type viewType)
|
||||
{
|
||||
return viewType == Scalar::Int32 || viewType == Scalar::BigInt64;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
GetWaiterByteOffset(Handle<TypedArrayObject*> view, uint32_t offset)
|
||||
{
|
||||
return view->byteOffset() + offset * TypedArrayElemSize(view->type());
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
BigIntToRawBits(Scalar::Type viewType, BigInt* value)
|
||||
{
|
||||
if (viewType == Scalar::BigInt64)
|
||||
return uint64_t(BigInt::toInt64(value));
|
||||
MOZ_ASSERT(viewType == Scalar::BigUint64);
|
||||
return BigInt::toUint64(value);
|
||||
}
|
||||
|
||||
static bool
|
||||
BigIntToRawBits(JSContext* cx, Scalar::Type viewType, HandleValue value, uint64_t* bits)
|
||||
{
|
||||
MOZ_ASSERT(Scalar::isBigIntType(viewType));
|
||||
|
||||
RootedBigInt bigint(cx, ToBigInt(cx, value));
|
||||
if (!bigint)
|
||||
return false;
|
||||
|
||||
*bits = BigIntToRawBits(viewType, bigint);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
SetBigIntResult(JSContext* cx, Scalar::Type viewType, uint64_t bits, MutableHandleValue result)
|
||||
{
|
||||
MOZ_ASSERT(Scalar::isBigIntType(viewType));
|
||||
|
||||
BigInt* bigint = viewType == Scalar::BigInt64
|
||||
? BigInt::createFromInt64(cx, mozilla::BitwiseCast<int64_t>(bits))
|
||||
: BigInt::createFromUint64(cx, bits);
|
||||
if (!bigint)
|
||||
return false;
|
||||
|
||||
result.setBigInt(bigint);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
WaitValueMatches(Handle<TypedArrayObject*> view, uint32_t offset, uint64_t expected)
|
||||
{
|
||||
SharedMem<void*> viewData = view->viewDataShared();
|
||||
if (view->type() == Scalar::BigInt64) {
|
||||
return jit::AtomicOperations::loadSafeWhenRacy(viewData.cast<uint64_t*>() + offset) ==
|
||||
expected;
|
||||
}
|
||||
|
||||
return uint32_t(jit::AtomicOperations::loadSafeWhenRacy(viewData.cast<int32_t*>() + offset)) ==
|
||||
uint32_t(expected);
|
||||
}
|
||||
|
||||
static int32_t
|
||||
CompareExchange(Scalar::Type viewType, int32_t oldCandidate, int32_t newCandidate,
|
||||
SharedMem<void*> viewData, uint32_t offset, bool* badArrayType = nullptr)
|
||||
@@ -191,6 +257,20 @@ js::atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp)
|
||||
uint32_t offset;
|
||||
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
|
||||
return false;
|
||||
|
||||
if (Scalar::isBigIntType(view->type())) {
|
||||
uint64_t oldCandidate;
|
||||
if (!BigIntToRawBits(cx, view->type(), oldv, &oldCandidate))
|
||||
return false;
|
||||
uint64_t newCandidate;
|
||||
if (!BigIntToRawBits(cx, view->type(), newv, &newCandidate))
|
||||
return false;
|
||||
|
||||
uint64_t result = jit::AtomicOperations::compareExchangeSeqCst(
|
||||
view->viewDataShared().cast<uint64_t*>() + offset, oldCandidate, newCandidate);
|
||||
return SetBigIntResult(cx, view->type(), result, r);
|
||||
}
|
||||
|
||||
int32_t oldCandidate;
|
||||
if (!ToInt32(cx, oldv, &oldCandidate))
|
||||
return false;
|
||||
@@ -259,6 +339,22 @@ js::atomics_load(JSContext* cx, unsigned argc, Value* vp)
|
||||
r.setNumber(v);
|
||||
return true;
|
||||
}
|
||||
case Scalar::BigInt64: {
|
||||
int64_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<int64_t*>() + offset);
|
||||
BigInt* bigint = BigInt::createFromInt64(cx, v);
|
||||
if (!bigint)
|
||||
return false;
|
||||
r.setBigInt(bigint);
|
||||
return true;
|
||||
}
|
||||
case Scalar::BigUint64: {
|
||||
uint64_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint64_t*>() + offset);
|
||||
BigInt* bigint = BigInt::createFromUint64(cx, v);
|
||||
if (!bigint)
|
||||
return false;
|
||||
r.setBigInt(bigint);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return ReportBadArrayType(cx);
|
||||
}
|
||||
@@ -337,6 +433,25 @@ ExchangeOrStore(JSContext* cx, unsigned argc, Value* vp)
|
||||
uint32_t offset;
|
||||
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
|
||||
return false;
|
||||
|
||||
if (Scalar::isBigIntType(view->type())) {
|
||||
RootedBigInt bigint(cx, ToBigInt(cx, valv));
|
||||
if (!bigint)
|
||||
return false;
|
||||
|
||||
uint64_t value = BigIntToRawBits(view->type(), bigint);
|
||||
if (op == DoStore) {
|
||||
jit::AtomicOperations::storeSeqCst(view->viewDataShared().cast<uint64_t*>() + offset,
|
||||
value);
|
||||
r.setBigInt(bigint);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t result = jit::AtomicOperations::exchangeSeqCst(
|
||||
view->viewDataShared().cast<uint64_t*>() + offset, value);
|
||||
return SetBigIntResult(cx, view->type(), result, r);
|
||||
}
|
||||
|
||||
double integerValue;
|
||||
if (!ToInteger(cx, valv, &integerValue))
|
||||
return false;
|
||||
@@ -380,6 +495,24 @@ AtomicsBinop(JSContext* cx, HandleValue objv, HandleValue idxv, HandleValue valv
|
||||
uint32_t offset;
|
||||
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
|
||||
return false;
|
||||
|
||||
if (Scalar::isBigIntType(view->type())) {
|
||||
uint64_t value;
|
||||
if (!BigIntToRawBits(cx, view->type(), valv, &value))
|
||||
return false;
|
||||
|
||||
SharedMem<uint64_t*> addr = view->viewDataShared().cast<uint64_t*>() + offset;
|
||||
uint64_t old = jit::AtomicOperations::loadSeqCst(addr);
|
||||
for (;;) {
|
||||
uint64_t replacement = T::operate64(old, value);
|
||||
uint64_t observed = jit::AtomicOperations::compareExchangeSeqCst(addr, old,
|
||||
replacement);
|
||||
if (observed == old)
|
||||
return SetBigIntResult(cx, view->type(), old, r);
|
||||
old = observed;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t numberValue;
|
||||
if (!ToInt32(cx, valv, &numberValue))
|
||||
return false;
|
||||
@@ -434,6 +567,7 @@ class PerformAdd
|
||||
public:
|
||||
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAddSeqCst)
|
||||
static int32_t perform(int32_t x, int32_t y) { return x + y; }
|
||||
static uint64_t operate64(uint64_t x, uint64_t y) { return x + y; }
|
||||
};
|
||||
|
||||
bool
|
||||
@@ -448,6 +582,7 @@ class PerformSub
|
||||
public:
|
||||
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchSubSeqCst)
|
||||
static int32_t perform(int32_t x, int32_t y) { return x - y; }
|
||||
static uint64_t operate64(uint64_t x, uint64_t y) { return x - y; }
|
||||
};
|
||||
|
||||
bool
|
||||
@@ -462,6 +597,7 @@ class PerformAnd
|
||||
public:
|
||||
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAndSeqCst)
|
||||
static int32_t perform(int32_t x, int32_t y) { return x & y; }
|
||||
static uint64_t operate64(uint64_t x, uint64_t y) { return x & y; }
|
||||
};
|
||||
|
||||
bool
|
||||
@@ -476,6 +612,7 @@ class PerformOr
|
||||
public:
|
||||
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchOrSeqCst)
|
||||
static int32_t perform(int32_t x, int32_t y) { return x | y; }
|
||||
static uint64_t operate64(uint64_t x, uint64_t y) { return x | y; }
|
||||
};
|
||||
|
||||
bool
|
||||
@@ -490,6 +627,7 @@ class PerformXor
|
||||
public:
|
||||
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchXorSeqCst)
|
||||
static int32_t perform(int32_t x, int32_t y) { return x ^ y; }
|
||||
static uint64_t operate64(uint64_t x, uint64_t y) { return x ^ y; }
|
||||
};
|
||||
|
||||
bool
|
||||
@@ -693,11 +831,13 @@ js::atomics_cmpxchg_asm_callout(wasm::Instance* instance, int32_t vt, int32_t of
|
||||
|
||||
namespace js {
|
||||
|
||||
class AtomicsWaitAsyncTask;
|
||||
|
||||
// Represents one waiting worker.
|
||||
//
|
||||
// The type is declared opaque in SharedArrayObject.h. Instances of
|
||||
// js::FutexWaiter are stack-allocated and linked onto a list across a
|
||||
// call to FutexRuntime::wait().
|
||||
// js::FutexWaiter are linked onto a list across a call to FutexRuntime::wait()
|
||||
// or an async wait task.
|
||||
//
|
||||
// The 'waiters' field of the SharedArrayRawBuffer points to the highest
|
||||
// priority waiter in the list, and lower priority nodes are linked through
|
||||
@@ -711,14 +851,34 @@ class FutexWaiter
|
||||
public:
|
||||
FutexWaiter(uint32_t offset, JSRuntime* rt)
|
||||
: offset(offset),
|
||||
kind(Sync),
|
||||
rt(rt),
|
||||
asyncTask(nullptr),
|
||||
lower_pri(nullptr),
|
||||
back(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t offset; // int32 element index within the SharedArrayBuffer
|
||||
FutexWaiter(uint32_t offset, AtomicsWaitAsyncTask* asyncTask)
|
||||
: offset(offset),
|
||||
kind(Async),
|
||||
rt(nullptr),
|
||||
asyncTask(asyncTask),
|
||||
lower_pri(nullptr),
|
||||
back(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
bool isWaiting() const;
|
||||
void notify();
|
||||
|
||||
uint32_t offset; // byte offset within the SharedArrayBuffer
|
||||
enum WaiterKind {
|
||||
Sync,
|
||||
Async
|
||||
} kind;
|
||||
JSRuntime* rt; // The runtime of the waiter
|
||||
AtomicsWaitAsyncTask* asyncTask; // The async waiter task, if any
|
||||
FutexWaiter* lower_pri; // Lower priority nodes in circular doubly-linked list of waiters
|
||||
FutexWaiter* back; // Other direction
|
||||
};
|
||||
@@ -742,8 +902,195 @@ class AutoLockFutexAPI
|
||||
js::UniqueLock<js::Mutex>& unique() { return *unique_; }
|
||||
};
|
||||
|
||||
static void
|
||||
AddWaiter(SharedArrayRawBuffer* sarb, FutexWaiter* waiter)
|
||||
{
|
||||
if (FutexWaiter* waiters = sarb->waiters()) {
|
||||
waiter->lower_pri = waiters;
|
||||
waiter->back = waiters->back;
|
||||
waiters->back->lower_pri = waiter;
|
||||
waiters->back = waiter;
|
||||
} else {
|
||||
waiter->lower_pri = waiter->back = waiter;
|
||||
sarb->setWaiters(waiter);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
RemoveWaiter(SharedArrayRawBuffer* sarb, FutexWaiter* waiter)
|
||||
{
|
||||
if (waiter->lower_pri == waiter) {
|
||||
sarb->setWaiters(nullptr);
|
||||
} else {
|
||||
waiter->lower_pri->back = waiter->back;
|
||||
waiter->back->lower_pri = waiter->lower_pri;
|
||||
if (sarb->waiters() == waiter)
|
||||
sarb->setWaiters(waiter->lower_pri);
|
||||
}
|
||||
waiter->lower_pri = nullptr;
|
||||
waiter->back = nullptr;
|
||||
}
|
||||
|
||||
class AtomicsWaitAsyncTask : public PromiseTask
|
||||
{
|
||||
enum class State {
|
||||
Waiting,
|
||||
Notified,
|
||||
TimedOut
|
||||
};
|
||||
|
||||
SharedArrayRawBuffer* sarb_;
|
||||
FutexWaiter waiter_;
|
||||
mozilla::Maybe<mozilla::TimeDuration> timeout_;
|
||||
ConditionVariable cond_;
|
||||
State state_;
|
||||
bool isInWaiterList_;
|
||||
|
||||
public:
|
||||
AtomicsWaitAsyncTask(JSContext* cx, Handle<PromiseObject*> promise,
|
||||
SharedArrayRawBuffer* sarb, uint32_t offset,
|
||||
mozilla::Maybe<mozilla::TimeDuration>& timeout)
|
||||
: PromiseTask(cx, promise),
|
||||
sarb_(sarb),
|
||||
waiter_(offset, this),
|
||||
timeout_(timeout),
|
||||
state_(State::Waiting),
|
||||
isInWaiterList_(false)
|
||||
{
|
||||
}
|
||||
|
||||
~AtomicsWaitAsyncTask() {
|
||||
if (isInWaiterList_) {
|
||||
AutoLockFutexAPI lock;
|
||||
if (isInWaiterList_)
|
||||
removeFromWaiterList();
|
||||
}
|
||||
sarb_->dropReference();
|
||||
}
|
||||
|
||||
FutexWaiter* waiter() {
|
||||
return &waiter_;
|
||||
}
|
||||
|
||||
void setInWaiterList() {
|
||||
isInWaiterList_ = true;
|
||||
}
|
||||
|
||||
bool isWaiting() const {
|
||||
return state_ == State::Waiting;
|
||||
}
|
||||
|
||||
void notify() {
|
||||
MOZ_ASSERT(isWaiting());
|
||||
state_ = State::Notified;
|
||||
cond_.notify_all();
|
||||
}
|
||||
|
||||
void execute() override {
|
||||
AutoLockFutexAPI lock;
|
||||
|
||||
const bool isTimed = timeout_.isSome();
|
||||
auto finalEnd = timeout_.map([](mozilla::TimeDuration& timeout) {
|
||||
return mozilla::TimeStamp::Now() + timeout;
|
||||
});
|
||||
auto maxSlice = mozilla::TimeDuration::FromSeconds(4000.0);
|
||||
|
||||
while (state_ == State::Waiting) {
|
||||
if (isTimed) {
|
||||
auto sliceEnd = finalEnd.map([&](mozilla::TimeStamp& finalEnd) {
|
||||
auto sliceEnd = mozilla::TimeStamp::Now() + maxSlice;
|
||||
if (finalEnd < sliceEnd)
|
||||
sliceEnd = finalEnd;
|
||||
return sliceEnd;
|
||||
});
|
||||
|
||||
mozilla::Unused << cond_.wait_until(lock.unique(), *sliceEnd);
|
||||
if (state_ == State::Waiting && mozilla::TimeStamp::Now() >= *finalEnd) {
|
||||
state_ = State::TimedOut;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
cond_.wait(lock.unique());
|
||||
}
|
||||
}
|
||||
|
||||
if (isInWaiterList_)
|
||||
removeFromWaiterList();
|
||||
}
|
||||
|
||||
private:
|
||||
void removeFromWaiterList() {
|
||||
MOZ_ASSERT(isInWaiterList_);
|
||||
RemoveWaiter(sarb_, &waiter_);
|
||||
isInWaiterList_ = false;
|
||||
}
|
||||
|
||||
bool finishPromise(JSContext* cx, Handle<PromiseObject*> promise) override {
|
||||
RootedValue result(cx);
|
||||
if (state_ == State::TimedOut)
|
||||
result.setString(cx->names().futexTimedOut);
|
||||
else
|
||||
result.setString(cx->names().futexOK);
|
||||
return PromiseObject::resolve(cx, promise, result);
|
||||
}
|
||||
};
|
||||
|
||||
bool
|
||||
FutexWaiter::isWaiting() const
|
||||
{
|
||||
if (kind == Sync)
|
||||
return rt->fx.isWaiting();
|
||||
return asyncTask->isWaiting();
|
||||
}
|
||||
|
||||
void
|
||||
FutexWaiter::notify()
|
||||
{
|
||||
if (kind == Sync) {
|
||||
rt->fx.notify(FutexRuntime::NotifyExplicit);
|
||||
return;
|
||||
}
|
||||
asyncTask->notify();
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
static bool
|
||||
GetWaitTimeout(JSContext* cx, HandleValue timeoutv,
|
||||
mozilla::Maybe<mozilla::TimeDuration>* timeout)
|
||||
{
|
||||
timeout->reset();
|
||||
if (!timeoutv.isUndefined()) {
|
||||
double timeout_ms;
|
||||
if (!ToNumber(cx, timeoutv, &timeout_ms))
|
||||
return false;
|
||||
if (!mozilla::IsNaN(timeout_ms)) {
|
||||
if (timeout_ms < 0)
|
||||
*timeout = mozilla::Some(mozilla::TimeDuration::FromSeconds(0.0));
|
||||
else if (!mozilla::IsInfinite(timeout_ms))
|
||||
*timeout = mozilla::Some(mozilla::TimeDuration::FromMilliseconds(timeout_ms));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
CreateWaitAsyncResult(JSContext* cx, bool isAsync, HandleValue value, MutableHandleValue rval)
|
||||
{
|
||||
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
RootedValue asyncValue(cx, BooleanValue(isAsync));
|
||||
if (!NativeDefineDataProperty(cx, obj, cx->names().async, asyncValue, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
if (!NativeDefineDataProperty(cx, obj, cx->names().value, value, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
|
||||
rval.setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -759,26 +1106,24 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
|
||||
Rooted<TypedArrayObject*> view(cx, nullptr);
|
||||
if (!GetSharedTypedArray(cx, objv, &view))
|
||||
return false;
|
||||
if (view->type() != Scalar::Int32)
|
||||
if (!IsWaitableTypedArray(view->type()))
|
||||
return ReportBadArrayType(cx);
|
||||
uint32_t offset;
|
||||
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
|
||||
return false;
|
||||
int32_t value;
|
||||
if (!ToInt32(cx, valv, &value))
|
||||
return false;
|
||||
mozilla::Maybe<mozilla::TimeDuration> timeout;
|
||||
if (!timeoutv.isUndefined()) {
|
||||
double timeout_ms;
|
||||
if (!ToNumber(cx, timeoutv, &timeout_ms))
|
||||
uint64_t value = 0;
|
||||
if (view->type() == Scalar::BigInt64) {
|
||||
if (!BigIntToRawBits(cx, view->type(), valv, &value))
|
||||
return false;
|
||||
if (!mozilla::IsNaN(timeout_ms)) {
|
||||
if (timeout_ms < 0)
|
||||
timeout = mozilla::Some(mozilla::TimeDuration::FromSeconds(0.0));
|
||||
else if (!mozilla::IsInfinite(timeout_ms))
|
||||
timeout = mozilla::Some(mozilla::TimeDuration::FromMilliseconds(timeout_ms));
|
||||
}
|
||||
} else {
|
||||
int32_t int32Value;
|
||||
if (!ToInt32(cx, valv, &int32Value))
|
||||
return false;
|
||||
value = uint32_t(int32Value);
|
||||
}
|
||||
mozilla::Maybe<mozilla::TimeDuration> timeout;
|
||||
if (!GetWaitTimeout(cx, timeoutv, &timeout))
|
||||
return false;
|
||||
|
||||
if (!rt->fx.canWait())
|
||||
return ReportCannotWait(cx);
|
||||
@@ -787,8 +1132,7 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
|
||||
// and it provides the necessary memory fence.
|
||||
AutoLockFutexAPI lock;
|
||||
|
||||
SharedMem<int32_t*> addr = view->viewDataShared().cast<int32_t*>() + offset;
|
||||
if (jit::AtomicOperations::loadSafeWhenRacy(addr) != value) {
|
||||
if (!WaitValueMatches(view, offset, value)) {
|
||||
r.setString(cx->names().futexNotEqual);
|
||||
return true;
|
||||
}
|
||||
@@ -796,16 +1140,8 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
|
||||
Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
|
||||
SharedArrayRawBuffer* sarb = sab->rawBufferObject();
|
||||
|
||||
FutexWaiter w(offset, rt);
|
||||
if (FutexWaiter* waiters = sarb->waiters()) {
|
||||
w.lower_pri = waiters;
|
||||
w.back = waiters->back;
|
||||
waiters->back->lower_pri = &w;
|
||||
waiters->back = &w;
|
||||
} else {
|
||||
w.lower_pri = w.back = &w;
|
||||
sarb->setWaiters(&w);
|
||||
}
|
||||
FutexWaiter w(GetWaiterByteOffset(view, offset), rt);
|
||||
AddWaiter(sarb, &w);
|
||||
|
||||
FutexRuntime::WaitResult result = FutexRuntime::FutexOK;
|
||||
bool retval = rt->fx.wait(cx, lock.unique(), timeout, &result);
|
||||
@@ -820,17 +1156,98 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
|
||||
}
|
||||
}
|
||||
|
||||
if (w.lower_pri == &w) {
|
||||
sarb->setWaiters(nullptr);
|
||||
} else {
|
||||
w.lower_pri->back = w.back;
|
||||
w.back->lower_pri = w.lower_pri;
|
||||
if (sarb->waiters() == &w)
|
||||
sarb->setWaiters(w.lower_pri);
|
||||
}
|
||||
RemoveWaiter(sarb, &w);
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool
|
||||
js::atomics_waitAsync(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
HandleValue objv = args.get(0);
|
||||
HandleValue idxv = args.get(1);
|
||||
HandleValue valv = args.get(2);
|
||||
HandleValue timeoutv = args.get(3);
|
||||
MutableHandleValue r = args.rval();
|
||||
|
||||
Rooted<TypedArrayObject*> view(cx, nullptr);
|
||||
if (!GetSharedTypedArray(cx, objv, &view))
|
||||
return false;
|
||||
if (!IsWaitableTypedArray(view->type()))
|
||||
return ReportBadArrayType(cx);
|
||||
uint32_t offset;
|
||||
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
|
||||
return false;
|
||||
uint64_t value = 0;
|
||||
if (view->type() == Scalar::BigInt64) {
|
||||
if (!BigIntToRawBits(cx, view->type(), valv, &value))
|
||||
return false;
|
||||
} else {
|
||||
int32_t int32Value;
|
||||
if (!ToInt32(cx, valv, &int32Value))
|
||||
return false;
|
||||
value = uint32_t(int32Value);
|
||||
}
|
||||
mozilla::Maybe<mozilla::TimeDuration> timeout;
|
||||
if (!GetWaitTimeout(cx, timeoutv, &timeout))
|
||||
return false;
|
||||
|
||||
Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
|
||||
if (!promise)
|
||||
return false;
|
||||
|
||||
RootedValue promiseValue(cx, ObjectValue(*promise));
|
||||
RootedValue result(cx);
|
||||
if (!CreateWaitAsyncResult(cx, true, promiseValue, &result))
|
||||
return false;
|
||||
|
||||
Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
|
||||
SharedArrayRawBuffer* sarb = sab->rawBufferObject();
|
||||
if (!sarb->addReference()) {
|
||||
JS_ReportErrorASCII(cx, "Reference count overflow on SharedArrayBuffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto task = cx->make_unique<AtomicsWaitAsyncTask>(cx, promise, sarb, offset, timeout);
|
||||
if (!task) {
|
||||
sarb->dropReference();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isAsync = false;
|
||||
RootedValue immediateResult(cx);
|
||||
{
|
||||
AutoLockFutexAPI lock;
|
||||
|
||||
if (!WaitValueMatches(view, offset, value)) {
|
||||
immediateResult.setString(cx->names().futexNotEqual);
|
||||
} else if (timeout.isSome() && timeout->ToMilliseconds() == 0.0) {
|
||||
immediateResult.setString(cx->names().futexTimedOut);
|
||||
} else {
|
||||
if (!cx->startAsyncTaskCallback || !cx->finishAsyncTaskCallback ||
|
||||
!CanUseExtraThreads())
|
||||
{
|
||||
JS_ReportErrorASCII(cx, "Atomics.waitAsync not supported in this runtime.");
|
||||
return false;
|
||||
}
|
||||
|
||||
task->waiter()->offset = GetWaiterByteOffset(view, offset);
|
||||
AddWaiter(sarb, task->waiter());
|
||||
task->setInWaiterList();
|
||||
isAsync = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAsync)
|
||||
return CreateWaitAsyncResult(cx, false, immediateResult, r);
|
||||
|
||||
if (!StartPromiseTask(cx, Move(task)))
|
||||
return false;
|
||||
|
||||
r.set(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
js::atomics_notify(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -843,11 +1260,12 @@ js::atomics_notify(JSContext* cx, unsigned argc, Value* vp)
|
||||
Rooted<TypedArrayObject*> view(cx, nullptr);
|
||||
if (!GetSharedTypedArray(cx, objv, &view))
|
||||
return false;
|
||||
if (view->type() != Scalar::Int32)
|
||||
if (!IsWaitableTypedArray(view->type()))
|
||||
return ReportBadArrayType(cx);
|
||||
uint32_t offset;
|
||||
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
|
||||
return false;
|
||||
offset = GetWaiterByteOffset(view, offset);
|
||||
double count;
|
||||
if (countv.isUndefined()) {
|
||||
count = mozilla::PositiveInfinity<double>();
|
||||
@@ -870,9 +1288,9 @@ js::atomics_notify(JSContext* cx, unsigned argc, Value* vp)
|
||||
do {
|
||||
FutexWaiter* c = iter;
|
||||
iter = iter->lower_pri;
|
||||
if (c->offset != offset || !c->rt->fx.isWaiting())
|
||||
if (c->offset != offset || !c->isWaiting())
|
||||
continue;
|
||||
c->rt->fx.notify(FutexRuntime::NotifyExplicit);
|
||||
c->notify();
|
||||
++woken;
|
||||
--count;
|
||||
} while (count > 0 && iter != waiters);
|
||||
@@ -1106,6 +1524,7 @@ const JSFunctionSpec AtomicsMethods[] = {
|
||||
JS_INLINABLE_FN("xor", atomics_xor, 3,0, AtomicsXor),
|
||||
JS_INLINABLE_FN("isLockFree", atomics_isLockFree, 1,0, AtomicsIsLockFree),
|
||||
JS_FN("wait", atomics_wait, 4,0),
|
||||
JS_FN("waitAsync", atomics_waitAsync, 4,0),
|
||||
JS_FN("notify", atomics_notify, 3,0),
|
||||
JS_FN("wake", atomics_notify, 3,0), //Legacy name
|
||||
JS_FS_END
|
||||
|
||||
@@ -35,6 +35,7 @@ MOZ_MUST_USE bool atomics_or(JSContext* cx, unsigned argc, Value* vp);
|
||||
MOZ_MUST_USE bool atomics_xor(JSContext* cx, unsigned argc, Value* vp);
|
||||
MOZ_MUST_USE bool atomics_isLockFree(JSContext* cx, unsigned argc, Value* vp);
|
||||
MOZ_MUST_USE bool atomics_wait(JSContext* cx, unsigned argc, Value* vp);
|
||||
MOZ_MUST_USE bool atomics_waitAsync(JSContext* cx, unsigned argc, Value* vp);
|
||||
MOZ_MUST_USE bool atomics_notify(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
/* asm.js callouts */
|
||||
|
||||
@@ -0,0 +1,608 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "builtin/FinalizationRegistryObject.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "jscntxt.h"
|
||||
|
||||
#include "builtin/WeakRefObject.h"
|
||||
#include "gc/Nursery.h"
|
||||
#include "gc/Tracer.h"
|
||||
#include "vm/EqualityOperations.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/Interpreter.h"
|
||||
#include "vm/Symbol.h"
|
||||
|
||||
#include "jswrapper.h"
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
#include "vm/Interpreter-inl.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
enum class WeakCellKind {
|
||||
Empty,
|
||||
Object,
|
||||
Symbol
|
||||
};
|
||||
|
||||
static JSObject*
|
||||
NormalizeWeakObject(JSObject* obj)
|
||||
{
|
||||
if (JSObject* unwrapped = CheckedUnwrap(obj, /* stopAtWindowProxy = */ false))
|
||||
return unwrapped;
|
||||
return obj;
|
||||
}
|
||||
|
||||
static bool
|
||||
GetPrototypeFromFinalizationRegistryConstructor(JSContext* cx, HandleObject newTarget,
|
||||
MutableHandleObject proto)
|
||||
{
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, proto))
|
||||
return false;
|
||||
if (proto)
|
||||
return true;
|
||||
|
||||
RootedObject realmObject(cx, CheckedUnwrap(newTarget, /* stopAtWindowProxy = */ false));
|
||||
if (!realmObject)
|
||||
return false;
|
||||
|
||||
{
|
||||
JSAutoCompartment ac(cx, realmObject);
|
||||
Rooted<GlobalObject*> global(cx, &realmObject->global());
|
||||
if (!GlobalObject::ensureConstructor(cx, global, JSProto_FinalizationRegistry))
|
||||
return false;
|
||||
proto.set(&global->getPrototype(JSProto_FinalizationRegistry).toObject());
|
||||
}
|
||||
|
||||
return cx->compartment()->wrap(cx, proto);
|
||||
}
|
||||
|
||||
struct WeakCell {
|
||||
WeakCellKind kind;
|
||||
WeakRef<JSObject*> object;
|
||||
WeakRef<JS::Symbol*> symbol;
|
||||
|
||||
WeakCell() : kind(WeakCellKind::Empty), object(nullptr), symbol(nullptr) {}
|
||||
explicit WeakCell(JSObject* obj) : kind(WeakCellKind::Object), object(obj), symbol(nullptr) {}
|
||||
explicit WeakCell(JS::Symbol* sym) : kind(WeakCellKind::Symbol), object(nullptr), symbol(sym) {}
|
||||
|
||||
void clear() {
|
||||
kind = WeakCellKind::Empty;
|
||||
object = nullptr;
|
||||
symbol = nullptr;
|
||||
}
|
||||
|
||||
bool isEmpty() const { return kind == WeakCellKind::Empty; }
|
||||
|
||||
bool isDead() const {
|
||||
if (kind == WeakCellKind::Object)
|
||||
return !object.unbarrieredGet();
|
||||
if (kind == WeakCellKind::Symbol)
|
||||
return !symbol.unbarrieredGet();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sameValue(HandleValue value) const {
|
||||
if (kind == WeakCellKind::Object)
|
||||
return value.isObject() && object.get() == NormalizeWeakObject(&value.toObject());
|
||||
if (kind == WeakCellKind::Symbol)
|
||||
return value.isSymbol() && symbol.get() == value.toSymbol();
|
||||
return false;
|
||||
}
|
||||
|
||||
void trace(JSTracer* trc, const char* name) {
|
||||
if (kind == WeakCellKind::Object) {
|
||||
JSObject* target = object.unbarrieredGet();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
if (IsInsideNursery(target)) {
|
||||
TraceManuallyBarrieredEdge(trc, object.unsafeGet(), name);
|
||||
} else {
|
||||
if (trc->isMarkingTracer() && !target->asTenured().zone()->isCollecting())
|
||||
return;
|
||||
TraceWeakEdge(trc, &object, name);
|
||||
}
|
||||
} else if (kind == WeakCellKind::Symbol) {
|
||||
JS::Symbol* target = symbol.unbarrieredGet();
|
||||
if (target) {
|
||||
if (trc->isMarkingTracer() && !target->asTenured().zone()->isCollecting())
|
||||
return;
|
||||
TraceWeakEdge(trc, &symbol, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void traceIfZoneIsCollecting(JSTracer* trc, const char* name) {
|
||||
if (kind == WeakCellKind::Object) {
|
||||
JSObject* target = object.unbarrieredGet();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
if (IsInsideNursery(target)) {
|
||||
TraceManuallyBarrieredEdge(trc, object.unsafeGet(), name);
|
||||
} else if (target->asTenured().zone()->isCollecting()) {
|
||||
TraceWeakEdge(trc, &object, name);
|
||||
}
|
||||
} else if (kind == WeakCellKind::Symbol) {
|
||||
JS::Symbol* target = symbol.unbarrieredGet();
|
||||
if (target && target->asTenured().zone()->isCollecting())
|
||||
TraceWeakEdge(trc, &symbol, name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct FinalizationRecord {
|
||||
WeakCell target;
|
||||
JS::Heap<Value> heldValue;
|
||||
WeakCell unregisterToken;
|
||||
bool active;
|
||||
bool queued;
|
||||
|
||||
FinalizationRecord(HandleValue targetValue, HandleValue heldValue,
|
||||
HandleValue unregisterTokenValue)
|
||||
: heldValue(heldValue),
|
||||
active(true),
|
||||
queued(false)
|
||||
{
|
||||
if (targetValue.isObject())
|
||||
target = WeakCell(NormalizeWeakObject(&targetValue.toObject()));
|
||||
else
|
||||
target = WeakCell(targetValue.toSymbol());
|
||||
|
||||
if (unregisterTokenValue.isObject())
|
||||
unregisterToken = WeakCell(NormalizeWeakObject(&unregisterTokenValue.toObject()));
|
||||
else if (unregisterTokenValue.isSymbol())
|
||||
unregisterToken = WeakCell(unregisterTokenValue.toSymbol());
|
||||
}
|
||||
|
||||
void trace(JSTracer* trc) {
|
||||
if (active || queued)
|
||||
JS::TraceEdge(trc, &heldValue, "FinalizationRegistry held value");
|
||||
|
||||
if (active) {
|
||||
target.trace(trc, "FinalizationRegistry target");
|
||||
if (!unregisterToken.isEmpty())
|
||||
unregisterToken.trace(trc, "FinalizationRegistry unregister token");
|
||||
}
|
||||
}
|
||||
|
||||
void traceWeakEdgesForCollectedZones(JSTracer* trc) {
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
target.traceIfZoneIsCollecting(trc, "FinalizationRegistry target");
|
||||
if (!unregisterToken.isEmpty())
|
||||
unregisterToken.traceIfZoneIsCollecting(trc,
|
||||
"FinalizationRegistry unregister token");
|
||||
}
|
||||
|
||||
bool targetIsDead() const {
|
||||
return active && target.isDead();
|
||||
}
|
||||
|
||||
bool matchesToken(HandleValue token) const {
|
||||
return active && !queued && !unregisterToken.isEmpty() &&
|
||||
unregisterToken.sameValue(token);
|
||||
}
|
||||
|
||||
void queueForCleanup() {
|
||||
MOZ_ASSERT(active);
|
||||
MOZ_ASSERT(!queued);
|
||||
active = false;
|
||||
queued = true;
|
||||
target.clear();
|
||||
unregisterToken.clear();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
active = false;
|
||||
queued = false;
|
||||
target.clear();
|
||||
unregisterToken.clear();
|
||||
heldValue.setUndefined();
|
||||
}
|
||||
};
|
||||
|
||||
using FinalizationRecordVector = Vector<FinalizationRecord*, 0, SystemAllocPolicy>;
|
||||
|
||||
struct FinalizationRegistryObject::Data {
|
||||
JS::Heap<JSObject*> cleanupCallback;
|
||||
JS::Heap<JSObject*> cleanupJob;
|
||||
FinalizationRecordVector records;
|
||||
FinalizationRecordVector cleanupQueue;
|
||||
bool queuedForCleanup;
|
||||
|
||||
explicit Data(JSObject* cleanupCallback)
|
||||
: cleanupCallback(cleanupCallback),
|
||||
cleanupJob(nullptr),
|
||||
records(SystemAllocPolicy()),
|
||||
cleanupQueue(SystemAllocPolicy()),
|
||||
queuedForCleanup(false)
|
||||
{}
|
||||
|
||||
~Data() {
|
||||
for (size_t i = 0; i < records.length(); i++)
|
||||
js_delete(records[i]);
|
||||
}
|
||||
|
||||
void trace(JSTracer* trc) {
|
||||
JS::TraceEdge(trc, &cleanupCallback, "FinalizationRegistry cleanup callback");
|
||||
if (cleanupJob.unbarrieredGet())
|
||||
JS::TraceEdge(trc, &cleanupJob, "FinalizationRegistry cleanup job");
|
||||
for (size_t i = 0; i < records.length(); i++)
|
||||
records[i]->trace(trc);
|
||||
}
|
||||
|
||||
bool appendRecord(FinalizationRecord* record) {
|
||||
return records.append(record);
|
||||
}
|
||||
|
||||
bool appendCleanupRecord(FinalizationRecord* record) {
|
||||
return cleanupQueue.append(record);
|
||||
}
|
||||
|
||||
void compactRecords() {
|
||||
for (size_t i = 0; i < records.length();) {
|
||||
FinalizationRecord* record = records[i];
|
||||
if (!record->active && !record->queued) {
|
||||
js_delete(record);
|
||||
records.erase(records.begin() + i);
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void unregister(HandleValue token, bool* removed) {
|
||||
*removed = false;
|
||||
for (size_t i = 0; i < records.length(); i++) {
|
||||
FinalizationRecord* record = records[i];
|
||||
if (record->matchesToken(token)) {
|
||||
record->clear();
|
||||
*removed = true;
|
||||
}
|
||||
}
|
||||
compactRecords();
|
||||
}
|
||||
};
|
||||
|
||||
static FinalizationRegistryObject::Data*
|
||||
GetData(JSObject* obj)
|
||||
{
|
||||
return obj->as<FinalizationRegistryObject>().getData();
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE bool
|
||||
IsFinalizationRegistry(HandleValue v)
|
||||
{
|
||||
return v.isObject() && v.toObject().is<FinalizationRegistryObject>();
|
||||
}
|
||||
|
||||
static bool
|
||||
FinalizationRegistry_register_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsFinalizationRegistry(args.thisv()));
|
||||
|
||||
if (!CanBeHeldWeakly(args.get(0))) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_FINALIZATION_REGISTRY_TARGET);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedValue target(cx, args[0]);
|
||||
RootedValue heldValue(cx, args.get(1));
|
||||
|
||||
bool same;
|
||||
if (!SameValue(cx, target, heldValue, &same))
|
||||
return false;
|
||||
if (same) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_FINALIZATION_REGISTRY_HELD_VALUE);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedValue unregisterToken(cx, args.get(2));
|
||||
if (!CanBeHeldWeakly(unregisterToken)) {
|
||||
if (!unregisterToken.isUndefined()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN);
|
||||
return false;
|
||||
}
|
||||
unregisterToken.setUndefined();
|
||||
}
|
||||
|
||||
FinalizationRecord* record = cx->new_<FinalizationRecord>(target, heldValue, unregisterToken);
|
||||
if (!record)
|
||||
return false;
|
||||
|
||||
auto* data = GetData(&args.thisv().toObject());
|
||||
if (!data->appendRecord(record)) {
|
||||
js_delete(record);
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
FinalizationRegistry_unregister_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsFinalizationRegistry(args.thisv()));
|
||||
|
||||
RootedValue unregisterToken(cx, args.get(0));
|
||||
if (!CanBeHeldWeakly(unregisterToken)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool removed = false;
|
||||
auto* data = GetData(&args.thisv().toObject());
|
||||
data->unregister(unregisterToken, &removed);
|
||||
|
||||
args.rval().setBoolean(removed);
|
||||
return true;
|
||||
}
|
||||
|
||||
const JSPropertySpec FinalizationRegistryObject::properties[] = {
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
const JSFunctionSpec FinalizationRegistryObject::methods[] = {
|
||||
JS_FN("register", FinalizationRegistryObject::register_, 2, 0),
|
||||
JS_FN("unregister", FinalizationRegistryObject::unregister, 1, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
static JSObject*
|
||||
InitFinalizationRegistryClass(JSContext* cx, HandleObject obj, bool defineMembers)
|
||||
{
|
||||
Handle<GlobalObject*> global = obj.as<GlobalObject>();
|
||||
RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
if (!proto)
|
||||
return nullptr;
|
||||
|
||||
RootedFunction ctor(cx, GlobalObject::createConstructor(cx, FinalizationRegistryObject::construct,
|
||||
ClassName(JSProto_FinalizationRegistry, cx), 1));
|
||||
if (!ctor)
|
||||
return nullptr;
|
||||
|
||||
if (!LinkConstructorAndPrototype(cx, ctor, proto))
|
||||
return nullptr;
|
||||
|
||||
if (defineMembers) {
|
||||
if (!DefinePropertiesAndFunctions(cx, proto, FinalizationRegistryObject::properties,
|
||||
FinalizationRegistryObject::methods)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!DefineToStringTag(cx, proto, cx->names().FinalizationRegistry))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_FinalizationRegistry, ctor, proto))
|
||||
return nullptr;
|
||||
return proto;
|
||||
}
|
||||
|
||||
/* static */ FinalizationRegistryObject*
|
||||
FinalizationRegistryObject::create(JSContext* cx, HandleObject cleanupCallback,
|
||||
HandleObject proto /* = nullptr */)
|
||||
{
|
||||
Rooted<FinalizationRegistryObject*> obj(cx,
|
||||
NewObjectWithClassProto<FinalizationRegistryObject>(cx, proto));
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
Data* data = cx->new_<Data>(cleanupCallback);
|
||||
if (!data)
|
||||
return nullptr;
|
||||
|
||||
obj->setPrivate(data);
|
||||
|
||||
RootedAtom funName(cx, cx->names().empty);
|
||||
RootedFunction cleanupJob(cx, NewNativeFunction(cx, FinalizationRegistryObject::cleanupJob, 0,
|
||||
funName, gc::AllocKind::FUNCTION_EXTENDED,
|
||||
GenericObject));
|
||||
if (!cleanupJob)
|
||||
return nullptr;
|
||||
|
||||
cleanupJob->setExtendedSlot(0, ObjectValue(*obj));
|
||||
data->cleanupJob = cleanupJob;
|
||||
|
||||
if (!cx->zone()->finalizationRegistries.append(WeakRef<JSObject*>(obj))) {
|
||||
ReportOutOfMemory(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FinalizationRegistryObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!ThrowIfNotConstructing(cx, args, "FinalizationRegistry"))
|
||||
return false;
|
||||
|
||||
RootedValue cleanupCallbackValue(cx, args.get(0));
|
||||
RootedObject cleanupCallback(cx, ValueToCallable(cx, cleanupCallbackValue, 0, NO_CONSTRUCT));
|
||||
if (!cleanupCallback)
|
||||
return false;
|
||||
|
||||
RootedObject proto(cx);
|
||||
RootedObject newTarget(cx, &args.newTarget().toObject());
|
||||
if (!GetPrototypeFromFinalizationRegistryConstructor(cx, newTarget, &proto))
|
||||
return false;
|
||||
|
||||
Rooted<FinalizationRegistryObject*> obj(cx,
|
||||
FinalizationRegistryObject::create(cx, cleanupCallback, proto));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FinalizationRegistryObject::register_(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsFinalizationRegistry, FinalizationRegistry_register_impl>(cx, args);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FinalizationRegistryObject::unregister(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsFinalizationRegistry, FinalizationRegistry_unregister_impl>(cx, args);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FinalizationRegistryObject::cleanupJob(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
RootedFunction job(cx, &args.callee().as<JSFunction>());
|
||||
Rooted<FinalizationRegistryObject*> registry(cx,
|
||||
&job->getExtendedSlot(0).toObject().as<FinalizationRegistryObject>());
|
||||
Data* data = registry->getData();
|
||||
if (!data) {
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
data->queuedForCleanup = false;
|
||||
|
||||
RootedValue callback(cx, ObjectValue(*data->cleanupCallback.get()));
|
||||
RootedValue heldValue(cx);
|
||||
RootedValue ignored(cx);
|
||||
RootedValue undefined(cx, UndefinedValue());
|
||||
|
||||
while (data->cleanupQueue.length() > 0) {
|
||||
FinalizationRecord* record = data->cleanupQueue[0];
|
||||
data->cleanupQueue.erase(data->cleanupQueue.begin());
|
||||
|
||||
if (!record->queued) {
|
||||
data->compactRecords();
|
||||
continue;
|
||||
}
|
||||
|
||||
heldValue.set(record->heldValue.get());
|
||||
record->clear();
|
||||
data->compactRecords();
|
||||
|
||||
if (!Call(cx, callback, undefined, heldValue, &ignored))
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
FinalizationRegistryObject::trace(JSTracer* trc, JSObject* obj)
|
||||
{
|
||||
if (Data* data = GetData(obj))
|
||||
data->trace(trc);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
FinalizationRegistryObject::finalize(FreeOp* fop, JSObject* obj)
|
||||
{
|
||||
if (Data* data = GetData(obj))
|
||||
fop->delete_(data);
|
||||
}
|
||||
|
||||
void
|
||||
FinalizationRegistryObject::traceWeakEdgesForCollectedZones(JSTracer* trc)
|
||||
{
|
||||
Data* data = getData();
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < data->records.length(); i++)
|
||||
data->records[i]->traceWeakEdgesForCollectedZones(trc);
|
||||
}
|
||||
|
||||
void
|
||||
FinalizationRegistryObject::sweepAfterGC(JSRuntime* rt)
|
||||
{
|
||||
Data* data = getData();
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
bool needsCleanupJob = false;
|
||||
for (size_t i = 0; i < data->records.length(); i++) {
|
||||
FinalizationRecord* record = data->records[i];
|
||||
if (!record->targetIsDead())
|
||||
continue;
|
||||
|
||||
record->queueForCleanup();
|
||||
if (!data->appendCleanupRecord(record)) {
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
oomUnsafe.crash("queueing FinalizationRegistry cleanup record");
|
||||
}
|
||||
needsCleanupJob = true;
|
||||
}
|
||||
|
||||
if (needsCleanupJob && !data->queuedForCleanup) {
|
||||
JSContext* cx = rt->contextFromMainThread();
|
||||
RootedObject cleanupJob(cx, data->cleanupJob.unbarrieredGet());
|
||||
if (!rt->enqueueFinalizationRegistryCleanupJob(cx, cleanupJob)) {
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
oomUnsafe.crash("queueing FinalizationRegistry cleanup job");
|
||||
}
|
||||
data->queuedForCleanup = true;
|
||||
}
|
||||
|
||||
data->compactRecords();
|
||||
}
|
||||
|
||||
static const ClassOps FinalizationRegistryObjectClassOps = {
|
||||
nullptr, /* addProperty */
|
||||
nullptr, /* delProperty */
|
||||
nullptr, /* getProperty */
|
||||
nullptr, /* setProperty */
|
||||
nullptr, /* enumerate */
|
||||
nullptr, /* resolve */
|
||||
nullptr, /* mayResolve */
|
||||
FinalizationRegistryObject::finalize,
|
||||
nullptr, /* call */
|
||||
nullptr, /* hasInstance */
|
||||
nullptr, /* construct */
|
||||
FinalizationRegistryObject::trace
|
||||
};
|
||||
|
||||
const Class FinalizationRegistryObject::class_ = {
|
||||
"FinalizationRegistry",
|
||||
JSCLASS_HAS_PRIVATE |
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_FinalizationRegistry) |
|
||||
JSCLASS_FOREGROUND_FINALIZE,
|
||||
&FinalizationRegistryObjectClassOps
|
||||
};
|
||||
|
||||
/* static */ JSObject*
|
||||
FinalizationRegistryObject::initClass(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
return ::InitFinalizationRegistryClass(cx, obj, true);
|
||||
}
|
||||
|
||||
JSObject*
|
||||
js::InitFinalizationRegistryClass(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
return FinalizationRegistryObject::initClass(cx, obj);
|
||||
}
|
||||
|
||||
JSObject*
|
||||
js::InitBareFinalizationRegistryCtor(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
return ::InitFinalizationRegistryClass(cx, obj, false);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef builtin_FinalizationRegistryObject_h
|
||||
#define builtin_FinalizationRegistryObject_h
|
||||
|
||||
#include "gc/Barrier.h"
|
||||
#include "vm/NativeObject.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
class FinalizationRegistryObject : public NativeObject
|
||||
{
|
||||
public:
|
||||
struct Data;
|
||||
|
||||
static const Class class_;
|
||||
|
||||
static JSObject* initClass(JSContext* cx, HandleObject obj);
|
||||
static FinalizationRegistryObject* create(JSContext* cx, HandleObject cleanupCallback,
|
||||
HandleObject proto = nullptr);
|
||||
|
||||
static void trace(JSTracer* trc, JSObject* obj);
|
||||
static void finalize(FreeOp* fop, JSObject* obj);
|
||||
void traceWeakEdgesForCollectedZones(JSTracer* trc);
|
||||
[[nodiscard]] static bool construct(JSContext* cx, unsigned argc, Value* vp);
|
||||
[[nodiscard]] static bool register_(JSContext* cx, unsigned argc, Value* vp);
|
||||
[[nodiscard]] static bool unregister(JSContext* cx, unsigned argc, Value* vp);
|
||||
[[nodiscard]] static bool cleanupJob(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
void sweepAfterGC(JSRuntime* rt);
|
||||
|
||||
Data* getData() const {
|
||||
return static_cast<Data*>(getPrivate());
|
||||
}
|
||||
|
||||
static const JSPropertySpec properties[];
|
||||
static const JSFunctionSpec methods[];
|
||||
};
|
||||
|
||||
extern JSObject*
|
||||
InitFinalizationRegistryClass(JSContext* cx, HandleObject obj);
|
||||
|
||||
extern JSObject*
|
||||
InitBareFinalizationRegistryCtor(JSContext* cx, HandleObject obj);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* builtin_FinalizationRegistryObject_h */
|
||||
@@ -55,6 +55,49 @@ function MapForEach(callbackfn, thisArg = undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
// ES2024
|
||||
// Map.groupBy ( items, callbackfn )
|
||||
function MapGroupBy(items, callbackfn) {
|
||||
// Step 1.
|
||||
RequireObjectCoercible(items);
|
||||
|
||||
// Step 2.
|
||||
if (!IsCallable(callbackfn))
|
||||
ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
|
||||
|
||||
// Step 3.
|
||||
var groups = std_Map_create();
|
||||
|
||||
// Step 4.
|
||||
var k = 0;
|
||||
|
||||
// Steps 5-8.
|
||||
for (var value of allowContentIter(items)) {
|
||||
// Step 6.a.
|
||||
if (k >= MAX_NUMERIC_INDEX)
|
||||
ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
|
||||
|
||||
// Step 6.b.
|
||||
var key = callContentFunction(callbackfn, undefined, value, k);
|
||||
|
||||
// Steps 6.c-d.
|
||||
var elements;
|
||||
if (callFunction(std_Map_has, groups, key)) {
|
||||
elements = callFunction(std_Map_get, groups, key);
|
||||
callFunction(std_Array_push, elements, value);
|
||||
} else {
|
||||
elements = [value];
|
||||
callFunction(std_Map_set, groups, key, elements);
|
||||
}
|
||||
|
||||
// Step 6.e.
|
||||
k++;
|
||||
}
|
||||
|
||||
// Step 9.
|
||||
return groups;
|
||||
}
|
||||
|
||||
var iteratorTemp = { mapIterationResultPair : null };
|
||||
|
||||
function MapIteratorNext() {
|
||||
|
||||
@@ -332,9 +332,15 @@ const JSPropertySpec MapObject::staticProperties[] = {
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
const JSFunctionSpec MapObject::staticMethods[] = {
|
||||
JS_SELF_HOSTED_FN("groupBy", "MapGroupBy", 2, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
static JSObject*
|
||||
InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSProtoKey key, Native construct,
|
||||
const JSPropertySpec* properties, const JSFunctionSpec* methods,
|
||||
const JSFunctionSpec* staticMethods,
|
||||
const JSPropertySpec* staticProperties)
|
||||
{
|
||||
RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
@@ -343,8 +349,13 @@ InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSPro
|
||||
|
||||
Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, construct, ClassName(key, cx), 0));
|
||||
if (!ctor ||
|
||||
!JS_DefineProperties(cx, ctor, staticProperties) ||
|
||||
!LinkConstructorAndPrototype(cx, ctor, proto) ||
|
||||
!JS_DefineProperties(cx, ctor, staticProperties))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (staticMethods && !JS_DefineFunctions(cx, ctor, staticMethods))
|
||||
return nullptr;
|
||||
if (!LinkConstructorAndPrototype(cx, ctor, proto) ||
|
||||
!DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
|
||||
!GlobalObject::initBuiltinConstructor(cx, global, key, ctor, proto))
|
||||
{
|
||||
@@ -359,7 +370,7 @@ MapObject::initClass(JSContext* cx, JSObject* obj)
|
||||
Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
|
||||
RootedObject proto(cx,
|
||||
InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods,
|
||||
staticProperties));
|
||||
staticMethods, staticProperties));
|
||||
if (proto) {
|
||||
// Define the "entries" method.
|
||||
JSFunction* fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0);
|
||||
@@ -1084,7 +1095,7 @@ SetObject::initClass(JSContext* cx, JSObject* obj)
|
||||
Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
|
||||
RootedObject proto(cx,
|
||||
InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods,
|
||||
staticProperties));
|
||||
nullptr, staticProperties));
|
||||
if (proto) {
|
||||
// Define the "values" method.
|
||||
JSFunction* fun = JS_DefineFunction(cx, proto, "values", values, 0, 0);
|
||||
|
||||
@@ -110,7 +110,9 @@ class MapObject : public NativeObject {
|
||||
static MOZ_MUST_USE bool getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj,
|
||||
JS::MutableHandle<GCVector<JS::Value>> entries);
|
||||
static MOZ_MUST_USE bool entries(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool get(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool has(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool set(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MapObject* create(JSContext* cx, HandleObject proto = nullptr);
|
||||
|
||||
// Publicly exposed Map calls for JSAPI access (webidl maplike/setlike
|
||||
@@ -137,6 +139,7 @@ class MapObject : public NativeObject {
|
||||
|
||||
static const JSPropertySpec properties[];
|
||||
static const JSFunctionSpec methods[];
|
||||
static const JSFunctionSpec staticMethods[];
|
||||
static const JSPropertySpec staticProperties[];
|
||||
ValueMap* getData() { return static_cast<ValueMap*>(getPrivate()); }
|
||||
static ValueMap& extract(HandleObject o);
|
||||
@@ -153,10 +156,8 @@ class MapObject : public NativeObject {
|
||||
static MOZ_MUST_USE bool size_impl(JSContext* cx, const CallArgs& args);
|
||||
static MOZ_MUST_USE bool size(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool get_impl(JSContext* cx, const CallArgs& args);
|
||||
static MOZ_MUST_USE bool get(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool has_impl(JSContext* cx, const CallArgs& args);
|
||||
static MOZ_MUST_USE bool set_impl(JSContext* cx, const CallArgs& args);
|
||||
static MOZ_MUST_USE bool set(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool delete_impl(JSContext* cx, const CallArgs& args);
|
||||
static MOZ_MUST_USE bool delete_(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool keys_impl(JSContext* cx, const CallArgs& args);
|
||||
|
||||
@@ -240,12 +240,16 @@ function ObjectGroupBy(items, callbackfn) {
|
||||
// Steps 5-8.
|
||||
for (var value of allowContentIter(items)) {
|
||||
// Step 6.a.
|
||||
var key = callContentFunction(callbackfn, undefined, value, k);
|
||||
if (k >= MAX_NUMERIC_INDEX)
|
||||
ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
|
||||
|
||||
// Step 6.b.
|
||||
var key = callContentFunction(callbackfn, undefined, value, k);
|
||||
|
||||
// Step 6.c.
|
||||
key = ToPropertyKey(key);
|
||||
|
||||
// Steps 6.c-d.
|
||||
// Steps 6.d-e.
|
||||
var elements = groups[key];
|
||||
if (elements === undefined) {
|
||||
_DefineDataProperty(groups, key, [value]);
|
||||
@@ -253,7 +257,7 @@ function ObjectGroupBy(items, callbackfn) {
|
||||
callFunction(std_Array_push, elements, value);
|
||||
}
|
||||
|
||||
// Step 6.e.
|
||||
// Step 6.f.
|
||||
k++;
|
||||
}
|
||||
|
||||
|
||||
@@ -3722,6 +3722,48 @@ Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
// ES2024
|
||||
// Promise.withResolvers ( )
|
||||
static bool
|
||||
Promise_static_withResolvers(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1.
|
||||
if (!args.thisv().isObject()) {
|
||||
ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, -1, args.thisv(), nullptr);
|
||||
return false;
|
||||
}
|
||||
RootedObject C(cx, &args.thisv().toObject());
|
||||
|
||||
// Step 2.
|
||||
Rooted<PromiseCapability> capability(cx);
|
||||
if (!NewPromiseCapability(cx, C, &capability, false))
|
||||
return false;
|
||||
|
||||
// Step 3.
|
||||
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
// Steps 4-7.
|
||||
RootedValue promise(cx, ObjectValue(*capability.promise()));
|
||||
if (!::JS_DefineProperty(cx, obj, "promise", promise, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
|
||||
RootedValue resolve(cx, ObjectValue(*capability.resolve()));
|
||||
if (!::JS_DefineProperty(cx, obj, "resolve", resolve, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
|
||||
RootedValue reject(cx, ObjectValue(*capability.reject()));
|
||||
if (!::JS_DefineProperty(cx, obj, "reject", reject, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
|
||||
// Step 8.
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
|
||||
*/
|
||||
@@ -5251,6 +5293,7 @@ static const JSFunctionSpec promise_static_methods[] = {
|
||||
JS_FN("race", Promise_static_race, 1, 0),
|
||||
JS_FN("reject", Promise_reject, 1, 0),
|
||||
JS_FN("resolve", Promise_static_resolve, 1, 0),
|
||||
JS_FN("withResolvers", Promise_static_withResolvers, 0, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
|
||||
@@ -654,6 +654,82 @@ function String_repeat(count) {
|
||||
return T;
|
||||
}
|
||||
|
||||
// ES2024
|
||||
// String.prototype.isWellFormed ( )
|
||||
function String_isWellFormed() {
|
||||
// Steps 1-2.
|
||||
RequireObjectCoercible(this);
|
||||
var S = ToString(this);
|
||||
|
||||
// Step 3.
|
||||
var length = S.length;
|
||||
for (var k = 0; k < length; k++) {
|
||||
var c = callFunction(std_String_charCodeAt, S, k);
|
||||
if (c >= 0xD800 && c <= 0xDBFF) {
|
||||
if (k + 1 >= length)
|
||||
return false;
|
||||
|
||||
var d = callFunction(std_String_charCodeAt, S, k + 1);
|
||||
if (d < 0xDC00 || d > 0xDFFF)
|
||||
return false;
|
||||
|
||||
k++;
|
||||
} else if (c >= 0xDC00 && c <= 0xDFFF) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4.
|
||||
return true;
|
||||
}
|
||||
|
||||
// ES2024
|
||||
// String.prototype.toWellFormed ( )
|
||||
function String_toWellFormed() {
|
||||
// Steps 1-2.
|
||||
RequireObjectCoercible(this);
|
||||
var S = ToString(this);
|
||||
|
||||
// Step 3.
|
||||
var length = S.length;
|
||||
var result = "";
|
||||
var copied = 0;
|
||||
|
||||
for (var k = 0; k < length; k++) {
|
||||
var c = callFunction(std_String_charCodeAt, S, k);
|
||||
var isUnpairedSurrogate = false;
|
||||
|
||||
if (c >= 0xD800 && c <= 0xDBFF) {
|
||||
if (k + 1 < length) {
|
||||
var d = callFunction(std_String_charCodeAt, S, k + 1);
|
||||
if (d >= 0xDC00 && d <= 0xDFFF) {
|
||||
k++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
isUnpairedSurrogate = true;
|
||||
} else if (c >= 0xDC00 && c <= 0xDFFF) {
|
||||
isUnpairedSurrogate = true;
|
||||
}
|
||||
|
||||
if (isUnpairedSurrogate) {
|
||||
if (copied < k)
|
||||
result += callFunction(String_substring, S, copied, k);
|
||||
result += "\uFFFD";
|
||||
copied = k + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (copied === 0)
|
||||
return S;
|
||||
|
||||
if (copied < length)
|
||||
result += callFunction(String_substring, S, copied, length);
|
||||
|
||||
// Step 4.
|
||||
return result;
|
||||
}
|
||||
|
||||
// ES6 draft specification, section 21.1.3.27, version 2013-09-27.
|
||||
function String_iterator() {
|
||||
RequireObjectCoercible(this);
|
||||
|
||||
@@ -41,10 +41,21 @@ function TypedArrayContentTypeIsBigIntMethod() {
|
||||
return IsBigInt64TypedArray(this) || IsBigUint64TypedArray(this);
|
||||
}
|
||||
|
||||
function ThrowIfTypedArrayOutOfBounds(tarray) {
|
||||
if (TypedArrayIsOutOfBounds(tarray))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
}
|
||||
|
||||
function ThrowIfPossiblyWrappedTypedArrayOutOfBounds(tarray) {
|
||||
if (PossiblyWrappedTypedArrayIsOutOfBounds(tarray))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
}
|
||||
|
||||
function GetAttachedArrayBuffer(tarray) {
|
||||
var buffer = ViewedArrayBufferIfReified(tarray);
|
||||
if (IsDetachedBuffer(buffer))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
|
||||
ThrowIfTypedArrayOutOfBounds(tarray);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -67,6 +78,7 @@ function IsTypedArrayEnsuringArrayBuffer(arg) {
|
||||
if (IsObject(arg) && IsPossiblyWrappedTypedArray(arg)) {
|
||||
if (PossiblyWrappedTypedArrayHasDetachedBuffer(arg))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
|
||||
ThrowIfPossiblyWrappedTypedArrayOutOfBounds(arg);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -89,6 +101,7 @@ function ValidateTypedArray(obj, error) {
|
||||
if (IsPossiblyWrappedTypedArray(obj)) {
|
||||
if (PossiblyWrappedTypedArrayHasDetachedBuffer(obj))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
|
||||
ThrowIfPossiblyWrappedTypedArrayOutOfBounds(obj);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1130,12 +1143,18 @@ function TypedArraySet(overloaded, offset = 0) {
|
||||
// Steps 9-10.
|
||||
var targetBuffer = GetAttachedArrayBuffer(target);
|
||||
|
||||
ThrowIfTypedArrayOutOfBounds(target);
|
||||
|
||||
// Step 11.
|
||||
var targetLength = TypedArrayLength(target);
|
||||
|
||||
// Steps 12 et seq.
|
||||
if (IsPossiblyWrappedTypedArray(overloaded))
|
||||
if (IsPossiblyWrappedTypedArray(overloaded)) {
|
||||
if (PossiblyWrappedTypedArrayHasDetachedBuffer(overloaded))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
|
||||
ThrowIfPossiblyWrappedTypedArrayOutOfBounds(overloaded);
|
||||
return SetFromTypedArray(target, overloaded, targetOffset, targetLength);
|
||||
}
|
||||
|
||||
return SetFromNonTypedArray(target, overloaded, targetOffset, targetLength, targetBuffer);
|
||||
}
|
||||
@@ -1472,6 +1491,8 @@ function TypedArraySubarray(begin, end) {
|
||||
"TypedArraySubarray");
|
||||
}
|
||||
|
||||
GetAttachedArrayBuffer(obj);
|
||||
|
||||
// Steps 4-6.
|
||||
var buffer = TypedArrayBuffer(obj);
|
||||
var srcLength = TypedArrayLength(obj);
|
||||
@@ -1952,7 +1973,10 @@ function ArrayBufferSlice(start, end) {
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
|
||||
|
||||
// Steps 19-21.
|
||||
ArrayBufferCopyData(new_, 0, O, first | 0, newLen | 0, isWrapped);
|
||||
var currentLen = ArrayBufferByteLength(O);
|
||||
var copyLen = first >= currentLen ? 0 : std_Math_min(newLen, currentLen - first);
|
||||
if (copyLen > 0)
|
||||
ArrayBufferCopyData(new_, 0, O, first | 0, copyLen | 0, isWrapped);
|
||||
|
||||
// Step 22.
|
||||
return new_;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "jsapi.h"
|
||||
#include "jscntxt.h"
|
||||
|
||||
#include "builtin/WeakRefObject.h"
|
||||
#include "vm/SelfHosting.h"
|
||||
|
||||
#include "vm/Interpreter-inl.h"
|
||||
@@ -21,21 +22,80 @@ IsWeakMap(HandleValue v)
|
||||
return v.isObject() && v.toObject().is<WeakMapObject>();
|
||||
}
|
||||
|
||||
struct WeakMapObject::Data
|
||||
{
|
||||
ObjectValueMap objectMap;
|
||||
SymbolValueMap symbolMap;
|
||||
|
||||
Data(JSContext* cx, JSObject* owner)
|
||||
: objectMap(cx, owner),
|
||||
symbolMap(cx, owner)
|
||||
{}
|
||||
|
||||
bool init() {
|
||||
return objectMap.init() && symbolMap.init();
|
||||
}
|
||||
};
|
||||
|
||||
ObjectValueMap*
|
||||
WeakMapObject::getMap()
|
||||
{
|
||||
Data* data = getData();
|
||||
return data ? &data->objectMap : nullptr;
|
||||
}
|
||||
|
||||
SymbolValueMap*
|
||||
WeakMapObject::getSymbolMap()
|
||||
{
|
||||
Data* data = getData();
|
||||
return data ? &data->symbolMap : nullptr;
|
||||
}
|
||||
|
||||
static bool
|
||||
EnsureWeakMapData(JSContext* cx, Handle<WeakMapObject*> mapObj)
|
||||
{
|
||||
if (mapObj->getData())
|
||||
return true;
|
||||
|
||||
auto data = cx->make_unique<WeakMapObject::Data>(cx, mapObj.get());
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
if (!data->init()) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
mapObj->setPrivate(data.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
WeakMap_has_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsWeakMap(args.thisv()));
|
||||
|
||||
if (!args.get(0).isObject()) {
|
||||
if (!CanBeHeldWeakly(args.get(0))) {
|
||||
args.rval().setBoolean(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
|
||||
JSObject* key = &args[0].toObject();
|
||||
if (map->has(key)) {
|
||||
args.rval().setBoolean(true);
|
||||
return true;
|
||||
WeakMapObject& weakMap = args.thisv().toObject().as<WeakMapObject>();
|
||||
if (args.get(0).isObject()) {
|
||||
if (ObjectValueMap* map = weakMap.getMap()) {
|
||||
JSObject* key = &args[0].toObject();
|
||||
if (map->has(key)) {
|
||||
args.rval().setBoolean(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (SymbolValueMap* map = weakMap.getSymbolMap()) {
|
||||
JS::Symbol* key = args[0].toSymbol();
|
||||
if (map->has(key)) {
|
||||
args.rval().setBoolean(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,16 +115,27 @@ WeakMap_get_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsWeakMap(args.thisv()));
|
||||
|
||||
if (!args.get(0).isObject()) {
|
||||
if (!CanBeHeldWeakly(args.get(0))) {
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
|
||||
JSObject* key = &args[0].toObject();
|
||||
if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
|
||||
args.rval().set(ptr->value());
|
||||
return true;
|
||||
WeakMapObject& weakMap = args.thisv().toObject().as<WeakMapObject>();
|
||||
if (args.get(0).isObject()) {
|
||||
if (ObjectValueMap* map = weakMap.getMap()) {
|
||||
JSObject* key = &args[0].toObject();
|
||||
if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
|
||||
args.rval().set(ptr->value());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (SymbolValueMap* map = weakMap.getSymbolMap()) {
|
||||
JS::Symbol* key = args[0].toSymbol();
|
||||
if (SymbolValueMap::Ptr ptr = map->lookup(key)) {
|
||||
args.rval().set(ptr->value());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,17 +155,29 @@ WeakMap_delete_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsWeakMap(args.thisv()));
|
||||
|
||||
if (!args.get(0).isObject()) {
|
||||
if (!CanBeHeldWeakly(args.get(0))) {
|
||||
args.rval().setBoolean(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
|
||||
JSObject* key = &args[0].toObject();
|
||||
if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
|
||||
map->remove(ptr);
|
||||
args.rval().setBoolean(true);
|
||||
return true;
|
||||
WeakMapObject& weakMap = args.thisv().toObject().as<WeakMapObject>();
|
||||
if (args.get(0).isObject()) {
|
||||
if (ObjectValueMap* map = weakMap.getMap()) {
|
||||
JSObject* key = &args[0].toObject();
|
||||
if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
|
||||
map->remove(ptr);
|
||||
args.rval().setBoolean(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (SymbolValueMap* map = weakMap.getSymbolMap()) {
|
||||
JS::Symbol* key = args[0].toSymbol();
|
||||
if (SymbolValueMap::Ptr ptr = map->lookup(key)) {
|
||||
map->remove(ptr);
|
||||
args.rval().setBoolean(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,22 +209,13 @@ TryPreserveReflector(JSContext* cx, HandleObject obj)
|
||||
return true;
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE bool
|
||||
SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
|
||||
HandleObject key, HandleValue value)
|
||||
static bool
|
||||
SetWeakMapObjectEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
|
||||
HandleObject key, HandleValue value)
|
||||
{
|
||||
if (!EnsureWeakMapData(cx, mapObj))
|
||||
return false;
|
||||
ObjectValueMap* map = mapObj->getMap();
|
||||
if (!map) {
|
||||
auto newMap = cx->make_unique<ObjectValueMap>(cx, mapObj.get());
|
||||
if (!newMap)
|
||||
return false;
|
||||
if (!newMap->init()) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
map = newMap.release();
|
||||
mapObj->setPrivate(map);
|
||||
}
|
||||
|
||||
// Preserve wrapped native keys to prevent wrapper optimization.
|
||||
if (!TryPreserveReflector(cx, key))
|
||||
@@ -162,13 +236,28 @@ SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
WeakMap_set_impl(JSContext* cx, const CallArgs& args)
|
||||
static bool
|
||||
SetWeakMapSymbolEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
|
||||
Handle<JS::Symbol*> key, HandleValue value)
|
||||
{
|
||||
MOZ_ASSERT(IsWeakMap(args.thisv()));
|
||||
if (!EnsureWeakMapData(cx, mapObj))
|
||||
return false;
|
||||
SymbolValueMap* map = mapObj->getSymbolMap();
|
||||
|
||||
if (!args.get(0).isObject()) {
|
||||
UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr);
|
||||
MOZ_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment());
|
||||
if (!map->put(key, value)) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE bool
|
||||
SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
|
||||
HandleValue key, HandleValue value)
|
||||
{
|
||||
if (!CanBeHeldWeakly(key)) {
|
||||
UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, key, nullptr);
|
||||
if (!bytes)
|
||||
return false;
|
||||
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
|
||||
@@ -176,11 +265,24 @@ WeakMap_set_impl(JSContext* cx, const CallArgs& args)
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject key(cx, &args[0].toObject());
|
||||
if (key.isObject()) {
|
||||
RootedObject objectKey(cx, &key.toObject());
|
||||
return SetWeakMapObjectEntryInternal(cx, mapObj, objectKey, value);
|
||||
}
|
||||
|
||||
Rooted<JS::Symbol*> symbolKey(cx, key.toSymbol());
|
||||
return SetWeakMapSymbolEntryInternal(cx, mapObj, symbolKey, value);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
WeakMap_set_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsWeakMap(args.thisv()));
|
||||
|
||||
Rooted<JSObject*> thisObj(cx, &args.thisv().toObject());
|
||||
Rooted<WeakMapObject*> map(cx, &thisObj->as<WeakMapObject>());
|
||||
|
||||
if (!SetWeakMapEntryInternal(cx, map, key, args.get(1)))
|
||||
if (!SetWeakMapEntryInternal(cx, map, args.get(0), args.get(1)))
|
||||
return false;
|
||||
args.rval().set(args.thisv());
|
||||
return true;
|
||||
@@ -193,6 +295,13 @@ js::WeakMap_set(JSContext* cx, unsigned argc, Value* vp)
|
||||
return CallNonGenericMethod<IsWeakMap, WeakMap_set_impl>(cx, args);
|
||||
}
|
||||
|
||||
bool
|
||||
js::SetWeakMapEntryValue(JSContext* cx, HandleObject mapObj, HandleValue key, HandleValue val)
|
||||
{
|
||||
Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>());
|
||||
return SetWeakMapEntryInternal(cx, rootedMap, key, val);
|
||||
}
|
||||
|
||||
JS_FRIEND_API(bool)
|
||||
JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHandleObject ret)
|
||||
{
|
||||
@@ -205,11 +314,11 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHan
|
||||
RootedObject arr(cx, NewDenseEmptyArray(cx));
|
||||
if (!arr)
|
||||
return false;
|
||||
ObjectValueMap* map = obj->as<WeakMapObject>().getMap();
|
||||
if (map) {
|
||||
WeakMapObject::Data* data = obj->as<WeakMapObject>().getData();
|
||||
if (data) {
|
||||
// Prevent GC from mutating the weakmap while iterating.
|
||||
AutoSuppressGC suppress(cx);
|
||||
for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) {
|
||||
for (ObjectValueMap::Base::Range r = data->objectMap.all(); !r.empty(); r.popFront()) {
|
||||
JS::ExposeObjectToActiveJS(r.front().key());
|
||||
RootedObject key(cx, r.front().key());
|
||||
if (!cx->compartment()->wrap(cx, &key))
|
||||
@@ -217,6 +326,14 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHan
|
||||
if (!NewbornArrayPush(cx, arr, ObjectValue(*key)))
|
||||
return false;
|
||||
}
|
||||
for (SymbolValueMap::Base::Range r = data->symbolMap.all(); !r.empty(); r.popFront()) {
|
||||
gc::ExposeGCThingToActiveJS(JS::GCCellPtr(r.front().key().get()));
|
||||
RootedValue key(cx, SymbolValue(r.front().key()));
|
||||
if (!cx->compartment()->wrap(cx, &key))
|
||||
return false;
|
||||
if (!NewbornArrayPush(cx, arr, key))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ret.set(arr);
|
||||
return true;
|
||||
@@ -225,21 +342,23 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHan
|
||||
static void
|
||||
WeakMap_mark(JSTracer* trc, JSObject* obj)
|
||||
{
|
||||
if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap())
|
||||
map->trace(trc);
|
||||
if (WeakMapObject::Data* data = obj->as<WeakMapObject>().getData()) {
|
||||
data->objectMap.trace(trc);
|
||||
data->symbolMap.trace(trc);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
WeakMap_finalize(FreeOp* fop, JSObject* obj)
|
||||
{
|
||||
MOZ_ASSERT(fop->maybeOffMainThread());
|
||||
if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap()) {
|
||||
if (WeakMapObject::Data* data = obj->as<WeakMapObject>().getData()) {
|
||||
#ifdef DEBUG
|
||||
map->~ObjectValueMap();
|
||||
memset(static_cast<void*>(map), 0xdc, sizeof(*map));
|
||||
fop->free_(map);
|
||||
data->~Data();
|
||||
memset(static_cast<void*>(data), 0xdc, sizeof(*data));
|
||||
fop->free_(data);
|
||||
#else
|
||||
fop->delete_(map);
|
||||
fop->delete_(data);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -282,7 +401,8 @@ JS::SetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key,
|
||||
CHECK_REQUEST(cx);
|
||||
assertSameCompartment(cx, key, val);
|
||||
Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>());
|
||||
return SetWeakMapEntryInternal(cx, rootedMap, key, val);
|
||||
RootedValue keyValue(cx, ObjectValue(*key));
|
||||
return SetWeakMapEntryInternal(cx, rootedMap, keyValue, val);
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -386,4 +506,3 @@ js::InitBareWeakMapCtor(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
return InitWeakMapClass(cx, obj, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,11 @@ class WeakMapObject : public NativeObject
|
||||
public:
|
||||
static const Class class_;
|
||||
|
||||
ObjectValueMap* getMap() { return static_cast<ObjectValueMap*>(getPrivate()); }
|
||||
struct Data;
|
||||
|
||||
Data* getData() { return static_cast<Data*>(getPrivate()); }
|
||||
ObjectValueMap* getMap();
|
||||
SymbolValueMap* getSymbolMap();
|
||||
};
|
||||
|
||||
} // namespace js
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
#include "gc/Nursery.h"
|
||||
#include "gc/Tracer.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/Symbol.h"
|
||||
|
||||
#include "jswrapper.h"
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
#include "vm/Interpreter-inl.h"
|
||||
@@ -31,17 +33,64 @@ IsWeakRef(HandleValue v)
|
||||
return v.isObject() && v.toObject().is<WeakRefObject>();
|
||||
}
|
||||
|
||||
static bool
|
||||
GetPrototypeFromWeakRefConstructor(JSContext* cx, HandleObject newTarget,
|
||||
MutableHandleObject proto)
|
||||
{
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, proto))
|
||||
return false;
|
||||
if (proto)
|
||||
return true;
|
||||
|
||||
RootedObject realmObject(cx, CheckedUnwrap(newTarget, /* stopAtWindowProxy = */ false));
|
||||
if (!realmObject)
|
||||
return false;
|
||||
|
||||
{
|
||||
JSAutoCompartment ac(cx, realmObject);
|
||||
Rooted<GlobalObject*> global(cx, &realmObject->global());
|
||||
if (!GlobalObject::ensureConstructor(cx, global, JSProto_WeakRef))
|
||||
return false;
|
||||
proto.set(&global->getPrototype(JSProto_WeakRef).toObject());
|
||||
}
|
||||
|
||||
return cx->compartment()->wrap(cx, proto);
|
||||
}
|
||||
|
||||
static bool
|
||||
WeakRef_deref_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsWeakRef(args.thisv()));
|
||||
|
||||
WeakRefObject::Referent* data = GetReferent(&args.thisv().toObject());
|
||||
JSObject* target = data ? data->target.get() : nullptr;
|
||||
if (target)
|
||||
args.rval().setObject(*target);
|
||||
else
|
||||
if (!data) {
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data->isObject()) {
|
||||
JSObject* target = data->objectTarget.get();
|
||||
if (target) {
|
||||
RootedValue kept(cx, ObjectValue(*target));
|
||||
if (!cx->runtime()->addWeakRefKeptObject(cx, kept))
|
||||
return false;
|
||||
args.rval().set(kept);
|
||||
} else {
|
||||
args.rval().setUndefined();
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(data->isSymbol());
|
||||
JS::Symbol* target = data->symbolTarget.get();
|
||||
if (target) {
|
||||
RootedValue kept(cx, SymbolValue(target));
|
||||
if (!cx->runtime()->addWeakRefKeptObject(cx, kept))
|
||||
return false;
|
||||
args.rval().set(kept);
|
||||
} else {
|
||||
args.rval().setUndefined();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -83,13 +132,18 @@ InitWeakRefClass(JSContext* cx, HandleObject obj, bool defineMembers)
|
||||
}
|
||||
|
||||
/* static */ WeakRefObject*
|
||||
WeakRefObject::create(JSContext* cx, HandleObject target, HandleObject proto /* = nullptr */)
|
||||
WeakRefObject::create(JSContext* cx, HandleValue target, HandleObject proto /* = nullptr */)
|
||||
{
|
||||
Rooted<WeakRefObject*> obj(cx, NewObjectWithClassProto<WeakRefObject>(cx, proto));
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
Referent* data = cx->new_<Referent>(target, cx->options().weakRefs());
|
||||
Referent* data;
|
||||
if (target.isObject())
|
||||
data = cx->new_<Referent>(&target.toObject());
|
||||
else
|
||||
data = cx->new_<Referent>(target.toSymbol());
|
||||
|
||||
if (!data)
|
||||
return nullptr;
|
||||
|
||||
@@ -97,6 +151,18 @@ WeakRefObject::create(JSContext* cx, HandleObject target, HandleObject proto /*
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool
|
||||
js::CanBeHeldWeakly(HandleValue target)
|
||||
{
|
||||
if (target.isObject())
|
||||
return true;
|
||||
|
||||
if (target.isSymbol())
|
||||
return target.toSymbol()->code() != JS::SymbolCode::InSymbolRegistry;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -105,28 +171,25 @@ WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
if (!ThrowIfNotConstructing(cx, args, "WeakRef"))
|
||||
return false;
|
||||
|
||||
if (!args.get(0).isObject()) {
|
||||
UniqueChars bytes =
|
||||
DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr);
|
||||
if (!bytes)
|
||||
return false;
|
||||
|
||||
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
|
||||
bytes.get());
|
||||
if (!CanBeHeldWeakly(args.get(0))) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_WEAKREF_TARGET);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject target(cx, &args[0].toObject());
|
||||
RootedValue target(cx, args[0]);
|
||||
|
||||
RootedObject proto(cx);
|
||||
RootedObject newTarget(cx, &args.newTarget().toObject());
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
|
||||
if (!GetPrototypeFromWeakRefConstructor(cx, newTarget, &proto))
|
||||
return false;
|
||||
|
||||
Rooted<WeakRefObject*> obj(cx, WeakRefObject::create(cx, target, proto));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
if (!cx->runtime()->addWeakRefKeptObject(cx, target))
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
@@ -142,19 +205,23 @@ WeakRefObject::deref(JSContext* cx, unsigned argc, Value* vp)
|
||||
WeakRefObject::trace(JSTracer* trc, JSObject* obj)
|
||||
{
|
||||
if (Referent* data = GetReferent(obj)) {
|
||||
JSObject* target = data->target.unbarrieredGet();
|
||||
if (!target)
|
||||
return;
|
||||
if (data->isObject()) {
|
||||
JSObject* target = data->objectTarget.unbarrieredGet();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
// When pref-disabled, keep referent alive via strong trace so deref()
|
||||
// stays usable as a stub without touching GC internals.
|
||||
if (!data->enabled) {
|
||||
TraceManuallyBarrieredEdge(trc, data->target.unsafeGet(), "WeakRef stub referent");
|
||||
} else if (IsInsideNursery(target)) {
|
||||
// Weak edges must be tenured; trace strongly while referent is in the nursery.
|
||||
TraceManuallyBarrieredEdge(trc, data->target.unsafeGet(), "WeakRef nursery referent");
|
||||
if (IsInsideNursery(target)) {
|
||||
// Weak edges must be tenured; trace strongly while referent is in the nursery.
|
||||
TraceManuallyBarrieredEdge(trc, data->objectTarget.unsafeGet(),
|
||||
"WeakRef nursery referent");
|
||||
} else {
|
||||
TraceWeakEdge(trc, &data->objectTarget, "WeakRef referent");
|
||||
}
|
||||
} else {
|
||||
TraceWeakEdge(trc, &data->target, "WeakRef referent");
|
||||
MOZ_ASSERT(data->isSymbol());
|
||||
JS::Symbol* target = data->symbolTarget.unbarrieredGet();
|
||||
if (target)
|
||||
TraceWeakEdge(trc, &data->symbolTarget, "WeakRef symbol referent");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +229,6 @@ WeakRefObject::trace(JSTracer* trc, JSObject* obj)
|
||||
/* static */ void
|
||||
WeakRefObject::finalize(FreeOp* fop, JSObject* obj)
|
||||
{
|
||||
MOZ_ASSERT(!fop->maybeOffMainThread());
|
||||
if (Referent* data = GetReferent(obj))
|
||||
fop->delete_(data);
|
||||
}
|
||||
|
||||
@@ -11,20 +11,34 @@
|
||||
|
||||
namespace js {
|
||||
|
||||
bool CanBeHeldWeakly(HandleValue target);
|
||||
|
||||
class WeakRefObject : public NativeObject
|
||||
{
|
||||
public:
|
||||
struct Referent {
|
||||
explicit Referent(JSObject* obj, bool enabled)
|
||||
: target(obj), enabled(enabled) {}
|
||||
WeakRef<JSObject*> target;
|
||||
bool enabled;
|
||||
enum class Kind {
|
||||
Object,
|
||||
Symbol
|
||||
};
|
||||
|
||||
explicit Referent(JSObject* obj)
|
||||
: kind(Kind::Object), objectTarget(obj), symbolTarget(nullptr) {}
|
||||
explicit Referent(JS::Symbol* sym)
|
||||
: kind(Kind::Symbol), objectTarget(nullptr), symbolTarget(sym) {}
|
||||
|
||||
bool isObject() const { return kind == Kind::Object; }
|
||||
bool isSymbol() const { return kind == Kind::Symbol; }
|
||||
|
||||
Kind kind;
|
||||
WeakRef<JSObject*> objectTarget;
|
||||
WeakRef<JS::Symbol*> symbolTarget;
|
||||
};
|
||||
|
||||
static const Class class_;
|
||||
|
||||
static JSObject* initClass(JSContext* cx, HandleObject obj);
|
||||
static WeakRefObject* create(JSContext* cx, HandleObject target, HandleObject proto = nullptr);
|
||||
static WeakRefObject* create(JSContext* cx, HandleValue target, HandleObject proto = nullptr);
|
||||
|
||||
static void trace(JSTracer* trc, JSObject* obj);
|
||||
static void finalize(FreeOp* fop, JSObject* obj);
|
||||
@@ -37,7 +51,8 @@ class WeakRefObject : public NativeObject
|
||||
|
||||
WeakRef<JSObject*>& target() {
|
||||
MOZ_ASSERT(getData());
|
||||
return getData()->target;
|
||||
MOZ_ASSERT(getData()->isObject());
|
||||
return getData()->objectTarget;
|
||||
}
|
||||
|
||||
static const JSPropertySpec properties[];
|
||||
|
||||
@@ -32,7 +32,7 @@ function WeakSet_add(value) {
|
||||
ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "add", typeof S);
|
||||
|
||||
// Step 5.
|
||||
if (!IsObject(value))
|
||||
if (!CanBeHeldWeakly(value))
|
||||
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(0, value));
|
||||
|
||||
// Steps 7-8.
|
||||
@@ -55,7 +55,7 @@ function WeakSet_delete(value) {
|
||||
ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "delete", typeof S);
|
||||
|
||||
// Step 5.
|
||||
if (!IsObject(value))
|
||||
if (!CanBeHeldWeakly(value))
|
||||
return false;
|
||||
|
||||
// Steps 7-8.
|
||||
@@ -75,7 +75,7 @@ function WeakSet_has(value) {
|
||||
ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "has", typeof S);
|
||||
|
||||
// Step 6.
|
||||
if (!IsObject(value))
|
||||
if (!CanBeHeldWeakly(value))
|
||||
return false;
|
||||
|
||||
// Steps 7-8.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "builtin/MapObject.h"
|
||||
#include "builtin/SelfHostingDefines.h"
|
||||
#include "builtin/WeakMapObject.h"
|
||||
#include "builtin/WeakRefObject.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/SelfHosting.h"
|
||||
|
||||
@@ -108,7 +109,6 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
||||
if (optimized) {
|
||||
RootedValue keyVal(cx);
|
||||
RootedObject keyObject(cx);
|
||||
RootedValue placeholder(cx, BooleanValue(true));
|
||||
RootedObject map(cx, &obj->getReservedSlot(WEAKSET_MAP_SLOT).toObject());
|
||||
RootedArrayObject array(cx, &iterable.toObject().as<ArrayObject>());
|
||||
@@ -116,7 +116,7 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
keyVal.set(array->getDenseElement(index));
|
||||
MOZ_ASSERT(!keyVal.isMagic(JS_ELEMENTS_HOLE));
|
||||
|
||||
if (keyVal.isPrimitive()) {
|
||||
if (!CanBeHeldWeakly(keyVal)) {
|
||||
UniqueChars bytes =
|
||||
DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, keyVal, nullptr);
|
||||
if (!bytes)
|
||||
@@ -126,8 +126,7 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
return false;
|
||||
}
|
||||
|
||||
keyObject = &keyVal.toObject();
|
||||
if (!SetWeakMapEntry(cx, map, keyObject, placeholder))
|
||||
if (!SetWeakMapEntryValue(cx, map, keyVal, placeholder))
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -947,6 +947,8 @@ class GCRuntime
|
||||
IncrementalProgress drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase);
|
||||
template <class CompartmentIterT> void markWeakReferences(gcstats::Phase phase);
|
||||
void markWeakReferencesInCurrentGroup(gcstats::Phase phase);
|
||||
template <class ZoneIterT> void traceFinalizationRegistryWeakRefs();
|
||||
void traceFinalizationRegistryWeakRefsInCurrentGroup();
|
||||
template <class ZoneIterT, class CompartmentIterT> void markGrayReferences(gcstats::Phase phase);
|
||||
void markBufferedGrayRoots(JS::Zone* zone);
|
||||
void markGrayReferencesInCurrentGroup(gcstats::Phase phase);
|
||||
@@ -959,6 +961,7 @@ class GCRuntime
|
||||
void getNextZoneGroup();
|
||||
void endMarkingZoneGroup();
|
||||
void beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock);
|
||||
void sweepFinalizationRegistries();
|
||||
bool shouldReleaseObservedTypes();
|
||||
void endSweepingZoneGroup();
|
||||
IncrementalProgress performSweepActions(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "jscntxt.h"
|
||||
|
||||
#include "ds/SplayTree.h"
|
||||
#include "gc/Barrier.h"
|
||||
#include "gc/FindSCCs.h"
|
||||
#include "gc/GCRuntime.h"
|
||||
#include "js/GCHashTable.h"
|
||||
@@ -325,6 +326,10 @@ struct Zone : public JS::shadow::Zone,
|
||||
using WeakEdges = js::Vector<js::gc::TenuredCell**, 0, js::SystemAllocPolicy>;
|
||||
WeakEdges gcWeakRefs;
|
||||
|
||||
// FinalizationRegistry objects with weakly-held target cells in this zone.
|
||||
using FinalizationRegistryVector = js::Vector<js::WeakRef<JSObject*>, 0, js::SystemAllocPolicy>;
|
||||
FinalizationRegistryVector finalizationRegistries;
|
||||
|
||||
// List of non-ephemeron weak containers to sweep during beginSweepingZoneGroup.
|
||||
mozilla::LinkedList<WeakCache<void*>> weakCaches_;
|
||||
void registerWeakCache(WeakCache<void*>* cachep) {
|
||||
|
||||
@@ -1464,6 +1464,12 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_
|
||||
res.isNumber() &&
|
||||
!TypedArrayGetElemStubExists(stub, obj))
|
||||
{
|
||||
if (obj->is<TypedArrayObject>() &&
|
||||
obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!cx->runtime()->jitSupportsFloatingPoint &&
|
||||
(TypedThingRequiresFloatingPoint(obj) || rhs.isDouble()))
|
||||
{
|
||||
@@ -2154,6 +2160,8 @@ ICGetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm)
|
||||
Register obj = masm.extractObject(R0, ExtractTemp0);
|
||||
masm.loadPtr(Address(ICStubReg, ICGetElem_TypedArray::offsetOfShape()), scratchReg);
|
||||
masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
|
||||
if (layout_ == Layout_TypedArray)
|
||||
GuardResizableOrGrowableTypedArray(masm, obj, scratchReg, &failure);
|
||||
|
||||
// Ensure the index is an integer.
|
||||
if (cx->runtime()->jitSupportsFloatingPoint) {
|
||||
@@ -2625,6 +2633,9 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_
|
||||
bool expectOutOfBounds;
|
||||
double idx = index.toNumber();
|
||||
if (obj->is<TypedArrayObject>()) {
|
||||
if (obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
|
||||
return true;
|
||||
|
||||
expectOutOfBounds = (idx < 0 || idx >= double(obj->as<TypedArrayObject>().length()));
|
||||
} else {
|
||||
// Typed objects throw on out of bounds accesses. Don't attach
|
||||
@@ -3215,6 +3226,8 @@ ICSetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm)
|
||||
Register obj = masm.extractObject(R0, ExtractTemp0);
|
||||
masm.loadPtr(Address(ICStubReg, ICSetElem_TypedArray::offsetOfShape()), scratchReg);
|
||||
masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
|
||||
if (layout_ == Layout_TypedArray)
|
||||
GuardResizableOrGrowableTypedArray(masm, obj, scratchReg, &failure);
|
||||
|
||||
// Ensure the index is an integer.
|
||||
if (cx->runtime()->jitSupportsFloatingPoint) {
|
||||
|
||||
@@ -8889,9 +8889,17 @@ CodeGenerator::branchIfNotEmptyObjectElements(Register obj, Label* target)
|
||||
Address(obj, NativeObject::offsetOfElements()),
|
||||
ImmPtr(js::emptyObjectElements),
|
||||
&emptyObj);
|
||||
masm.branchPtr(Assembler::NotEqual,
|
||||
masm.branchPtr(Assembler::Equal,
|
||||
Address(obj, NativeObject::offsetOfElements()),
|
||||
ImmPtr(js::emptyObjectElementsShared),
|
||||
&emptyObj);
|
||||
masm.branchPtr(Assembler::Equal,
|
||||
Address(obj, NativeObject::offsetOfElements()),
|
||||
ImmPtr(js::emptyObjectElementsResizableOrGrowable),
|
||||
&emptyObj);
|
||||
masm.branchPtr(Assembler::NotEqual,
|
||||
Address(obj, NativeObject::offsetOfElements()),
|
||||
ImmPtr(js::emptyObjectElementsSharedResizableOrGrowable),
|
||||
target);
|
||||
masm.bind(&emptyObj);
|
||||
}
|
||||
|
||||
@@ -535,8 +535,7 @@ class CodeGenerator final : public CodeGeneratorSpecific
|
||||
Label* ifDoesntEmulateUndefined,
|
||||
Register scratch, OutOfLineTestObject* ool);
|
||||
|
||||
// Branch to target unless obj has an emptyObjectElements or emptyObjectElementsShared
|
||||
// elements pointer.
|
||||
// Branch to target unless obj has one of the empty elements pointers.
|
||||
void branchIfNotEmptyObjectElements(Register obj, Label* target);
|
||||
|
||||
void emitStoreElementTyped(const LAllocation* value, MIRType valueType, MIRType elementType,
|
||||
|
||||
@@ -1245,6 +1245,7 @@ GenerateTypedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAtta
|
||||
masm.branchPtr(Assembler::AboveOrEqual, tmpReg,
|
||||
ImmPtr(&TypedArrayObject::classes[Scalar::MaxTypedArrayViewType]),
|
||||
failures);
|
||||
GuardResizableOrGrowableTypedArray(masm, object, tmpReg, failures);
|
||||
|
||||
// Load length.
|
||||
masm.loadTypedOrValue(Address(object, TypedArrayObject::lengthOffset()), output);
|
||||
@@ -1644,6 +1645,9 @@ GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript
|
||||
if (!JSID_IS_ATOM(id, cx->names().length))
|
||||
return true;
|
||||
|
||||
if (obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
|
||||
return true;
|
||||
|
||||
if (hasTypedArrayLengthStub(obj))
|
||||
return true;
|
||||
|
||||
@@ -4038,6 +4042,12 @@ GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& i
|
||||
if (!obj->is<TypedArrayObject>() && !obj->is<UnboxedArrayObject>())
|
||||
return false;
|
||||
|
||||
if (obj->is<TypedArrayObject>() &&
|
||||
obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(idval.isInt32() || idval.isString());
|
||||
|
||||
// Don't emit a stub if the access is out of bounds. We make to make
|
||||
@@ -4092,6 +4102,9 @@ GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm,
|
||||
// Decide to what type index the stub should be optimized
|
||||
Register tmpReg = output.scratchReg().gpr();
|
||||
MOZ_ASSERT(tmpReg != InvalidReg);
|
||||
if (array->is<TypedArrayObject>())
|
||||
GuardResizableOrGrowableTypedArray(masm, object, tmpReg, &failures);
|
||||
|
||||
Register indexReg = tmpReg;
|
||||
if (idval.isString()) {
|
||||
MOZ_ASSERT(GetIndexFromString(idval.toString()) != UINT32_MAX);
|
||||
@@ -4575,6 +4588,7 @@ GenerateSetTypedArrayElement(JSContext* cx, MacroAssembler& masm, IonCache::Stub
|
||||
if (!shape)
|
||||
return false;
|
||||
masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
|
||||
GuardResizableOrGrowableTypedArray(masm, object, temp, &failures);
|
||||
|
||||
// Ensure the index is an int32.
|
||||
Register indexReg;
|
||||
@@ -4661,6 +4675,9 @@ SetPropertyIC::tryAttachTypedArrayElement(JSContext* cx, HandleScript outerScrip
|
||||
if (!IsTypedArrayElementSetInlineable(obj, idval, val))
|
||||
return true;
|
||||
|
||||
if (obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
|
||||
return true;
|
||||
|
||||
*emitted = true;
|
||||
|
||||
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
|
||||
|
||||
@@ -365,13 +365,9 @@ IonBuilder::inlineNativeGetter(CallInfo& callInfo, JSFunction* target)
|
||||
|
||||
// Try to optimize typed array lengths.
|
||||
if (TypedArrayObject::isOriginalLengthGetter(native)) {
|
||||
Scalar::Type type = thisTypes->getTypedArrayType(constraints());
|
||||
if (type == Scalar::MaxTypedArrayViewType)
|
||||
return InliningStatus_NotInlined;
|
||||
|
||||
MInstruction* length = addTypedArrayLength(thisArg);
|
||||
current->push(length);
|
||||
return InliningStatus_Inlined;
|
||||
// RAB/GSAB views can have dynamic length or temporarily become
|
||||
// out-of-bounds. Let the property IC/VM path handle the getter.
|
||||
return InliningStatus_NotInlined;
|
||||
}
|
||||
|
||||
// Try to optimize RegExp getters.
|
||||
@@ -2480,21 +2476,10 @@ IsTypedArrayObject(CompilerConstraintList* constraints, MDefinition* def)
|
||||
IonBuilder::InliningStatus
|
||||
IonBuilder::inlinePossiblyWrappedTypedArrayLength(CallInfo& callInfo)
|
||||
{
|
||||
MOZ_ASSERT(!callInfo.constructing());
|
||||
MOZ_ASSERT(callInfo.argc() == 1);
|
||||
if (callInfo.getArg(0)->type() != MIRType::Object)
|
||||
return InliningStatus_NotInlined;
|
||||
if (getInlineReturnType() != MIRType::Int32)
|
||||
return InliningStatus_NotInlined;
|
||||
(void) callInfo;
|
||||
|
||||
if (!IsTypedArrayObject(constraints(), callInfo.getArg(0)))
|
||||
return InliningStatus_NotInlined;
|
||||
|
||||
MInstruction* length = addTypedArrayLength(callInfo.getArg(0));
|
||||
current->push(length);
|
||||
|
||||
callInfo.setImplicitlyUsedUnchecked();
|
||||
return InliningStatus_Inlined;
|
||||
// RAB/GSAB views require dynamic length semantics.
|
||||
return InliningStatus_NotInlined;
|
||||
}
|
||||
|
||||
IonBuilder::InliningStatus
|
||||
@@ -3251,45 +3236,14 @@ bool
|
||||
IonBuilder::atomicsMeetsPreconditions(CallInfo& callInfo, Scalar::Type* arrayType,
|
||||
bool* requiresTagCheck, AtomicCheckResult checkResult)
|
||||
{
|
||||
if (!JitSupportsAtomics())
|
||||
return false;
|
||||
(void) callInfo;
|
||||
(void) arrayType;
|
||||
(void) requiresTagCheck;
|
||||
(void) checkResult;
|
||||
|
||||
if (callInfo.getArg(0)->type() != MIRType::Object)
|
||||
return false;
|
||||
|
||||
if (callInfo.getArg(1)->type() != MIRType::Int32)
|
||||
return false;
|
||||
|
||||
// Ensure that the first argument is a TypedArray that maps shared
|
||||
// memory.
|
||||
//
|
||||
// Then check both that the element type is something we can
|
||||
// optimize and that the return type is suitable for that element
|
||||
// type.
|
||||
|
||||
TemporaryTypeSet* arg0Types = callInfo.getArg(0)->resultTypeSet();
|
||||
if (!arg0Types)
|
||||
return false;
|
||||
|
||||
TemporaryTypeSet::TypedArraySharedness sharedness;
|
||||
*arrayType = arg0Types->getTypedArrayType(constraints(), &sharedness);
|
||||
*requiresTagCheck = sharedness != TemporaryTypeSet::KnownShared;
|
||||
switch (*arrayType) {
|
||||
case Scalar::Int8:
|
||||
case Scalar::Uint8:
|
||||
case Scalar::Int16:
|
||||
case Scalar::Uint16:
|
||||
case Scalar::Int32:
|
||||
return checkResult == DontCheckAtomicResult || getInlineReturnType() == MIRType::Int32;
|
||||
case Scalar::Uint32:
|
||||
// Bug 1077305: it would be attractive to allow inlining even
|
||||
// if the inline return type is Int32, which it will frequently
|
||||
// be.
|
||||
return checkResult == DontCheckAtomicResult || getInlineReturnType() == MIRType::Double;
|
||||
default:
|
||||
// Excludes floating types and Uint8Clamped.
|
||||
return false;
|
||||
}
|
||||
// Atomics bounds checks through addTypedArrayLengthAndData assume stable
|
||||
// SharedArrayBuffer lengths. Avoid this path now that growable SAB exists.
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
+8
-18
@@ -5505,25 +5505,15 @@ jit::ElementAccessIsTypedArray(CompilerConstraintList* constraints,
|
||||
MDefinition* obj, MDefinition* id,
|
||||
Scalar::Type* arrayType)
|
||||
{
|
||||
if (obj->mightBeType(MIRType::String))
|
||||
return false;
|
||||
(void) constraints;
|
||||
(void) obj;
|
||||
(void) id;
|
||||
(void) arrayType;
|
||||
|
||||
if (id->type() != MIRType::Int32 && id->type() != MIRType::Double)
|
||||
return false;
|
||||
|
||||
TemporaryTypeSet* types = obj->resultTypeSet();
|
||||
if (!types)
|
||||
return false;
|
||||
|
||||
*arrayType = types->getTypedArrayType(constraints);
|
||||
|
||||
// FIXME: https://bugzil.la/1536699
|
||||
if (*arrayType == Scalar::MaxTypedArrayViewType ||
|
||||
Scalar::isBigIntType(*arrayType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// Resizable ArrayBuffer and growable SharedArrayBuffer views need dynamic
|
||||
// length/out-of-bounds handling. This older MIR path assumes stable
|
||||
// typed-array length/data slots, so keep element accesses on IC/VM paths.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -3608,6 +3608,17 @@ CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Labe
|
||||
masm.branch32(Assembler::NotEqual, AbsoluteAddress(address), Imm32(0), failure);
|
||||
}
|
||||
|
||||
void
|
||||
GuardResizableOrGrowableTypedArray(MacroAssembler& masm, Register obj, Register scratch,
|
||||
Label* failure)
|
||||
{
|
||||
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
|
||||
masm.branchTest32(Assembler::NonZero,
|
||||
Address(scratch, ObjectElements::offsetOfFlags()),
|
||||
Imm32(ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER),
|
||||
failure);
|
||||
}
|
||||
|
||||
void
|
||||
LoadTypedThingData(MacroAssembler& masm, TypedThingLayout layout, Register obj, Register result)
|
||||
{
|
||||
|
||||
@@ -2302,6 +2302,10 @@ CheckDOMProxyExpandoDoesNotShadow(JSContext* cx, MacroAssembler& masm, Register
|
||||
void
|
||||
CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Label* failure);
|
||||
|
||||
void
|
||||
GuardResizableOrGrowableTypedArray(MacroAssembler& masm, Register obj, Register scratch,
|
||||
Label* failure);
|
||||
|
||||
MOZ_MUST_USE bool
|
||||
DoCallNativeGetter(JSContext* cx, HandleFunction callee, HandleObject obj,
|
||||
MutableHandleValue result);
|
||||
|
||||
@@ -81,6 +81,10 @@ MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE, 0, JSEXN_TYPEERR, "reduce of empty array
|
||||
MSG_DEF(JSMSG_UNEXPECTED_TYPE, 2, JSEXN_TYPEERR, "{0} is {1}")
|
||||
MSG_DEF(JSMSG_MISSING_FUN_ARG, 2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")
|
||||
MSG_DEF(JSMSG_NOT_NONNULL_OBJECT, 1, JSEXN_TYPEERR, "{0} is not a non-null object")
|
||||
MSG_DEF(JSMSG_NOT_WEAKREF_TARGET, 0, JSEXN_TYPEERR, "WeakRef target must be an object or a non-registered symbol")
|
||||
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_TARGET, 0, JSEXN_TYPEERR, "FinalizationRegistry target must be an object or a non-registered symbol")
|
||||
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_HELD_VALUE, 0, JSEXN_TYPEERR, "FinalizationRegistry held value must not be the target")
|
||||
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN, 0, JSEXN_TYPEERR, "FinalizationRegistry unregister token must be an object or a non-registered symbol")
|
||||
MSG_DEF(JSMSG_SET_NON_OBJECT_RECEIVER, 1, JSEXN_TYPEERR, "can't assign to properties of {0}: not an object")
|
||||
MSG_DEF(JSMSG_INVALID_DESCRIPTOR, 0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified")
|
||||
MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE, 1, JSEXN_TYPEERR, "{0}: Object is not extensible")
|
||||
@@ -548,6 +552,8 @@ MSG_DEF(JSMSG_TOO_LONG_ARRAY, 0, JSEXN_TYPEERR, "Too long array")
|
||||
|
||||
// Typed array
|
||||
MSG_DEF(JSMSG_BAD_INDEX, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
|
||||
MSG_DEF(JSMSG_ARRAYBUFFER_NOT_RESIZABLE, 0, JSEXN_TYPEERR, "ArrayBuffer is not resizable")
|
||||
MSG_DEF(JSMSG_ARRAYBUFFER_CANNOT_DETACH, 0, JSEXN_TYPEERR, "ArrayBuffer cannot be detached")
|
||||
MSG_DEF(JSMSG_NON_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected ArrayBuffer, but species constructor returned non-ArrayBuffer")
|
||||
MSG_DEF(JSMSG_SAME_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different ArrayBuffer, but species constructor returned same ArrayBuffer")
|
||||
MSG_DEF(JSMSG_SHORT_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected ArrayBuffer with at least {0} bytes, but species constructor returns ArrayBuffer with {1} bytes")
|
||||
@@ -555,12 +561,15 @@ MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS, 0, JSEXN_TYPEERR, "invalid arguments")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG,1, JSEXN_RANGEERR, "argument {0} must be >= 0")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_DETACHED, 0, JSEXN_TYPEERR, "attempting to access detached ArrayBuffer")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS, 0, JSEXN_RANGEERR, "attempting to construct out-of-bounds TypedArray on ArrayBuffer")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS, 0, JSEXN_TYPEERR, "attempting to access out-of-bounds TypedArray")
|
||||
MSG_DEF(JSMSG_DATA_VIEW_OUT_OF_BOUNDS, 0, JSEXN_TYPEERR, "attempting to access out-of-bounds DataView")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_CALL_OR_CONSTRUCT, 1, JSEXN_TYPEERR, "cannot directly {0} builtin %TypedArray%")
|
||||
MSG_DEF(JSMSG_NON_TYPED_ARRAY_RETURNED, 0, JSEXN_TYPEERR, "constructor didn't return TypedArray object")
|
||||
MSG_DEF(JSMSG_SHORT_TYPED_ARRAY_RETURNED, 2, JSEXN_TYPEERR, "expected TypedArray of at least length {0}, but constructor returned TypedArray of length {1}")
|
||||
|
||||
// Shared array buffer
|
||||
MSG_DEF(JSMSG_SHARED_ARRAY_BAD_LENGTH, 0, JSEXN_RANGEERR, "length argument out of range")
|
||||
MSG_DEF(JSMSG_SHARED_ARRAY_NOT_GROWABLE, 0, JSEXN_TYPEERR, "SharedArrayBuffer is not growable")
|
||||
MSG_DEF(JSMSG_NON_SHARED_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected SharedArrayBuffer, but species constructor returned non-SharedArrayBuffer")
|
||||
MSG_DEF(JSMSG_SAME_SHARED_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different SharedArrayBuffer, but species constructor returned same SharedArrayBuffer")
|
||||
MSG_DEF(JSMSG_SHORT_SHARED_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected SharedArrayBuffer with at least {0} bytes, but species constructor returns SharedArrayBuffer with {1} bytes")
|
||||
|
||||
@@ -21,6 +21,64 @@ struct MyHeap
|
||||
|
||||
BEGIN_TEST(testGCWeakRef)
|
||||
{
|
||||
CHECK(cx->options().weakRefs());
|
||||
cx->options().setWeakRefs(false);
|
||||
CHECK(cx->options().weakRefs());
|
||||
|
||||
JS::RootedValue v(cx);
|
||||
EXEC("var weakRefTarget = { x: 42 };\n"
|
||||
"var weakRef = new WeakRef(weakRefTarget);\n"
|
||||
"weakRefTarget = null;\n");
|
||||
|
||||
// The constructor keeps the target alive until the host clears kept
|
||||
// objects. This must remain true even after callers try the old disabled
|
||||
// option path above.
|
||||
JS_GC(cx);
|
||||
JS_GC(cx);
|
||||
EVAL("weakRef.deref()", &v);
|
||||
CHECK(v.isObject());
|
||||
|
||||
JS::ClearWeakRefKeptObjects(cx);
|
||||
v = JS::UndefinedValue();
|
||||
JS_GC(cx);
|
||||
JS_GC(cx);
|
||||
EVAL("weakRef.deref()", &v);
|
||||
CHECK(v.isUndefined());
|
||||
|
||||
EXEC("var weakRefSymbol = Symbol('weak-ref-symbol');\n"
|
||||
"var symbolRef = new WeakRef(weakRefSymbol);\n"
|
||||
"if (symbolRef.deref() !== weakRefSymbol)\n"
|
||||
" throw new Error('WeakRef must accept unique symbols');\n"
|
||||
"var registeredSymbolRejected = false;\n"
|
||||
"try {\n"
|
||||
" new WeakRef(Symbol.for('weak-ref-symbol'));\n"
|
||||
"} catch (e) {\n"
|
||||
" registeredSymbolRejected = e instanceof TypeError;\n"
|
||||
"}\n"
|
||||
"if (!registeredSymbolRejected)\n"
|
||||
" throw new Error('WeakRef must reject registered symbols');\n");
|
||||
|
||||
EXEC("var keptTarget = { y: 7 };\n"
|
||||
"var keptRef = new WeakRef(keptTarget);\n");
|
||||
JS::ClearWeakRefKeptObjects(cx);
|
||||
EXEC("var keptResult = keptRef.deref();\n"
|
||||
"if (keptResult !== keptTarget)\n"
|
||||
" throw new Error('WeakRef deref must return the target');\n"
|
||||
"keptTarget = null;\n"
|
||||
"keptResult = null;\n");
|
||||
|
||||
JS_GC(cx);
|
||||
JS_GC(cx);
|
||||
EVAL("keptRef.deref()", &v);
|
||||
CHECK(v.isObject());
|
||||
|
||||
JS::ClearWeakRefKeptObjects(cx);
|
||||
v = JS::UndefinedValue();
|
||||
JS_GC(cx);
|
||||
JS_GC(cx);
|
||||
EVAL("keptRef.deref()", &v);
|
||||
CHECK(v.isUndefined());
|
||||
|
||||
// Create an object and add a property to it so that we can read the
|
||||
// property back later to verify that object internals are not garbage.
|
||||
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
|
||||
@@ -38,7 +96,6 @@ BEGIN_TEST(testGCWeakRef)
|
||||
// references.
|
||||
CHECK(heap.get().weak.unbarrieredGet() != nullptr);
|
||||
obj = heap.get().weak;
|
||||
JS::RootedValue v(cx);
|
||||
CHECK(JS_GetProperty(cx, obj, "x", &v));
|
||||
CHECK(v.isInt32());
|
||||
CHECK(v.toInt32() == 42);
|
||||
@@ -61,4 +118,3 @@ BEGIN_TEST(testGCWeakRef)
|
||||
return true;
|
||||
}
|
||||
END_TEST(testGCWeakRef)
|
||||
|
||||
|
||||
@@ -627,6 +627,15 @@ JS::InitSelfHostedCode(JSContext* cx)
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS::ClearWeakRefKeptObjects(JSContext* cx)
|
||||
{
|
||||
MOZ_ASSERT(cx);
|
||||
MOZ_ASSERT(!cx->runtime()->isHeapBusy());
|
||||
|
||||
cx->runtime()->clearWeakRefKeptObjects();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(const char*)
|
||||
JS_GetImplementationVersion(void)
|
||||
{
|
||||
|
||||
+10
-5
@@ -999,7 +999,7 @@ class JS_PUBLIC_API(ContextOptions) {
|
||||
strictMode_(false),
|
||||
extraWarnings_(false),
|
||||
arrayProtoValues_(true),
|
||||
weakRefs_(false)
|
||||
streams_(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1139,13 +1139,12 @@ class JS_PUBLIC_API(ContextOptions) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool weakRefs() const { return weakRefs_; }
|
||||
bool weakRefs() const { return true; }
|
||||
ContextOptions& setWeakRefs(bool flag) {
|
||||
weakRefs_ = flag;
|
||||
(void) flag;
|
||||
return *this;
|
||||
}
|
||||
ContextOptions& toggleWeakRefs() {
|
||||
weakRefs_ = !weakRefs_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -1166,7 +1165,6 @@ class JS_PUBLIC_API(ContextOptions) {
|
||||
bool extraWarnings_ : 1;
|
||||
bool arrayProtoValues_ : 1;
|
||||
bool streams_ : 1;
|
||||
bool weakRefs_ : 1;
|
||||
};
|
||||
|
||||
JS_PUBLIC_API(ContextOptions&)
|
||||
@@ -1187,6 +1185,13 @@ InitSelfHostedCode(JSContext* cx);
|
||||
JS_PUBLIC_API(void)
|
||||
AssertObjectBelongsToCurrentThread(JSObject* obj);
|
||||
|
||||
/**
|
||||
* Clear objects and symbols that WeakRef.prototype.deref kept alive for the
|
||||
* current synchronous JavaScript execution.
|
||||
*/
|
||||
JS_PUBLIC_API(void)
|
||||
ClearWeakRefKeptObjects(JSContext* cx);
|
||||
|
||||
} /* namespace JS */
|
||||
|
||||
extern JS_PUBLIC_API(const char*)
|
||||
|
||||
@@ -1748,6 +1748,9 @@ JS_IsFloat64Array(JSObject* obj);
|
||||
extern JS_FRIEND_API(bool)
|
||||
JS_GetTypedArraySharedness(JSObject* obj);
|
||||
|
||||
extern JS_FRIEND_API(uint32_t)
|
||||
JS_GetTypedArrayLength(JSObject* obj);
|
||||
|
||||
/*
|
||||
* Test for specific typed array types (ArrayBufferView subtypes) and return
|
||||
* the unwrapped object if so, else nullptr. Never throws.
|
||||
@@ -1814,8 +1817,7 @@ inline void \
|
||||
Get ## Type ## ArrayLengthAndData(JSObject* obj, uint32_t* length, bool* isSharedMemory, type** data) \
|
||||
{ \
|
||||
MOZ_ASSERT(GetObjectClass(obj) == detail::Type ## ArrayClassPtr); \
|
||||
const JS::Value& lenSlot = GetReservedSlot(obj, detail::TypedArrayLengthSlot); \
|
||||
*length = mozilla::AssertedCast<uint32_t>(lenSlot.toInt32()); \
|
||||
*length = JS_GetTypedArrayLength(obj); \
|
||||
*isSharedMemory = JS_GetTypedArraySharedness(obj); \
|
||||
*data = static_cast<type*>(GetObjectPrivate(obj)); \
|
||||
}
|
||||
|
||||
@@ -211,6 +211,7 @@
|
||||
# include "jswin.h"
|
||||
#endif
|
||||
|
||||
#include "builtin/FinalizationRegistryObject.h"
|
||||
#include "gc/FindSCCs.h"
|
||||
#include "gc/GCInternals.h"
|
||||
#include "gc/GCTrace.h"
|
||||
@@ -4029,6 +4030,8 @@ GCRuntime::markWeakReferences(gcstats::Phase phase)
|
||||
}
|
||||
MOZ_ASSERT(marker.isDrained());
|
||||
|
||||
traceFinalizationRegistryWeakRefs<ZoneIterT>();
|
||||
|
||||
marker.leaveWeakMarkingMode();
|
||||
}
|
||||
|
||||
@@ -4038,6 +4041,33 @@ GCRuntime::markWeakReferencesInCurrentGroup(gcstats::Phase phase)
|
||||
markWeakReferences<GCZoneGroupIter>(phase);
|
||||
}
|
||||
|
||||
template <class ZoneIterT>
|
||||
void
|
||||
GCRuntime::traceFinalizationRegistryWeakRefs()
|
||||
{
|
||||
for (ZoneIterT zone(rt); !zone.done(); zone.next()) {
|
||||
for (size_t i = 0; i < zone->finalizationRegistries.length(); i++) {
|
||||
WeakRef<JSObject*>& registry = zone->finalizationRegistries[i];
|
||||
if (registry.unbarrieredGet())
|
||||
TraceWeakEdge(&marker, ®istry, "FinalizationRegistry registry");
|
||||
}
|
||||
}
|
||||
|
||||
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
|
||||
for (size_t i = 0; i < zone->finalizationRegistries.length(); i++) {
|
||||
JSObject* registry = zone->finalizationRegistries[i].unbarrieredGet();
|
||||
if (registry && registry->is<FinalizationRegistryObject>())
|
||||
registry->as<FinalizationRegistryObject>().traceWeakEdgesForCollectedZones(&marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::traceFinalizationRegistryWeakRefsInCurrentGroup()
|
||||
{
|
||||
traceFinalizationRegistryWeakRefs<GCZoneGroupIter>();
|
||||
}
|
||||
|
||||
template <class ZoneIterT, class CompartmentIterT>
|
||||
void
|
||||
GCRuntime::markGrayReferences(gcstats::Phase phase)
|
||||
@@ -4749,6 +4779,8 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
oomUnsafe.crash("clearing weak keys in beginSweepingZoneGroup()");
|
||||
}
|
||||
|
||||
sweepFinalizationRegistries();
|
||||
|
||||
{
|
||||
gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START);
|
||||
callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_START);
|
||||
@@ -4904,6 +4936,23 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::sweepFinalizationRegistries()
|
||||
{
|
||||
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
|
||||
for (size_t i = 0; i < zone->finalizationRegistries.length();) {
|
||||
JSObject* obj = zone->finalizationRegistries[i].unbarrieredGet();
|
||||
if (!obj || !obj->is<FinalizationRegistryObject>()) {
|
||||
zone->finalizationRegistries.erase(zone->finalizationRegistries.begin() + i);
|
||||
continue;
|
||||
}
|
||||
|
||||
obj->as<FinalizationRegistryObject>().sweepAfterGC(rt);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::endSweepingZoneGroup()
|
||||
{
|
||||
@@ -6095,6 +6144,13 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
|
||||
repeat = (poked && cleanUpEverything) || wasReset || repeatForDeadZone;
|
||||
} while (repeat);
|
||||
|
||||
if (rt->isBeingDestroyed()) {
|
||||
rt->clearFinalizationRegistryCleanupJobs();
|
||||
} else if (!rt->drainFinalizationRegistryCleanupJobs(rt->contextFromMainThread())) {
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
oomUnsafe.crash("draining FinalizationRegistry cleanup jobs");
|
||||
}
|
||||
|
||||
if (reason == JS::gcreason::COMPARTMENT_REVIVED)
|
||||
maybeDoCycleCollection();
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
IF_SAB(real,imaginary)(SharedArrayBuffer, InitViaClassSpec, OCLASP(SharedArrayBuffer)) \
|
||||
IF_INTL(real,imaginary) (Intl, InitIntlClass, CLASP(Intl)) \
|
||||
IF_BDATA(real,imaginary)(TypedObject, InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \
|
||||
real(FinalizationRegistry, InitFinalizationRegistryClass, OCLASP(FinalizationRegistry)) \
|
||||
real(Reflect, InitReflect, nullptr) \
|
||||
real(WeakSet, InitWeakSetClass, OCLASP(WeakSet)) \
|
||||
real(TypedArray, InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \
|
||||
|
||||
@@ -3305,6 +3305,8 @@ static const JSFunctionSpec string_methods[] = {
|
||||
JS_SELF_HOSTED_FN("toLocaleUpperCase", "String_toLocaleUpperCase", 0,0),
|
||||
JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
|
||||
JS_SELF_HOSTED_FN("repeat", "String_repeat", 1,0),
|
||||
JS_SELF_HOSTED_FN("isWellFormed", "String_isWellFormed", 0,0),
|
||||
JS_SELF_HOSTED_FN("toWellFormed", "String_toWellFormed", 0,0),
|
||||
JS_FN("normalize", str_normalize, 0,0),
|
||||
|
||||
/* Perl-ish methods (search is actually Python-esque). */
|
||||
|
||||
@@ -16,11 +16,25 @@
|
||||
#include "gc/Marking.h"
|
||||
#include "gc/StoreBuffer.h"
|
||||
#include "js/HashTable.h"
|
||||
#include "vm/Symbol.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
class WeakMapBase;
|
||||
|
||||
template <>
|
||||
struct MovableCellHasher<JS::Symbol*>
|
||||
{
|
||||
using Key = JS::Symbol*;
|
||||
using Lookup = JS::Symbol*;
|
||||
|
||||
static bool hasHash(const Lookup& l) { return true; }
|
||||
static bool ensureHash(const Lookup& l) { return true; }
|
||||
static HashNumber hash(const Lookup& l) { return l->hash(); }
|
||||
static bool match(const Key& k, const Lookup& l) { return k == l; }
|
||||
static void rekey(Key& k, const Key& newKey) { k = newKey; }
|
||||
};
|
||||
|
||||
// A subclass template of js::HashMap whose keys and values may be garbage-collected. When
|
||||
// a key is collected, the table entry disappears, dropping its reference to the value.
|
||||
//
|
||||
@@ -296,9 +310,16 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSObject* getDelegate(JS::Symbol* sym) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
void exposeGCThingToActiveJS(const JS::Value& v) const { JS::ExposeValueToActiveJS(v); }
|
||||
void exposeGCThingToActiveJS(JSObject* obj) const { JS::ExposeObjectToActiveJS(obj); }
|
||||
void exposeGCThingToActiveJS(JS::Symbol* sym) const {
|
||||
gc::ExposeGCThingToActiveJS(JS::GCCellPtr(sym));
|
||||
}
|
||||
|
||||
bool keyNeedsMark(JSObject* key) const {
|
||||
JSObject* delegate = getDelegate(key);
|
||||
@@ -313,6 +334,10 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool keyNeedsMark(JS::Symbol* sym) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool findZoneEdges() override {
|
||||
// This is overridden by ObjectValueMap.
|
||||
return true;
|
||||
@@ -378,6 +403,9 @@ WeakMap_set(JSContext* cx, unsigned argc, Value* vp);
|
||||
extern bool
|
||||
WeakMap_delete(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
extern bool
|
||||
SetWeakMapEntryValue(JSContext* cx, HandleObject mapObj, HandleValue key, HandleValue val);
|
||||
|
||||
extern JSObject*
|
||||
InitWeakMapClass(JSContext* cx, HandleObject obj);
|
||||
|
||||
@@ -394,6 +422,16 @@ class ObjectValueMap : public WeakMap<HeapPtr<JSObject*>, HeapPtr<Value>,
|
||||
virtual bool findZoneEdges();
|
||||
};
|
||||
|
||||
class SymbolValueMap : public WeakMap<HeapPtr<JS::Symbol*>, HeapPtr<Value>,
|
||||
MovableCellHasher<HeapPtr<JS::Symbol*>>>
|
||||
{
|
||||
public:
|
||||
SymbolValueMap(JSContext* cx, JSObject* obj)
|
||||
: WeakMap<HeapPtr<JS::Symbol*>, HeapPtr<Value>,
|
||||
MovableCellHasher<HeapPtr<JS::Symbol*>>>(cx, obj)
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
// Generic weak map for mapping objects to other objects.
|
||||
class ObjectWeakMap
|
||||
|
||||
@@ -120,6 +120,7 @@ EXPORTS.js += [
|
||||
main_deunified_sources = [
|
||||
'builtin/AtomicsObject.cpp',
|
||||
'builtin/Eval.cpp',
|
||||
'builtin/FinalizationRegistryObject.cpp',
|
||||
'builtin/intl/Collator.cpp',
|
||||
'builtin/intl/CommonFunctions.cpp',
|
||||
'builtin/intl/DateTimeFormat.cpp',
|
||||
|
||||
+4
-1
@@ -676,7 +676,9 @@ RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
|
||||
AnalyzeEntrainedVariables(cx, script);
|
||||
#endif
|
||||
if (!compileOnly) {
|
||||
if (!JS_ExecuteScript(cx, script))
|
||||
bool ok = JS_ExecuteScript(cx, script);
|
||||
JS::ClearWeakRefKeptObjects(cx);
|
||||
if (!ok)
|
||||
return false;
|
||||
int64_t t2 = PRMJ_Now() - t1;
|
||||
if (printTiming)
|
||||
@@ -857,6 +859,7 @@ DrainJobQueue(JSContext* cx)
|
||||
}
|
||||
sc->jobQueue.clear();
|
||||
sc->drainingJobQueue = false;
|
||||
JS::ClearWeakRefKeptObjects(cx);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
// Manual WeakRef smoke tests for browser console use.
|
||||
//
|
||||
// Usage:
|
||||
// 1) Optionally flip `javascript.options.weakrefs` in about:config and reload.
|
||||
// 2) Paste this file into the browser console (or load via file://) and run:
|
||||
// 1) Paste this file into the browser console (or load via file://) and run:
|
||||
// runWeakRefManual();
|
||||
// 3) Inspect the returned array of results; no throws or crashes are expected.
|
||||
// 2) Inspect the returned array of results; no throws or crashes are expected.
|
||||
//
|
||||
// Notes:
|
||||
// - When the pref is ON, deref() should return the target.
|
||||
// - When the pref is OFF, the stub traces strongly so deref() should also
|
||||
// return the target.
|
||||
// - WeakRef is always enabled. Once all strong references are gone, a full GC
|
||||
// may clear the referent and make deref() return undefined.
|
||||
|
||||
function runWeakRefManual() {
|
||||
const results = [];
|
||||
@@ -21,7 +19,7 @@ function runWeakRefManual() {
|
||||
log("initial deref tag", ref.deref()?.tag);
|
||||
log("repeat deref identity", ref.deref() === target);
|
||||
|
||||
// Clear the strong reference; the WeakRef should still be able to return it.
|
||||
// Clear the strong reference. A future full GC may clear the WeakRef.
|
||||
target = null;
|
||||
|
||||
const afterClear = ref.deref();
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// |reftest| skip-if(!ArrayBuffer.prototype.transfer)
|
||||
|
||||
var fixed = new ArrayBuffer(4);
|
||||
assertEq(fixed.byteLength, 4);
|
||||
assertEq(fixed.maxByteLength, 4);
|
||||
assertEq(fixed.resizable, false);
|
||||
assertEq(fixed.detached, false);
|
||||
new Uint8Array(fixed).set([1, 2, 3, 4]);
|
||||
fixed.extra = 17;
|
||||
assertEq(Array.from(new Uint8Array(fixed)).join(","), "1,2,3,4");
|
||||
assertThrowsInstanceOf(() => fixed.resize(2), TypeError);
|
||||
assertEq(ArrayBuffer.prototype.transfer.length, 0);
|
||||
assertEq(ArrayBuffer.prototype.transferToFixedLength.length, 0);
|
||||
var resizeArgumentConverted = false;
|
||||
assertThrowsInstanceOf(() => fixed.resize({ valueOf() { resizeArgumentConverted = true; return 1; } }),
|
||||
TypeError);
|
||||
assertEq(resizeArgumentConverted, false);
|
||||
|
||||
var resizable = new ArrayBuffer(4, { maxByteLength: 8 });
|
||||
assertEq(resizable.byteLength, 4);
|
||||
assertEq(resizable.maxByteLength, 8);
|
||||
assertEq(resizable.resizable, true);
|
||||
|
||||
var bytes = new Uint8Array(resizable);
|
||||
bytes[0] = 11;
|
||||
bytes[3] = 44;
|
||||
resizable.resize(6);
|
||||
assertEq(resizable.byteLength, 6);
|
||||
assertEq(new Uint8Array(resizable)[0], 11);
|
||||
assertEq(new Uint8Array(resizable)[3], 44);
|
||||
assertEq(new Uint8Array(resizable)[4], 0);
|
||||
assertThrowsInstanceOf(() => resizable.resize(9), RangeError);
|
||||
|
||||
var source = new ArrayBuffer(4);
|
||||
var sourceBytes = new Uint8Array(source);
|
||||
sourceBytes[0] = 1;
|
||||
sourceBytes[1] = 2;
|
||||
var sourceView = new Uint8Array(source);
|
||||
var moved = source.transfer(6);
|
||||
assertEq(source.detached, true);
|
||||
assertEq(source.byteLength, 0);
|
||||
assertEq(source.maxByteLength, 0);
|
||||
assertEq(sourceView.length, 0);
|
||||
assertEq(moved.byteLength, 6);
|
||||
assertEq(moved.resizable, false);
|
||||
assertEq(moved.maxByteLength, 6);
|
||||
assertEq(new Uint8Array(moved)[0], 1);
|
||||
assertEq(new Uint8Array(moved)[1], 2);
|
||||
assertEq(new Uint8Array(moved)[4], 0);
|
||||
|
||||
var resizableSource = new ArrayBuffer(4, { maxByteLength: 8 });
|
||||
new Uint8Array(resizableSource)[0] = 7;
|
||||
var resizableMoved = resizableSource.transfer();
|
||||
assertEq(resizableSource.detached, true);
|
||||
assertEq(resizableSource.resizable, true);
|
||||
assertEq(resizableMoved.byteLength, 4);
|
||||
assertEq(resizableMoved.maxByteLength, 8);
|
||||
assertEq(resizableMoved.resizable, true);
|
||||
assertEq(new Uint8Array(resizableMoved)[0], 7);
|
||||
|
||||
var fixedMoved = resizableMoved.transferToFixedLength(10);
|
||||
assertEq(resizableMoved.detached, true);
|
||||
assertEq(fixedMoved.byteLength, 10);
|
||||
assertEq(fixedMoved.maxByteLength, 10);
|
||||
assertEq(fixedMoved.resizable, false);
|
||||
assertEq(new Uint8Array(fixedMoved)[0], 7);
|
||||
|
||||
var sliceSource = new ArrayBuffer(8, { maxByteLength: 8 });
|
||||
var sliceSourceBytes = new Uint8Array(sliceSource);
|
||||
for (var i = 0; i < sliceSourceBytes.length; i++)
|
||||
sliceSourceBytes[i] = i + 1;
|
||||
|
||||
sliceSource.constructor = {
|
||||
[Symbol.species]: function(byteLength) {
|
||||
sliceSource.resize(4);
|
||||
return new ArrayBuffer(byteLength);
|
||||
}
|
||||
};
|
||||
|
||||
var sliced = sliceSource.slice(2, 8);
|
||||
var slicedBytes = new Uint8Array(sliced);
|
||||
assertEq(sliced.byteLength, 6);
|
||||
assertEq(slicedBytes[0], 3);
|
||||
assertEq(slicedBytes[1], 4);
|
||||
assertEq(slicedBytes[2], 0);
|
||||
assertEq(slicedBytes[5], 0);
|
||||
|
||||
sliceSource.resize(8);
|
||||
for (var i = 0; i < sliceSourceBytes.length; i++)
|
||||
sliceSourceBytes[i] = i + 1;
|
||||
var zeroCopied = sliceSource.slice(6, 8);
|
||||
assertEq(zeroCopied.byteLength, 2);
|
||||
assertEq(new Uint8Array(zeroCopied)[0], 0);
|
||||
|
||||
assertThrowsInstanceOf(() => new ArrayBuffer(4, { maxByteLength: 3 }), RangeError);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
||||
@@ -0,0 +1,218 @@
|
||||
// |reftest| skip-if(!this.SharedArrayBuffer)
|
||||
|
||||
var rab = new ArrayBuffer(4, { maxByteLength: 16 });
|
||||
var tracking = new Uint8Array(rab);
|
||||
var fixed = new Uint8Array(rab, 1, 2);
|
||||
var bytes = new Uint8Array(rab);
|
||||
|
||||
bytes[1] = 11;
|
||||
bytes[2] = 22;
|
||||
|
||||
assertEq(tracking.length, 4);
|
||||
assertEq(tracking.byteLength, 4);
|
||||
assertEq(tracking.byteOffset, 0);
|
||||
assertEq(fixed.length, 2);
|
||||
assertEq(fixed.byteLength, 2);
|
||||
assertEq(fixed.byteOffset, 1);
|
||||
|
||||
rab.resize(2);
|
||||
assertEq(tracking.length, 2);
|
||||
assertEq(tracking.byteLength, 2);
|
||||
assertEq(fixed.length, 0);
|
||||
assertEq(fixed.byteLength, 0);
|
||||
assertEq(fixed.byteOffset, 0);
|
||||
assertEq(fixed[0], undefined);
|
||||
|
||||
rab.resize(8);
|
||||
assertEq(tracking.length, 8);
|
||||
assertEq(tracking.byteLength, 8);
|
||||
assertEq(fixed.length, 2);
|
||||
assertEq(fixed.byteLength, 2);
|
||||
assertEq(fixed.byteOffset, 1);
|
||||
assertEq(fixed[0], 11);
|
||||
assertEq(fixed[1], 0);
|
||||
tracking[6] = 66;
|
||||
assertEq(new Uint8Array(rab)[6], 66);
|
||||
|
||||
var dv = new DataView(rab, 4);
|
||||
assertEq(dv.byteOffset, 4);
|
||||
assertEq(dv.byteLength, 4);
|
||||
dv.setUint8(0, 44);
|
||||
assertEq(tracking[4], 44);
|
||||
|
||||
rab.resize(3);
|
||||
assertThrowsInstanceOf(() => dv.byteOffset, TypeError);
|
||||
assertThrowsInstanceOf(() => dv.byteLength, TypeError);
|
||||
assertThrowsInstanceOf(() => dv.getUint8(0), TypeError);
|
||||
|
||||
rab.resize(6);
|
||||
assertEq(dv.byteOffset, 4);
|
||||
assertEq(dv.byteLength, 2);
|
||||
assertEq(dv.getUint8(0), 0);
|
||||
|
||||
var fixedDv = new DataView(rab, 4, 2);
|
||||
rab.resize(5);
|
||||
assertThrowsInstanceOf(() => fixedDv.byteOffset, TypeError);
|
||||
assertThrowsInstanceOf(() => fixedDv.byteLength, TypeError);
|
||||
assertThrowsInstanceOf(() => fixedDv.getUint8(0), TypeError);
|
||||
rab.resize(6);
|
||||
assertEq(fixedDv.byteOffset, 4);
|
||||
assertEq(fixedDv.byteLength, 2);
|
||||
|
||||
var methodRab = new ArrayBuffer(4, { maxByteLength: 8 });
|
||||
var methodFixed = new Uint8Array(methodRab, 2, 2);
|
||||
methodRab.resize(2);
|
||||
[
|
||||
() => methodFixed.at(0),
|
||||
() => methodFixed.copyWithin(0, 0),
|
||||
() => methodFixed.entries(),
|
||||
() => methodFixed.every(x => true),
|
||||
() => methodFixed.fill(1),
|
||||
() => methodFixed.filter(x => true),
|
||||
() => methodFixed.find(x => true),
|
||||
() => methodFixed.findIndex(x => true),
|
||||
() => methodFixed.findLast(x => true),
|
||||
() => methodFixed.findLastIndex(x => true),
|
||||
() => methodFixed.forEach(x => x),
|
||||
() => methodFixed.includes(0),
|
||||
() => methodFixed.indexOf(0),
|
||||
() => methodFixed.join(","),
|
||||
() => methodFixed.keys(),
|
||||
() => methodFixed.lastIndexOf(0),
|
||||
() => methodFixed.map(x => x),
|
||||
() => methodFixed.reduce((a, b) => a + b, 0),
|
||||
() => methodFixed.reduceRight((a, b) => a + b, 0),
|
||||
() => methodFixed.reverse(),
|
||||
() => methodFixed.set([1], 0),
|
||||
() => methodFixed.slice(),
|
||||
() => methodFixed.some(x => true),
|
||||
() => methodFixed.sort(),
|
||||
() => methodFixed.subarray(),
|
||||
() => methodFixed.toLocaleString(),
|
||||
() => methodFixed.toReversed(),
|
||||
() => methodFixed.toSorted(),
|
||||
() => methodFixed.toString(),
|
||||
() => methodFixed.values(),
|
||||
() => methodFixed.with(0, 1),
|
||||
() => methodFixed[Symbol.iterator](),
|
||||
].forEach(fn => assertThrowsInstanceOf(fn, TypeError));
|
||||
|
||||
methodRab.resize(4);
|
||||
assertEq(methodFixed.length, 2);
|
||||
|
||||
var sourceRab = new ArrayBuffer(4, { maxByteLength: 8 });
|
||||
var oobSource = new Uint8Array(sourceRab, 2, 2);
|
||||
sourceRab.resize(2);
|
||||
assertThrowsInstanceOf(() => new Uint8Array(oobSource), TypeError);
|
||||
assertThrowsInstanceOf(() => new Uint16Array(oobSource), TypeError);
|
||||
assertThrowsInstanceOf(() => new Uint8Array(4).set(oobSource), TypeError);
|
||||
sourceRab.resize(4);
|
||||
assertEq(new Uint8Array(oobSource).length, 2);
|
||||
|
||||
var ctorRab = new ArrayBuffer(8, { maxByteLength: 8 });
|
||||
var ShrinkingNewTarget = new Proxy(function() {}, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === "prototype") {
|
||||
ctorRab.resize(2);
|
||||
return DataView.prototype;
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
});
|
||||
assertThrowsInstanceOf(() => Reflect.construct(DataView, [ctorRab, 4], ShrinkingNewTarget),
|
||||
RangeError);
|
||||
|
||||
var fixedCtorRab = new ArrayBuffer(8, { maxByteLength: 8 });
|
||||
var FixedShrinkingNewTarget = new Proxy(function() {}, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === "prototype") {
|
||||
fixedCtorRab.resize(5);
|
||||
return DataView.prototype;
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
});
|
||||
assertThrowsInstanceOf(() => Reflect.construct(DataView, [fixedCtorRab, 4, 2],
|
||||
FixedShrinkingNewTarget),
|
||||
RangeError);
|
||||
|
||||
var gsab = new SharedArrayBuffer(4, { maxByteLength: 16 });
|
||||
var sharedTracking = new Uint8Array(gsab);
|
||||
assertEq(sharedTracking.length, 4);
|
||||
gsab.grow(8);
|
||||
assertEq(sharedTracking.length, 8);
|
||||
sharedTracking[6] = 33;
|
||||
assertEq(new Uint8Array(gsab)[6], 33);
|
||||
|
||||
var sharedDv = new DataView(gsab);
|
||||
assertEq(sharedDv.buffer, gsab);
|
||||
assertEq(sharedDv.byteOffset, 0);
|
||||
assertEq(sharedDv.byteLength, 8);
|
||||
sharedDv.setUint8(7, 99);
|
||||
assertEq(sharedTracking[7], 99);
|
||||
gsab.grow(12);
|
||||
assertEq(sharedDv.byteLength, 12);
|
||||
assertEq(sharedDv.getUint8(7), 99);
|
||||
|
||||
var fixedSharedDv = new DataView(gsab, 4, 2);
|
||||
assertEq(fixedSharedDv.byteOffset, 4);
|
||||
assertEq(fixedSharedDv.byteLength, 2);
|
||||
gsab.grow(16);
|
||||
assertEq(fixedSharedDv.byteOffset, 4);
|
||||
assertEq(fixedSharedDv.byteLength, 2);
|
||||
|
||||
function readLength(view) {
|
||||
return view.length;
|
||||
}
|
||||
|
||||
function readElement(view, index) {
|
||||
return view[index];
|
||||
}
|
||||
|
||||
function writeElement(view, index, value) {
|
||||
view[index] = value;
|
||||
}
|
||||
|
||||
var normal = new Uint8Array(4);
|
||||
normal[0] = 7;
|
||||
for (var i = 0; i < 2000; i++) {
|
||||
assertEq(readLength(normal), 4);
|
||||
assertEq(readElement(normal, 0), 7);
|
||||
writeElement(normal, 1, 8);
|
||||
}
|
||||
|
||||
var icRab = new ArrayBuffer(4, { maxByteLength: 8 });
|
||||
var icTracking = new Uint8Array(icRab);
|
||||
icTracking[0] = 9;
|
||||
assertEq(readLength(icTracking), 4);
|
||||
assertEq(readElement(icTracking, 0), 9);
|
||||
icRab.resize(0);
|
||||
assertEq(readLength(icTracking), 0);
|
||||
assertEq(readElement(icTracking, 0), undefined);
|
||||
writeElement(icTracking, 0, 1);
|
||||
icRab.resize(4);
|
||||
assertEq(readLength(icTracking), 4);
|
||||
assertEq(readElement(icTracking, 0), 0);
|
||||
writeElement(icTracking, 0, 12);
|
||||
assertEq(readElement(icTracking, 0), 12);
|
||||
|
||||
var icFixed = new Uint8Array(icRab, 1, 2);
|
||||
assertEq(readLength(icFixed), 2);
|
||||
icRab.resize(2);
|
||||
assertEq(readLength(icFixed), 0);
|
||||
assertEq(readElement(icFixed, 0), undefined);
|
||||
writeElement(icFixed, 0, 55);
|
||||
icRab.resize(4);
|
||||
assertEq(readLength(icFixed), 2);
|
||||
assertEq(readElement(icFixed, 0), 0);
|
||||
|
||||
var icGsab = new SharedArrayBuffer(4, { maxByteLength: 8 });
|
||||
var icSharedTracking = new Uint8Array(icGsab);
|
||||
assertEq(readLength(icSharedTracking), 4);
|
||||
icGsab.grow(8);
|
||||
assertEq(readLength(icSharedTracking), 8);
|
||||
writeElement(icSharedTracking, 5, 77);
|
||||
assertEq(readElement(icSharedTracking, 5), 77);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
@@ -0,0 +1,77 @@
|
||||
// |reftest| skip-if(!this.SharedArrayBuffer || !this.Atomics || !this.drainJobQueue)
|
||||
|
||||
if (typeof SharedArrayBuffer === "function" && typeof Atomics === "object" &&
|
||||
typeof drainJobQueue === "function") {
|
||||
const sab = new SharedArrayBuffer(4);
|
||||
const i32 = new Int32Array(sab);
|
||||
|
||||
let result = Atomics.waitAsync(i32, 0, 1, 10);
|
||||
assertEq(result.async, false);
|
||||
assertEq(result.value, "not-equal");
|
||||
|
||||
result = Atomics.waitAsync(i32, 0, 0, 0);
|
||||
assertEq(result.async, false);
|
||||
assertEq(result.value, "timed-out");
|
||||
|
||||
result = Atomics.waitAsync(i32, 0, 0);
|
||||
assertEq(result.async, true);
|
||||
let notified;
|
||||
result.value.then(value => {
|
||||
notified = value;
|
||||
});
|
||||
assertEq(Atomics.notify(i32, 0, 1), 1);
|
||||
drainJobQueue();
|
||||
assertEq(notified, "ok");
|
||||
|
||||
result = Atomics.waitAsync(i32, 0, 0, 1);
|
||||
assertEq(result.async, true);
|
||||
let timedOut;
|
||||
result.value.then(value => {
|
||||
timedOut = value;
|
||||
});
|
||||
drainJobQueue();
|
||||
assertEq(timedOut, "timed-out");
|
||||
|
||||
if (typeof BigInt64Array === "function") {
|
||||
const bigSab = new SharedArrayBuffer(16);
|
||||
const i64 = new BigInt64Array(bigSab);
|
||||
const u64 = new BigUint64Array(bigSab);
|
||||
|
||||
assertEq(Atomics.store(i64, 0, -1n), -1n);
|
||||
assertEq(Atomics.load(i64, 0), -1n);
|
||||
assertEq(Atomics.load(u64, 0), 18446744073709551615n);
|
||||
assertEq(Atomics.exchange(i64, 0, 7n), -1n);
|
||||
assertEq(Atomics.compareExchange(i64, 0, 7n, 10n), 7n);
|
||||
assertEq(Atomics.add(i64, 0, 5n), 10n);
|
||||
assertEq(Atomics.load(i64, 0), 15n);
|
||||
assertEq(Atomics.sub(i64, 0, 20n), 15n);
|
||||
assertEq(Atomics.load(i64, 0), -5n);
|
||||
assertEq(Atomics.and(u64, 0, 7n), 18446744073709551611n);
|
||||
assertEq(Atomics.or(u64, 0, 8n), 3n);
|
||||
assertEq(Atomics.xor(u64, 0, 15n), 11n);
|
||||
|
||||
assertThrowsInstanceOf(() => Atomics.waitAsync(u64, 0, 0n), TypeError);
|
||||
|
||||
result = Atomics.waitAsync(i64, 1, 1n, 10);
|
||||
assertEq(result.async, false);
|
||||
assertEq(result.value, "not-equal");
|
||||
|
||||
result = Atomics.waitAsync(i64, 1, 0n, 0);
|
||||
assertEq(result.async, false);
|
||||
assertEq(result.value, "timed-out");
|
||||
|
||||
let offsetView = new BigInt64Array(bigSab, 8);
|
||||
result = Atomics.waitAsync(offsetView, 0, 0n);
|
||||
assertEq(result.async, true);
|
||||
notified = undefined;
|
||||
result.value.then(value => {
|
||||
notified = value;
|
||||
});
|
||||
assertEq(Atomics.notify(i64, 1, 1), 1);
|
||||
drainJobQueue();
|
||||
assertEq(notified, "ok");
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
@@ -0,0 +1,31 @@
|
||||
// |reftest| skip-if(!Promise.withResolvers)
|
||||
|
||||
var desc = Object.getOwnPropertyDescriptor(Promise, "withResolvers");
|
||||
assertEq(desc.enumerable, false);
|
||||
assertEq(desc.configurable, true);
|
||||
assertEq(desc.writable, true);
|
||||
assertEq(Promise.withResolvers.length, 0);
|
||||
assertEq(Promise.withResolvers.name, "withResolvers");
|
||||
|
||||
var capability = Promise.withResolvers();
|
||||
assertEq(capability.promise instanceof Promise, true);
|
||||
assertEq(typeof capability.resolve, "function");
|
||||
assertEq(typeof capability.reject, "function");
|
||||
assertEqArray(Object.keys(capability), ["promise", "resolve", "reject"]);
|
||||
|
||||
capability.resolve(42);
|
||||
capability.promise.then(v => assertEq(v, 42));
|
||||
|
||||
class MyPromise extends Promise {}
|
||||
var subCapability = Promise.withResolvers.call(MyPromise);
|
||||
assertEq(subCapability.promise instanceof MyPromise, true);
|
||||
subCapability.reject("rejected");
|
||||
subCapability.promise.then(
|
||||
() => { throw new Error("expected rejection"); },
|
||||
reason => assertEq(reason, "rejected")
|
||||
);
|
||||
|
||||
assertThrowsInstanceOf(() => Promise.withResolvers.call({}), TypeError);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
||||
@@ -0,0 +1,40 @@
|
||||
// |reftest| skip-if(!this.SharedArrayBuffer)
|
||||
|
||||
if (typeof SharedArrayBuffer === "function") {
|
||||
const fixed = new SharedArrayBuffer(4);
|
||||
assertEq(fixed.byteLength, 4);
|
||||
assertEq(fixed.maxByteLength, 4);
|
||||
assertEq(fixed.growable, false);
|
||||
assertThrowsInstanceOf(() => fixed.grow(4), TypeError);
|
||||
|
||||
assertThrowsInstanceOf(() => new SharedArrayBuffer(-1), RangeError);
|
||||
assertThrowsInstanceOf(() => new SharedArrayBuffer(8, {maxByteLength: 4}), RangeError);
|
||||
|
||||
let optionGetterCalled = false;
|
||||
const growable = new SharedArrayBuffer(4, {
|
||||
get maxByteLength() {
|
||||
optionGetterCalled = true;
|
||||
return 16;
|
||||
}
|
||||
});
|
||||
assertEq(optionGetterCalled, true);
|
||||
assertEq(growable.byteLength, 4);
|
||||
assertEq(growable.maxByteLength, 16);
|
||||
assertEq(growable.growable, true);
|
||||
|
||||
const before = new Uint8Array(growable);
|
||||
before[0] = 37;
|
||||
assertEq(growable.grow(12), undefined);
|
||||
assertEq(growable.byteLength, 12);
|
||||
assertEq(growable.maxByteLength, 16);
|
||||
|
||||
const after = new Uint8Array(growable);
|
||||
assertEq(after.length, 12);
|
||||
assertEq(after[0], 37);
|
||||
|
||||
assertThrowsInstanceOf(() => growable.grow(11), RangeError);
|
||||
assertThrowsInstanceOf(() => growable.grow(17), RangeError);
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
@@ -0,0 +1,26 @@
|
||||
// |reftest| skip-if(!String.prototype.isWellFormed||!String.prototype.toWellFormed)
|
||||
|
||||
assertEq("".isWellFormed(), true);
|
||||
assertEq("abc".isWellFormed(), true);
|
||||
assertEq("\uD83D\uDE00".isWellFormed(), true);
|
||||
assertEq("\uD800".isWellFormed(), false);
|
||||
assertEq("\uDC00".isWellFormed(), false);
|
||||
assertEq("\uD800a".isWellFormed(), false);
|
||||
assertEq("a\uDC00".isWellFormed(), false);
|
||||
assertEq("\uD800\uD800\uDC00".isWellFormed(), false);
|
||||
|
||||
assertEq("abc".toWellFormed(), "abc");
|
||||
assertEq("\uD83D\uDE00".toWellFormed(), "\uD83D\uDE00");
|
||||
assertEq("\uD800".toWellFormed(), "\uFFFD");
|
||||
assertEq("\uDC00".toWellFormed(), "\uFFFD");
|
||||
assertEq("\uD800a\uDC00".toWellFormed(), "\uFFFDa\uFFFD");
|
||||
assertEq("\uD800\uD800\uDC00".toWellFormed(), "\uFFFD\uD800\uDC00");
|
||||
|
||||
assertEq(String.prototype.isWellFormed.call(123), true);
|
||||
assertEq(String.prototype.toWellFormed.call({ toString() { return "\uD800x"; } }), "\uFFFDx");
|
||||
|
||||
assertThrowsInstanceOf(() => String.prototype.isWellFormed.call(null), TypeError);
|
||||
assertThrowsInstanceOf(() => String.prototype.toWellFormed.call(undefined), TypeError);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
||||
@@ -0,0 +1,36 @@
|
||||
var key = Symbol("weak");
|
||||
var map = new WeakMap();
|
||||
assertEq(map.has(key), false);
|
||||
assertEq(map.get(key), undefined);
|
||||
assertEq(map.set(key, 13), map);
|
||||
assertEq(map.has(key), true);
|
||||
assertEq(map.get(key), 13);
|
||||
assertEq(map.delete(key), true);
|
||||
assertEq(map.has(key), false);
|
||||
|
||||
var constructedKey = Symbol("constructed");
|
||||
var constructed = new WeakMap([[constructedKey, 7]]);
|
||||
assertEq(constructed.get(constructedKey), 7);
|
||||
|
||||
var registered = Symbol.for("registered");
|
||||
assertEq(map.has(registered), false);
|
||||
assertEq(map.get(registered), undefined);
|
||||
assertEq(map.delete(registered), false);
|
||||
assertThrowsInstanceOf(() => map.set(registered, 1), TypeError);
|
||||
assertThrowsInstanceOf(() => new WeakMap([[registered, 1]]), TypeError);
|
||||
|
||||
var setKey = Symbol("set");
|
||||
var set = new WeakSet([setKey]);
|
||||
assertEq(set.has(setKey), true);
|
||||
assertEq(set.delete(setKey), true);
|
||||
assertEq(set.has(setKey), false);
|
||||
assertEq(set.add(setKey), set);
|
||||
assertEq(set.has(setKey), true);
|
||||
|
||||
assertEq(set.has(registered), false);
|
||||
assertEq(set.delete(registered), false);
|
||||
assertThrowsInstanceOf(() => set.add(registered), TypeError);
|
||||
assertThrowsInstanceOf(() => new WeakSet([registered]), TypeError);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
||||
@@ -0,0 +1,34 @@
|
||||
// |reftest| skip-if(!Object.groupBy||!Map.groupBy)
|
||||
|
||||
var objectGroups = Object.groupBy(["a", "bb", "c"], value => value.length);
|
||||
assertEq(Object.getPrototypeOf(objectGroups), null);
|
||||
assertEqArray(objectGroups["1"], ["a", "c"]);
|
||||
assertEqArray(objectGroups["2"], ["bb"]);
|
||||
|
||||
var symbol = Symbol();
|
||||
var symbolGroups = Object.groupBy([1, 2], value => value === 1 ? symbol : "__proto__");
|
||||
assertEqArray(symbolGroups[symbol], [1]);
|
||||
assertEqArray(symbolGroups.__proto__, [2]);
|
||||
|
||||
var indexes = [];
|
||||
var mapGroups = Map.groupBy(["a", "bb", "c"], (value, index) => {
|
||||
indexes.push(index);
|
||||
return value.length;
|
||||
});
|
||||
assertEq(mapGroups instanceof Map, true);
|
||||
assertEqArray(indexes, [0, 1, 2]);
|
||||
assertEqArray(mapGroups.get(1), ["a", "c"]);
|
||||
assertEqArray(mapGroups.get(2), ["bb"]);
|
||||
|
||||
var key = {};
|
||||
var objectKeyGroups = Map.groupBy([1, 2], value => value === 1 ? key : NaN);
|
||||
assertEqArray(objectKeyGroups.get(key), [1]);
|
||||
assertEqArray(objectKeyGroups.get(NaN), [2]);
|
||||
|
||||
assertThrowsInstanceOf(() => Object.groupBy(null, x => x), TypeError);
|
||||
assertThrowsInstanceOf(() => Map.groupBy(undefined, x => x), TypeError);
|
||||
assertThrowsInstanceOf(() => Object.groupBy([], null), TypeError);
|
||||
assertThrowsInstanceOf(() => Map.groupBy([], null), TypeError);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
||||
+375
-36
@@ -22,6 +22,7 @@
|
||||
# include <valgrind/memcheck.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include "jsapi.h"
|
||||
#include "jsarray.h"
|
||||
#include "jscntxt.h"
|
||||
@@ -45,6 +46,7 @@
|
||||
#include "vm/Interpreter.h"
|
||||
#include "vm/SelfHosting.h"
|
||||
#include "vm/SharedArrayObject.h"
|
||||
#include "vm/TypedArrayObject.h"
|
||||
#include "vm/WrapperObject.h"
|
||||
#include "wasm/WasmSignalHandlers.h"
|
||||
#include "wasm/WasmTypes.h"
|
||||
@@ -170,12 +172,18 @@ static const JSPropertySpec static_properties[] = {
|
||||
|
||||
|
||||
static const JSFunctionSpec prototype_functions[] = {
|
||||
JS_FN("resize", ArrayBufferObject::fun_resize, 1, 0),
|
||||
JS_SELF_HOSTED_FN("slice", "ArrayBufferSlice", 2, 0),
|
||||
JS_FN("transfer", ArrayBufferObject::fun_transfer, 0, 0),
|
||||
JS_FN("transferToFixedLength", ArrayBufferObject::fun_transferToFixedLength, 0, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
static const JSPropertySpec prototype_properties[] = {
|
||||
JS_PSG("byteLength", ArrayBufferObject::byteLengthGetter, 0),
|
||||
JS_PSG("detached", ArrayBufferObject::detachedGetter, 0),
|
||||
JS_PSG("maxByteLength", ArrayBufferObject::maxByteLengthGetter, 0),
|
||||
JS_PSG("resizable", ArrayBufferObject::resizableGetter, 0),
|
||||
JS_STRING_SYM_PS(toStringTag, "ArrayBuffer", JSPROP_READONLY),
|
||||
JS_PS_END
|
||||
};
|
||||
@@ -252,6 +260,52 @@ ArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
|
||||
return CallNonGenericMethod<IsArrayBuffer, byteLengthGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
ArrayBufferObject::detachedGetterImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
|
||||
args.rval().setBoolean(args.thisv().toObject().as<ArrayBufferObject>().isDetached());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::detachedGetter(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, detachedGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
ArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
|
||||
ArrayBufferObject& buffer = args.thisv().toObject().as<ArrayBufferObject>();
|
||||
args.rval().setInt32(buffer.isDetached() ? 0 : int32_t(buffer.maxByteLength()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, maxByteLengthGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
ArrayBufferObject::resizableGetterImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
|
||||
args.rval().setBoolean(args.thisv().toObject().as<ArrayBufferObject>().isResizable());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::resizableGetter(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, resizableGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
/*
|
||||
* ArrayBuffer.isView(obj); ES6 (Dec 2013 draft) 24.1.3.1
|
||||
*/
|
||||
@@ -264,6 +318,39 @@ ArrayBufferObject::fun_isView(JSContext* cx, unsigned argc, Value* vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
GetArrayBufferMaxByteLengthOption(JSContext* cx, HandleValue options,
|
||||
uint32_t byteLength, uint32_t* maxByteLength,
|
||||
bool* resizable)
|
||||
{
|
||||
*maxByteLength = byteLength;
|
||||
*resizable = false;
|
||||
|
||||
if (!options.isObject())
|
||||
return true;
|
||||
|
||||
RootedObject opts(cx, &options.toObject());
|
||||
RootedValue maxByteLengthValue(cx);
|
||||
if (!GetProperty(cx, opts, opts, cx->names().maxByteLength, &maxByteLengthValue))
|
||||
return false;
|
||||
|
||||
if (maxByteLengthValue.isUndefined())
|
||||
return true;
|
||||
|
||||
uint64_t max;
|
||||
if (!ToIndex(cx, maxByteLengthValue, &max))
|
||||
return false;
|
||||
|
||||
if (max > INT32_MAX || max < byteLength) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
*maxByteLength = uint32_t(max);
|
||||
*resizable = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ES2017 draft 24.1.2.1
|
||||
bool
|
||||
@@ -287,12 +374,22 @@ ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
|
||||
}
|
||||
|
||||
// Step 3.
|
||||
uint32_t maxByteLength;
|
||||
bool resizable;
|
||||
if (!GetArrayBufferMaxByteLengthOption(cx, args.get(1), uint32_t(byteLength),
|
||||
&maxByteLength, &resizable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4.
|
||||
RootedObject proto(cx);
|
||||
RootedObject newTarget(cx, &args.newTarget().toObject());
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
|
||||
return false;
|
||||
|
||||
JSObject* bufobj = create(cx, uint32_t(byteLength), proto);
|
||||
JSObject* bufobj = create(cx, uint32_t(byteLength), BufferContents::createPlain(nullptr),
|
||||
OwnsData, proto, GenericObject, maxByteLength, resizable);
|
||||
if (!bufobj)
|
||||
return false;
|
||||
args.rval().setObject(*bufobj);
|
||||
@@ -302,13 +399,66 @@ ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
|
||||
static ArrayBufferObject::BufferContents
|
||||
AllocateArrayBufferContents(JSContext* cx, uint32_t nbytes)
|
||||
{
|
||||
uint8_t* p = cx->runtime()->pod_callocCanGC<uint8_t>(nbytes);
|
||||
uint8_t* p = cx->runtime()->pod_callocCanGC<uint8_t>(nbytes ? nbytes : 1);
|
||||
if (!p)
|
||||
ReportOutOfMemory(cx);
|
||||
|
||||
return ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(p);
|
||||
}
|
||||
|
||||
static bool
|
||||
ReportArrayBufferNotResizable(JSContext* cx)
|
||||
{
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARRAYBUFFER_NOT_RESIZABLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
ReportArrayBufferCannotDetach(JSContext* cx)
|
||||
{
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARRAYBUFFER_CANNOT_DETACH);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
ReportArrayBufferLengthOutOfRange(JSContext* cx)
|
||||
{
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
ArrayBufferViewFits(ArrayBufferViewObject* view, uint32_t newByteLength)
|
||||
{
|
||||
if (view->is<DataViewObject>()) {
|
||||
DataViewObject& dataView = view->as<DataViewObject>();
|
||||
uint32_t byteOffset = dataView.byteOffsetMaybeOutOfBounds();
|
||||
if (byteOffset > newByteLength)
|
||||
return false;
|
||||
uint32_t byteLength = dataView.isLengthTracking()
|
||||
? newByteLength - byteOffset
|
||||
: dataView.fixedByteLengthMaybeOutOfBounds();
|
||||
return byteOffset <= newByteLength && byteLength <= newByteLength - byteOffset;
|
||||
}
|
||||
|
||||
if (view->is<TypedArrayObject>()) {
|
||||
TypedArrayObject& typedArray = view->as<TypedArrayObject>();
|
||||
if (typedArray.isSharedMemory())
|
||||
return true;
|
||||
uint32_t byteOffset = typedArray.byteOffsetMaybeOutOfBounds();
|
||||
if (byteOffset > newByteLength)
|
||||
return false;
|
||||
uint32_t byteLength = typedArray.isLengthTracking()
|
||||
? newByteLength - byteOffset
|
||||
: typedArray.fixedLengthMaybeOutOfBounds() *
|
||||
typedArray.bytesPerElement();
|
||||
return byteOffset <= newByteLength && byteLength <= newByteLength - byteOffset;
|
||||
}
|
||||
|
||||
// Outline typed objects don't have a recoverable fixed byte range here.
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
NoteViewBufferWasDetached(ArrayBufferViewObject* view,
|
||||
ArrayBufferObject::BufferContents newContents,
|
||||
@@ -391,7 +541,8 @@ ArrayBufferObject::setNewData(FreeOp* fop, BufferContents newContents, OwnsState
|
||||
|
||||
void
|
||||
ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view,
|
||||
uint8_t* oldDataPointer, BufferContents newContents)
|
||||
uint8_t* oldDataPointer, BufferContents newContents,
|
||||
uint32_t newByteLength)
|
||||
{
|
||||
MOZ_ASSERT(!view->isSharedMemory());
|
||||
|
||||
@@ -402,7 +553,18 @@ ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view
|
||||
uint8_t* viewDataPointer = view->dataPointerUnshared(nogc);
|
||||
if (viewDataPointer) {
|
||||
MOZ_ASSERT(newContents);
|
||||
ptrdiff_t offset = viewDataPointer - oldDataPointer;
|
||||
uint32_t offset;
|
||||
if (view->is<DataViewObject>()) {
|
||||
offset = view->as<DataViewObject>().byteOffsetMaybeOutOfBounds();
|
||||
} else if (view->is<TypedArrayObject>()) {
|
||||
offset = view->as<TypedArrayObject>().byteOffsetMaybeOutOfBounds();
|
||||
} else {
|
||||
ptrdiff_t oldOffset = viewDataPointer - oldDataPointer;
|
||||
MOZ_ASSERT(oldOffset >= 0);
|
||||
offset = uint32_t(oldOffset);
|
||||
}
|
||||
if (offset > newByteLength)
|
||||
offset = 0;
|
||||
viewDataPointer = static_cast<uint8_t*>(newContents.data()) + offset;
|
||||
view->setDataPointerUnshared(viewDataPointer);
|
||||
}
|
||||
@@ -428,10 +590,180 @@ ArrayBufferObject::changeContents(JSContext* cx, BufferContents newContents,
|
||||
auto& innerViews = cx->compartment()->innerViews.get();
|
||||
if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) {
|
||||
for (size_t i = 0; i < views->length(); i++)
|
||||
changeViewContents(cx, (*views)[i], oldDataPointer, newContents);
|
||||
changeViewContents(cx, (*views)[i], oldDataPointer, newContents, byteLength());
|
||||
}
|
||||
if (firstView())
|
||||
changeViewContents(cx, firstView(), oldDataPointer, newContents);
|
||||
changeViewContents(cx, firstView(), oldDataPointer, newContents, byteLength());
|
||||
}
|
||||
|
||||
void
|
||||
ArrayBufferObject::changeContentsForResize(JSContext* cx, BufferContents newContents,
|
||||
OwnsState ownsState, uint32_t newByteLength)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!isWasm());
|
||||
MOZ_ASSERT(!forInlineTypedObject());
|
||||
|
||||
uint8_t* oldDataPointer = dataPointer();
|
||||
setNewData(cx->runtime()->defaultFreeOp(), newContents, ownsState);
|
||||
setByteLength(newByteLength);
|
||||
|
||||
auto& innerViews = cx->compartment()->innerViews.get();
|
||||
if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) {
|
||||
for (size_t i = 0; i < views->length(); i++) {
|
||||
ArrayBufferViewObject* view = (*views)[i];
|
||||
if (view->is<DataViewObject>() || view->is<TypedArrayObject>())
|
||||
changeViewContents(cx, view, oldDataPointer, newContents, newByteLength);
|
||||
else if (ArrayBufferViewFits(view, newByteLength))
|
||||
changeViewContents(cx, view, oldDataPointer, newContents, newByteLength);
|
||||
else
|
||||
NoteViewBufferWasDetached(view, newContents, cx);
|
||||
}
|
||||
}
|
||||
|
||||
if (firstView()) {
|
||||
if (firstView()->is<DataViewObject>() || firstView()->is<TypedArrayObject>())
|
||||
changeViewContents(cx, firstView(), oldDataPointer, newContents, newByteLength);
|
||||
else if (ArrayBufferViewFits(firstView(), newByteLength))
|
||||
changeViewContents(cx, firstView(), oldDataPointer, newContents, newByteLength);
|
||||
else
|
||||
NoteViewBufferWasDetached(firstView(), newContents, cx);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
ResizeArrayBuffer(JSContext* cx, Handle<ArrayBufferObject*> buffer, uint32_t newByteLength)
|
||||
{
|
||||
if (!buffer->isResizable())
|
||||
return ReportArrayBufferNotResizable(cx);
|
||||
|
||||
if (buffer->isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newByteLength > buffer->maxByteLength())
|
||||
return ReportArrayBufferLengthOutOfRange(cx);
|
||||
|
||||
if (!buffer->isPlain() || buffer->isPreparedForAsmJS() || buffer->forInlineTypedObject())
|
||||
return ReportArrayBufferCannotDetach(cx);
|
||||
|
||||
if (newByteLength == buffer->byteLength())
|
||||
return true;
|
||||
|
||||
ArrayBufferObject::BufferContents newContents = AllocateArrayBufferContents(cx, newByteLength);
|
||||
if (!newContents)
|
||||
return false;
|
||||
|
||||
uint32_t copyLength = std::min(newByteLength, buffer->byteLength());
|
||||
if (copyLength > 0)
|
||||
memcpy(newContents.data(), buffer->dataPointer(), copyLength);
|
||||
|
||||
buffer->changeContentsForResize(cx, newContents, ArrayBufferObject::OwnsData, newByteLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::fun_resize_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
|
||||
|
||||
Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
|
||||
if (!buffer->isResizable())
|
||||
return ReportArrayBufferNotResizable(cx);
|
||||
|
||||
uint64_t newByteLength;
|
||||
if (!ToIndex(cx, args.get(0), &newByteLength))
|
||||
return false;
|
||||
if (newByteLength > INT32_MAX)
|
||||
return ReportArrayBufferLengthOutOfRange(cx);
|
||||
|
||||
if (!ResizeArrayBuffer(cx, buffer, uint32_t(newByteLength)))
|
||||
return false;
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::fun_resize(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, fun_resize_impl>(cx, args);
|
||||
}
|
||||
|
||||
static bool
|
||||
ArrayBufferTransfer(JSContext* cx, const CallArgs& args, bool preserveResizability)
|
||||
{
|
||||
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
|
||||
Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
|
||||
|
||||
uint32_t newByteLength = buffer->byteLength();
|
||||
if (args.hasDefined(0)) {
|
||||
uint64_t newLength;
|
||||
if (!ToIndex(cx, args.get(0), &newLength))
|
||||
return false;
|
||||
if (newLength > INT32_MAX)
|
||||
return ReportArrayBufferLengthOutOfRange(cx);
|
||||
newByteLength = uint32_t(newLength);
|
||||
}
|
||||
|
||||
if (buffer->isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer->isWasm() || buffer->isPreparedForAsmJS())
|
||||
return ReportArrayBufferCannotDetach(cx);
|
||||
|
||||
bool newResizable = preserveResizability && buffer->isResizable();
|
||||
uint32_t newMaxByteLength = newResizable ? buffer->maxByteLength() : newByteLength;
|
||||
if (newResizable && newByteLength > newMaxByteLength)
|
||||
return ReportArrayBufferLengthOutOfRange(cx);
|
||||
|
||||
Rooted<ArrayBufferObject*> newBuffer(cx,
|
||||
ArrayBufferObject::create(cx, newByteLength, ArrayBufferObject::BufferContents::createPlain(nullptr),
|
||||
ArrayBufferObject::OwnsData, nullptr, GenericObject,
|
||||
newMaxByteLength, newResizable));
|
||||
if (!newBuffer)
|
||||
return false;
|
||||
|
||||
uint32_t copyLength = std::min(newByteLength, buffer->byteLength());
|
||||
if (copyLength > 0)
|
||||
memcpy(newBuffer->dataPointer(), buffer->dataPointer(), copyLength);
|
||||
|
||||
ArrayBufferObject::BufferContents detachedContents =
|
||||
buffer->hasStealableContents() ? ArrayBufferObject::BufferContents::createPlain(nullptr)
|
||||
: buffer->contents();
|
||||
ArrayBufferObject::detach(cx, buffer, detachedContents);
|
||||
|
||||
args.rval().setObject(*newBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::fun_transfer_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
return ArrayBufferTransfer(cx, args, true);
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::fun_transfer(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, fun_transfer_impl>(cx, args);
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::fun_transferToFixedLength_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
return ArrayBufferTransfer(cx, args, false);
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::fun_transferToFixedLength(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, fun_transferToFixedLength_impl>(cx, args);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -887,6 +1219,14 @@ ArrayBufferObject::byteLength() const
|
||||
return getSlot(BYTE_LENGTH_SLOT).toInt32();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ArrayBufferObject::maxByteLength() const
|
||||
{
|
||||
if (!isResizable())
|
||||
return byteLength();
|
||||
return getSlot(MAX_BYTE_LENGTH_SLOT).toInt32();
|
||||
}
|
||||
|
||||
void
|
||||
ArrayBufferObject::setByteLength(uint32_t length)
|
||||
{
|
||||
@@ -929,7 +1269,7 @@ js::WasmArrayBufferMaxSize(const ArrayBufferObjectMaybeShared* buf)
|
||||
if (buf->is<ArrayBufferObject>())
|
||||
return buf->as<ArrayBufferObject>().wasmMaxSize();
|
||||
|
||||
return Some(buf->as<SharedArrayBufferObject>().byteLength());
|
||||
return Some(buf->as<SharedArrayBufferObject>().maxByteLength());
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
@@ -1035,9 +1375,12 @@ ArrayBufferObject*
|
||||
ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents contents,
|
||||
OwnsState ownsState /* = OwnsData */,
|
||||
HandleObject proto /* = nullptr */,
|
||||
NewObjectKind newKind /* = GenericObject */)
|
||||
NewObjectKind newKind /* = GenericObject */,
|
||||
uint32_t maxByteLength /* = 0 */,
|
||||
bool resizable /* = false */)
|
||||
{
|
||||
MOZ_ASSERT_IF(contents.kind() == MAPPED, contents);
|
||||
MOZ_ASSERT_IF(resizable, maxByteLength >= nbytes);
|
||||
|
||||
// 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
|
||||
// Refuse to allocate too large buffers, currently limited to ~2 GiB.
|
||||
@@ -1046,10 +1389,6 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If we need to allocate data, try to use a larger object size class so
|
||||
// that the array buffer's data can be allocated inline with the object.
|
||||
// The extra space will be left unused by the object's fixed slots and
|
||||
// available for the buffer's data, see NewObject().
|
||||
size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&class_);
|
||||
|
||||
size_t nslots = reservedSlots;
|
||||
@@ -1066,18 +1405,10 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(ownsState == OwnsData);
|
||||
size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots;
|
||||
if (nbytes <= usableSlots * sizeof(Value)) {
|
||||
int newSlots = (nbytes - 1) / sizeof(Value) + 1;
|
||||
MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
|
||||
nslots = reservedSlots + newSlots;
|
||||
contents = BufferContents::createPlain(nullptr);
|
||||
} else {
|
||||
contents = AllocateArrayBufferContents(cx, nbytes);
|
||||
if (!contents)
|
||||
return nullptr;
|
||||
allocated = true;
|
||||
}
|
||||
contents = AllocateArrayBufferContents(cx, nbytes);
|
||||
if (!contents)
|
||||
return nullptr;
|
||||
allocated = true;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!(class_.flags & JSCLASS_HAS_PRIVATE));
|
||||
@@ -1098,9 +1429,10 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
|
||||
if (!contents) {
|
||||
void* data = obj->inlineDataPointer();
|
||||
memset(data, 0, nbytes);
|
||||
obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData);
|
||||
obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData,
|
||||
maxByteLength, resizable);
|
||||
} else {
|
||||
obj->initialize(nbytes, contents, ownsState);
|
||||
obj->initialize(nbytes, contents, ownsState, maxByteLength, resizable);
|
||||
}
|
||||
|
||||
return obj;
|
||||
@@ -1130,25 +1462,28 @@ ArrayBufferObject::createEmpty(JSContext* cx)
|
||||
bool
|
||||
ArrayBufferObject::createDataViewForThisImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
|
||||
MOZ_ASSERT(IsAnyArrayBuffer(args.thisv()));
|
||||
|
||||
/*
|
||||
* This method is only called for |DataView(alienBuf, ...)| which calls
|
||||
* this as |createDataViewForThis.call(alienBuf, byteOffset, byteLength,
|
||||
* DataView.prototype)|,
|
||||
* ergo there must be exactly 3 arguments.
|
||||
* DataView.prototype, lengthTracking)|,
|
||||
* ergo there must be exactly 4 arguments.
|
||||
*/
|
||||
MOZ_ASSERT(args.length() == 3);
|
||||
MOZ_ASSERT(args.length() == 4);
|
||||
|
||||
uint32_t byteOffset = args[0].toPrivateUint32();
|
||||
uint32_t byteLength = args[1].toPrivateUint32();
|
||||
Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
|
||||
bool lengthTracking = args[3].toBoolean();
|
||||
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx,
|
||||
&args.thisv().toObject().as<ArrayBufferObjectMaybeShared>());
|
||||
|
||||
/*
|
||||
* Pop off the passed-along prototype and delegate to normal DataViewObject
|
||||
* construction.
|
||||
*/
|
||||
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, &args[2].toObject());
|
||||
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer,
|
||||
&args[2].toObject(), lengthTracking);
|
||||
if (!obj)
|
||||
return false;
|
||||
args.rval().setObject(*obj);
|
||||
@@ -1159,7 +1494,7 @@ bool
|
||||
ArrayBufferObject::createDataViewForThis(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, createDataViewForThisImpl>(cx, args);
|
||||
return CallNonGenericMethod<IsAnyArrayBuffer, createDataViewForThisImpl>(cx, args);
|
||||
}
|
||||
|
||||
/* static */ ArrayBufferObject::BufferContents
|
||||
@@ -1528,6 +1863,8 @@ ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg)
|
||||
// The data may or may not be inline with the buffer. The buffer
|
||||
// can only move during a compacting GC, in which case its
|
||||
// objectMoved hook has already updated the buffer's data pointer.
|
||||
if (offset > buf.byteLength())
|
||||
offset = 0;
|
||||
obj->initPrivate(buf.dataPointer() + offset);
|
||||
}
|
||||
}
|
||||
@@ -1578,6 +1915,8 @@ ArrayBufferViewObject::dataPointerUnshared(const JS::AutoRequireNoGC& nogc)
|
||||
bool
|
||||
ArrayBufferViewObject::isSharedMemory()
|
||||
{
|
||||
if (is<DataViewObject>())
|
||||
return as<DataViewObject>().isSharedMemory();
|
||||
if (is<TypedArrayObject>())
|
||||
return as<TypedArrayObject>().isSharedMemory();
|
||||
return false;
|
||||
@@ -1609,7 +1948,7 @@ ArrayBufferViewObject::bufferObject(JSContext* cx, Handle<ArrayBufferViewObject*
|
||||
return thisObject->as<TypedArrayObject>().bufferEither();
|
||||
}
|
||||
MOZ_ASSERT(thisObject->is<DataViewObject>());
|
||||
return &thisObject->as<DataViewObject>().arrayBuffer();
|
||||
return &thisObject->as<DataViewObject>().arrayBufferEither();
|
||||
}
|
||||
|
||||
/* JS Friend API */
|
||||
@@ -1860,7 +2199,7 @@ JS_GetArrayBufferViewData(JSObject* obj, bool* isSharedMemory, const JS::AutoChe
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
if (obj->is<DataViewObject>()) {
|
||||
*isSharedMemory = false;
|
||||
*isSharedMemory = obj->as<DataViewObject>().isSharedMemory();
|
||||
return obj->as<DataViewObject>().dataPointer();
|
||||
}
|
||||
TypedArrayObject& ta = obj->as<TypedArrayObject>();
|
||||
@@ -1930,7 +2269,7 @@ js::GetArrayBufferViewLengthAndData(JSObject* obj, uint32_t* length, bool* isSha
|
||||
: obj->as<TypedArrayObject>().byteLength();
|
||||
|
||||
if (obj->is<DataViewObject>()) {
|
||||
*isSharedMemory = false;
|
||||
*isSharedMemory = obj->as<DataViewObject>().isSharedMemory();
|
||||
*data = static_cast<uint8_t*>(obj->as<DataViewObject>().dataPointer());
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -82,7 +82,7 @@ ArrayBufferObjectMaybeShared& AsAnyArrayBuffer(HandleValue val);
|
||||
class ArrayBufferObjectMaybeShared : public NativeObject
|
||||
{
|
||||
public:
|
||||
uint32_t byteLength() {
|
||||
uint32_t byteLength() const {
|
||||
return AnyArrayBufferByteLength(this);
|
||||
}
|
||||
|
||||
@@ -128,15 +128,22 @@ typedef MutableHandle<ArrayBufferObjectMaybeShared*> MutableHandleArrayBufferObj
|
||||
class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
{
|
||||
static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool resizableGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool detachedGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool fun_slice_impl(JSContext* cx, const CallArgs& args);
|
||||
static bool fun_resize_impl(JSContext* cx, const CallArgs& args);
|
||||
static bool fun_transfer_impl(JSContext* cx, const CallArgs& args);
|
||||
static bool fun_transferToFixedLength_impl(JSContext* cx, const CallArgs& args);
|
||||
|
||||
public:
|
||||
static const uint8_t DATA_SLOT = 0;
|
||||
static const uint8_t BYTE_LENGTH_SLOT = 1;
|
||||
static const uint8_t FIRST_VIEW_SLOT = 2;
|
||||
static const uint8_t FLAGS_SLOT = 3;
|
||||
static const uint8_t MAX_BYTE_LENGTH_SLOT = 4;
|
||||
|
||||
static const uint8_t RESERVED_SLOTS = 4;
|
||||
static const uint8_t RESERVED_SLOTS = 5;
|
||||
|
||||
static const size_t ARRAY_BUFFER_ALIGNMENT = 8;
|
||||
|
||||
@@ -189,7 +196,11 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
|
||||
// This PLAIN or WASM buffer has been prepared for asm.js and cannot
|
||||
// henceforth be transferred/detached.
|
||||
FOR_ASMJS = 0x40
|
||||
FOR_ASMJS = 0x40,
|
||||
|
||||
// This buffer was created with [[ArrayBufferMaxByteLength]] and can
|
||||
// be resized up to that maximum.
|
||||
RESIZABLE = 0x80
|
||||
};
|
||||
|
||||
static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED,
|
||||
@@ -230,8 +241,14 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
static const Class class_;
|
||||
|
||||
static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool resizableGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool detachedGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
static bool fun_slice(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool fun_resize(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool fun_transfer(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool fun_transferToFixedLength(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
static bool fun_isView(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
@@ -243,7 +260,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
BufferContents contents,
|
||||
OwnsState ownsState = OwnsData,
|
||||
HandleObject proto = nullptr,
|
||||
NewObjectKind newKind = GenericObject);
|
||||
NewObjectKind newKind = GenericObject,
|
||||
uint32_t maxByteLength = 0,
|
||||
bool resizable = false);
|
||||
static ArrayBufferObject* create(JSContext* cx, uint32_t nbytes,
|
||||
HandleObject proto = nullptr,
|
||||
NewObjectKind newKind = GenericObject);
|
||||
@@ -294,6 +313,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
|
||||
void setNewData(FreeOp* fop, BufferContents newContents, OwnsState ownsState);
|
||||
void changeContents(JSContext* cx, BufferContents newContents, OwnsState ownsState);
|
||||
void changeContentsForResize(JSContext* cx, BufferContents newContents,
|
||||
OwnsState ownsState, uint32_t newByteLength);
|
||||
|
||||
// Detach this buffer from its original memory. (This necessarily makes
|
||||
// views of this buffer unusable for modifying that original memory.)
|
||||
@@ -302,7 +323,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
|
||||
private:
|
||||
void changeViewContents(JSContext* cx, ArrayBufferViewObject* view,
|
||||
uint8_t* oldDataPointer, BufferContents newContents);
|
||||
uint8_t* oldDataPointer, BufferContents newContents,
|
||||
uint32_t newByteLength);
|
||||
void setFirstView(ArrayBufferViewObject* view);
|
||||
|
||||
uint8_t* inlineDataPointer() const;
|
||||
@@ -311,6 +333,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
uint8_t* dataPointer() const;
|
||||
SharedMem<uint8_t*> dataPointerShared() const;
|
||||
uint32_t byteLength() const;
|
||||
uint32_t maxByteLength() const;
|
||||
|
||||
BufferContents contents() const {
|
||||
return BufferContents(dataPointer(), bufferKind());
|
||||
@@ -334,6 +357,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
bool isWasm() const { return bufferKind() == WASM; }
|
||||
bool isMapped() const { return bufferKind() == MAPPED; }
|
||||
bool isDetached() const { return flags() & DETACHED; }
|
||||
bool isResizable() const { return flags() & RESIZABLE; }
|
||||
bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }
|
||||
|
||||
// WebAssembly support:
|
||||
@@ -391,12 +415,17 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
|
||||
void setIsDetached() { setFlags(flags() | DETACHED); }
|
||||
void setIsPreparedForAsmJS() { setFlags(flags() | FOR_ASMJS); }
|
||||
void setIsResizable() { setFlags(flags() | RESIZABLE); }
|
||||
|
||||
void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState) {
|
||||
void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState,
|
||||
uint32_t maxByteLength = 0, bool resizable = false) {
|
||||
setByteLength(byteLength);
|
||||
setFlags(0);
|
||||
setFixedSlot(MAX_BYTE_LENGTH_SLOT, Int32Value(maxByteLength ? maxByteLength : byteLength));
|
||||
setFirstView(nullptr);
|
||||
setDataPointer(contents, ownsState);
|
||||
if (resizable)
|
||||
setIsResizable();
|
||||
}
|
||||
|
||||
// Note: initialize() may be called after initEmpty(); initEmpty() must
|
||||
@@ -404,6 +433,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
void initEmpty() {
|
||||
setByteLength(0);
|
||||
setFlags(0);
|
||||
setFixedSlot(MAX_BYTE_LENGTH_SLOT, Int32Value(0));
|
||||
setFirstView(nullptr);
|
||||
setDataPointer(BufferContents::createPlain(nullptr), DoesntOwnData);
|
||||
}
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
macro(lookupSetter, lookupSetter, "__lookupSetter__") \
|
||||
macro(MapConstructorInit, MapConstructorInit, "MapConstructorInit") \
|
||||
macro(MapIterator, MapIterator, "Map Iterator") \
|
||||
macro(maxByteLength, maxByteLength, "maxByteLength") \
|
||||
macro(maximumFractionDigits, maximumFractionDigits, "maximumFractionDigits") \
|
||||
macro(maximumSignificantDigits, maximumSignificantDigits, "maximumSignificantDigits") \
|
||||
macro(message, message, "message") \
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "builtin/AtomicsObject.h"
|
||||
#include "builtin/BigInt.h"
|
||||
#include "builtin/Eval.h"
|
||||
#include "builtin/FinalizationRegistryObject.h"
|
||||
#include "builtin/MapObject.h"
|
||||
#include "builtin/ModuleObject.h"
|
||||
#include "builtin/Object.h"
|
||||
@@ -538,6 +539,7 @@ GlobalObject::initSelfHostingBuiltins(JSContext* cx, Handle<GlobalObject*> globa
|
||||
InitBareBuiltinCtor(cx, global, JSProto_Uint8Array) &&
|
||||
InitBareBuiltinCtor(cx, global, JSProto_Int32Array) &&
|
||||
InitBareSymbolCtor(cx, global) &&
|
||||
InitBareFinalizationRegistryCtor(cx, global) &&
|
||||
InitBareWeakMapCtor(cx, global) &&
|
||||
InitBareWeakRefCtor(cx, global) &&
|
||||
InitStopIterationClass(cx, global) &&
|
||||
|
||||
@@ -43,6 +43,22 @@ static const ObjectElements emptyElementsHeaderShared(0, 0, ObjectElements::Shar
|
||||
HeapSlot* const js::emptyObjectElementsShared =
|
||||
reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderShared) + sizeof(ObjectElements));
|
||||
|
||||
static const ObjectElements emptyElementsHeaderResizableOrGrowable(
|
||||
0, 0, ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER);
|
||||
|
||||
/* Objects with no elements share one empty set of elements. */
|
||||
HeapSlot* const js::emptyObjectElementsResizableOrGrowable =
|
||||
reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderResizableOrGrowable) +
|
||||
sizeof(ObjectElements));
|
||||
|
||||
static const ObjectElements emptyElementsHeaderSharedResizableOrGrowable(
|
||||
0, 0, ObjectElements::SHARED_MEMORY | ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER);
|
||||
|
||||
/* Objects with no elements share one empty set of elements. */
|
||||
HeapSlot* const js::emptyObjectElementsSharedResizableOrGrowable =
|
||||
reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderSharedResizableOrGrowable) +
|
||||
sizeof(ObjectElements));
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
@@ -61,10 +77,12 @@ ObjectElements::ConvertElementsToDoubles(JSContext* cx, uintptr_t elementsPtr)
|
||||
* This function is infallible, but has a fallible interface so that it can
|
||||
* be called directly from Ion code. Only arrays can have their dense
|
||||
* elements converted to doubles, and arrays never have empty elements.
|
||||
*/
|
||||
*/
|
||||
HeapSlot* elementsHeapPtr = (HeapSlot*) elementsPtr;
|
||||
MOZ_ASSERT(elementsHeapPtr != emptyObjectElements &&
|
||||
elementsHeapPtr != emptyObjectElementsShared);
|
||||
elementsHeapPtr != emptyObjectElementsShared &&
|
||||
elementsHeapPtr != emptyObjectElementsResizableOrGrowable &&
|
||||
elementsHeapPtr != emptyObjectElementsSharedResizableOrGrowable);
|
||||
|
||||
ObjectElements* header = ObjectElements::fromElements(elementsHeapPtr);
|
||||
MOZ_ASSERT(!header->shouldConvertDoubleElements());
|
||||
|
||||
@@ -185,6 +185,11 @@ class ObjectElements
|
||||
|
||||
// These elements are set to integrity level "frozen".
|
||||
FROZEN = 0x10,
|
||||
|
||||
// For TypedArrays only: this TypedArray views a resizable
|
||||
// ArrayBuffer or growable SharedArrayBuffer. JIT fast paths with
|
||||
// cached length/data assumptions must fall back for these objects.
|
||||
RESIZABLE_OR_GROWABLE_BUFFER = 0x20,
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -255,6 +260,10 @@ class ObjectElements
|
||||
: flags(SHARED_MEMORY), initializedLength(0), capacity(capacity), length(length)
|
||||
{}
|
||||
|
||||
constexpr ObjectElements(uint32_t capacity, uint32_t length, uint32_t flags)
|
||||
: flags(flags), initializedLength(0), capacity(capacity), length(length)
|
||||
{}
|
||||
|
||||
HeapSlot* elements() {
|
||||
return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(ObjectElements));
|
||||
}
|
||||
@@ -269,6 +278,10 @@ class ObjectElements
|
||||
return flags & SHARED_MEMORY;
|
||||
}
|
||||
|
||||
bool hasResizableOrGrowableBuffer() const {
|
||||
return flags & RESIZABLE_OR_GROWABLE_BUFFER;
|
||||
}
|
||||
|
||||
GCPtrNativeObject& ownerObject() const {
|
||||
MOZ_ASSERT(isCopyOnWrite());
|
||||
return *(GCPtrNativeObject*)(&elements()[initializedLength]);
|
||||
@@ -326,6 +339,8 @@ static_assert(ObjectElements::VALUES_PER_HEADER * sizeof(HeapSlot) == sizeof(Obj
|
||||
*/
|
||||
extern HeapSlot* const emptyObjectElements;
|
||||
extern HeapSlot* const emptyObjectElementsShared;
|
||||
extern HeapSlot* const emptyObjectElementsResizableOrGrowable;
|
||||
extern HeapSlot* const emptyObjectElementsSharedResizableOrGrowable;
|
||||
|
||||
struct Class;
|
||||
class GCMarker;
|
||||
@@ -480,6 +495,12 @@ class NativeObject : public ShapedObject
|
||||
elements_ = emptyObjectElementsShared;
|
||||
}
|
||||
|
||||
void setHasResizableOrGrowableBuffer() {
|
||||
MOZ_ASSERT(elements_ == emptyObjectElements || elements_ == emptyObjectElementsShared);
|
||||
elements_ = isSharedMemory() ? emptyObjectElementsSharedResizableOrGrowable
|
||||
: emptyObjectElementsResizableOrGrowable;
|
||||
}
|
||||
|
||||
bool isInWholeCellBuffer() const {
|
||||
const gc::TenuredCell* cell = &asTenured();
|
||||
gc::ArenaCellSet* cells = cell->arena()->bufferedCells;
|
||||
@@ -1254,7 +1275,10 @@ class NativeObject : public ShapedObject
|
||||
}
|
||||
|
||||
inline bool hasEmptyElements() const {
|
||||
return elements_ == emptyObjectElements || elements_ == emptyObjectElementsShared;
|
||||
return elements_ == emptyObjectElements ||
|
||||
elements_ == emptyObjectElementsShared ||
|
||||
elements_ == emptyObjectElementsResizableOrGrowable ||
|
||||
elements_ == emptyObjectElementsSharedResizableOrGrowable;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -194,6 +194,8 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
|
||||
simulator_(nullptr),
|
||||
#endif
|
||||
scriptAndCountsVector(nullptr),
|
||||
weakRefKeptObjects(nullptr),
|
||||
finalizationRegistryCleanupJobs(nullptr),
|
||||
lcovOutput(),
|
||||
NaNValue(DoubleNaNValue()),
|
||||
negativeInfinityValue(DoubleValue(NegativeInfinity<double>())),
|
||||
@@ -372,6 +374,9 @@ JSRuntime::destroyRuntime()
|
||||
MOZ_ASSERT(!isHeapBusy());
|
||||
MOZ_ASSERT(childRuntimeCount == 0);
|
||||
|
||||
clearWeakRefKeptObjects();
|
||||
clearFinalizationRegistryCleanupJobs();
|
||||
|
||||
fx.destroyInstance();
|
||||
|
||||
sharedIntlData.destroyInstance();
|
||||
@@ -460,6 +465,129 @@ JSRuntime::destroyRuntime()
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
SameWeakRefKeptObject(const JS::Value& kept, JS::HandleValue target)
|
||||
{
|
||||
MOZ_ASSERT(kept.isObject() || kept.isSymbol());
|
||||
MOZ_ASSERT(target.isObject() || target.isSymbol());
|
||||
|
||||
if (kept.isObject())
|
||||
return target.isObject() && &kept.toObject() == &target.toObject();
|
||||
|
||||
return target.isSymbol() && kept.toSymbol() == target.toSymbol();
|
||||
}
|
||||
|
||||
bool
|
||||
JSRuntime::addWeakRefKeptObject(JSContext* cx, JS::HandleValue target)
|
||||
{
|
||||
MOZ_ASSERT(cx->runtime() == this);
|
||||
MOZ_ASSERT(target.isObject() || target.isSymbol());
|
||||
MOZ_ASSERT(!isHeapBusy());
|
||||
|
||||
if (!weakRefKeptObjects) {
|
||||
auto* keptObjects =
|
||||
cx->new_<JS::PersistentRooted<js::WeakRefKeptObjectVector>>(
|
||||
cx, js::WeakRefKeptObjectVector(js::SystemAllocPolicy()));
|
||||
if (!keptObjects)
|
||||
return false;
|
||||
|
||||
weakRefKeptObjects = keptObjects;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < weakRefKeptObjects->length(); i++) {
|
||||
const JS::Value& kept = (*weakRefKeptObjects)[i];
|
||||
if (SameWeakRefKeptObject(kept, target))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!weakRefKeptObjects->append(target.get())) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::clearWeakRefKeptObjects()
|
||||
{
|
||||
MOZ_ASSERT(!isHeapBusy());
|
||||
|
||||
if (!weakRefKeptObjects)
|
||||
return;
|
||||
|
||||
defaultFreeOp()->delete_(weakRefKeptObjects);
|
||||
weakRefKeptObjects = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
JSRuntime::enqueueFinalizationRegistryCleanupJob(JSContext* cx, JS::HandleObject job)
|
||||
{
|
||||
MOZ_ASSERT(cx->runtime() == this);
|
||||
MOZ_ASSERT(job);
|
||||
MOZ_ASSERT(job->is<JSFunction>());
|
||||
MOZ_ASSERT(isHeapBusy());
|
||||
|
||||
if (!finalizationRegistryCleanupJobs) {
|
||||
auto* cleanupJobs =
|
||||
cx->new_<JS::PersistentRooted<js::FinalizationRegistryCleanupJobVector>>(
|
||||
cx, js::FinalizationRegistryCleanupJobVector(js::SystemAllocPolicy()));
|
||||
if (!cleanupJobs)
|
||||
return false;
|
||||
|
||||
finalizationRegistryCleanupJobs = cleanupJobs;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < finalizationRegistryCleanupJobs->length(); i++) {
|
||||
if ((*finalizationRegistryCleanupJobs)[i] == job)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!finalizationRegistryCleanupJobs->append(job)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
JSRuntime::drainFinalizationRegistryCleanupJobs(JSContext* cx)
|
||||
{
|
||||
MOZ_ASSERT(cx->runtime() == this);
|
||||
MOZ_ASSERT(!isHeapBusy());
|
||||
|
||||
if (!finalizationRegistryCleanupJobs)
|
||||
return true;
|
||||
|
||||
if (!enqueuePromiseJobCallback) {
|
||||
finalizationRegistryCleanupJobs->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t length = finalizationRegistryCleanupJobs->length();
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
RootedFunction job(cx, &(*finalizationRegistryCleanupJobs)[i]->as<JSFunction>());
|
||||
if (!enqueuePromiseJob(cx, job, nullptr, nullptr))
|
||||
return false;
|
||||
}
|
||||
|
||||
finalizationRegistryCleanupJobs->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::clearFinalizationRegistryCleanupJobs()
|
||||
{
|
||||
MOZ_ASSERT(!isHeapBusy());
|
||||
|
||||
if (!finalizationRegistryCleanupJobs)
|
||||
return;
|
||||
|
||||
defaultFreeOp()->delete_(finalizationRegistryCleanupJobs);
|
||||
finalizationRegistryCleanupJobs = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes* rtSizes)
|
||||
{
|
||||
|
||||
@@ -360,6 +360,8 @@ class PerThreadData
|
||||
};
|
||||
|
||||
using ScriptAndCountsVector = GCVector<ScriptAndCounts, 0, SystemAllocPolicy>;
|
||||
using WeakRefKeptObjectVector = JS::GCVector<JS::Value, 0, SystemAllocPolicy>;
|
||||
using FinalizationRegistryCleanupJobVector = JS::GCVector<JSObject*, 0, SystemAllocPolicy>;
|
||||
|
||||
class AutoLockForExclusiveAccess;
|
||||
} // namespace js
|
||||
@@ -887,6 +889,19 @@ struct JSRuntime : public JS::shadow::Runtime,
|
||||
/* Strong references on scripts held for PCCount profiling API. */
|
||||
JS::PersistentRooted<js::ScriptAndCountsVector>* scriptAndCountsVector;
|
||||
|
||||
/* Strong references to live WeakRef targets kept until the next job boundary. */
|
||||
JS::PersistentRooted<js::WeakRefKeptObjectVector>* weakRefKeptObjects;
|
||||
|
||||
[[nodiscard]] bool addWeakRefKeptObject(JSContext* cx, JS::HandleValue target);
|
||||
void clearWeakRefKeptObjects();
|
||||
|
||||
/* Cleanup jobs produced by FinalizationRegistry sweeping, enqueued after GC. */
|
||||
JS::PersistentRooted<js::FinalizationRegistryCleanupJobVector>* finalizationRegistryCleanupJobs;
|
||||
|
||||
[[nodiscard]] bool enqueueFinalizationRegistryCleanupJob(JSContext* cx, JS::HandleObject job);
|
||||
[[nodiscard]] bool drainFinalizationRegistryCleanupJobs(JSContext* cx);
|
||||
void clearFinalizationRegistryCleanupJobs();
|
||||
|
||||
/* Code coverage output. */
|
||||
js::coverage::LCovRuntime lcovOutput;
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "builtin/SelfHostingDefines.h"
|
||||
#include "builtin/Stream.h"
|
||||
#include "builtin/TypedObject.h"
|
||||
#include "builtin/WeakRefObject.h"
|
||||
#include "builtin/WeakSetObject.h"
|
||||
#include "gc/Marking.h"
|
||||
#include "gc/Policy.h"
|
||||
@@ -105,6 +106,14 @@ intrinsic_IsObject(JSContext* cx, unsigned argc, Value* vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_CanBeHeldWeakly(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
args.rval().setBoolean(CanBeHeldWeakly(args.get(0)));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_IsArray(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -270,6 +279,20 @@ intrinsic_GetBuiltinConstructor(JSContext* cx, unsigned argc, Value* vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_NewMap(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 0);
|
||||
|
||||
Rooted<MapObject*> map(cx, MapObject::create(cx));
|
||||
if (!map)
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*map);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_SubstringKernel(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -1287,6 +1310,36 @@ intrinsic_PossiblyWrappedTypedArrayHasDetachedBuffer(JSContext* cx, unsigned arg
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_TypedArrayIsOutOfBounds(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 1);
|
||||
|
||||
RootedObject obj(cx, &args[0].toObject());
|
||||
MOZ_ASSERT(obj->is<TypedArrayObject>());
|
||||
args.rval().setBoolean(obj->as<TypedArrayObject>().isOutOfBounds());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_PossiblyWrappedTypedArrayIsOutOfBounds(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 1);
|
||||
MOZ_ASSERT(args[0].isObject());
|
||||
|
||||
JSObject* obj = CheckedUnwrap(&args[0].toObject());
|
||||
if (!obj) {
|
||||
JS_ReportErrorASCII(cx, "Permission denied to access object");
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(obj->is<TypedArrayObject>());
|
||||
args.rval().setBoolean(obj->as<TypedArrayObject>().isOutOfBounds());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_MoveTypedArrayElements(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -1409,6 +1462,10 @@ intrinsic_SetFromTypedArrayApproach(JSContext* cx, unsigned argc, Value* vp)
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
if (unsafeTypedArrayCrossCompartment->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 21, 23.
|
||||
uint32_t unsafeSrcLengthCrossCompartment = unsafeTypedArrayCrossCompartment->length();
|
||||
@@ -2255,7 +2312,10 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
JS_INLINABLE_FN("std_Math_min", math_min, 2,0, MathMin),
|
||||
JS_INLINABLE_FN("std_Math_abs", math_abs, 1,0, MathAbs),
|
||||
|
||||
JS_FN("std_Map_create", intrinsic_NewMap, 0,0),
|
||||
JS_FN("std_Map_get", MapObject::get, 1,0),
|
||||
JS_FN("std_Map_has", MapObject::has, 1,0),
|
||||
JS_FN("std_Map_set", MapObject::set, 2,0),
|
||||
JS_FN("std_Map_iterator", MapObject::entries, 0,0),
|
||||
|
||||
JS_FN("std_Number_valueOf", num_valueOf, 0,0),
|
||||
@@ -2303,6 +2363,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
// Helper funtions after this point.
|
||||
JS_INLINABLE_FN("ToObject", intrinsic_ToObject, 1,0, IntrinsicToObject),
|
||||
JS_INLINABLE_FN("IsObject", intrinsic_IsObject, 1,0, IntrinsicIsObject),
|
||||
JS_FN("CanBeHeldWeakly", intrinsic_CanBeHeldWeakly, 1,0),
|
||||
JS_INLINABLE_FN("IsArray", intrinsic_IsArray, 1,0, ArrayIsArray),
|
||||
JS_INLINABLE_FN("IsWrappedArrayConstructor", intrinsic_IsWrappedArrayConstructor, 1,0,
|
||||
IntrinsicIsWrappedArrayConstructor),
|
||||
@@ -2462,6 +2523,10 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
1, 0, IntrinsicPossiblyWrappedTypedArrayLength),
|
||||
JS_FN("PossiblyWrappedTypedArrayHasDetachedBuffer",
|
||||
intrinsic_PossiblyWrappedTypedArrayHasDetachedBuffer, 1, 0),
|
||||
JS_FN("TypedArrayIsOutOfBounds",
|
||||
intrinsic_TypedArrayIsOutOfBounds, 1, 0),
|
||||
JS_FN("PossiblyWrappedTypedArrayIsOutOfBounds",
|
||||
intrinsic_PossiblyWrappedTypedArrayIsOutOfBounds, 1, 0),
|
||||
|
||||
JS_FN("MoveTypedArrayElements", intrinsic_MoveTypedArrayElements, 4,0),
|
||||
JS_FN("SetFromTypedArrayApproach",intrinsic_SetFromTypedArrayApproach, 4, 0),
|
||||
|
||||
+156
-15
@@ -8,6 +8,7 @@
|
||||
#include "mozilla/Atomics.h"
|
||||
|
||||
#include "jsfriendapi.h"
|
||||
#include "jsnum.h"
|
||||
#include "jsprf.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
@@ -104,15 +105,18 @@ SharedArrayAllocSize(uint32_t length)
|
||||
}
|
||||
|
||||
SharedArrayRawBuffer*
|
||||
SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
|
||||
SharedArrayRawBuffer::New(JSContext* cx, uint32_t length, uint32_t maxLength, bool growable)
|
||||
{
|
||||
// The value (uint32_t)-1 is used as a signal in various places,
|
||||
// so guard against it on principle.
|
||||
MOZ_ASSERT(length != (uint32_t)-1);
|
||||
MOZ_ASSERT(maxLength != (uint32_t)-1);
|
||||
MOZ_ASSERT(maxLength >= length);
|
||||
|
||||
// Add a page for the header and round to a page boundary.
|
||||
uint32_t allocSize = SharedArrayAllocSize(length);
|
||||
if (allocSize <= length)
|
||||
uint32_t allocationLength = growable ? maxLength : length;
|
||||
uint32_t allocSize = SharedArrayAllocSize(allocationLength);
|
||||
if (allocSize <= allocationLength)
|
||||
return nullptr;
|
||||
|
||||
// Test >= to guard against the case where multiple extant runtimes
|
||||
@@ -127,7 +131,8 @@ SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
|
||||
}
|
||||
}
|
||||
|
||||
bool preparedForAsmJS = jit::JitOptions.asmJSAtomicsEnable && IsValidAsmJSHeapLength(length);
|
||||
bool preparedForAsmJS =
|
||||
!growable && jit::JitOptions.asmJSAtomicsEnable && IsValidAsmJSHeapLength(length);
|
||||
|
||||
void* p = nullptr;
|
||||
if (preparedForAsmJS) {
|
||||
@@ -161,8 +166,9 @@ SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
|
||||
|
||||
uint8_t* buffer = reinterpret_cast<uint8_t*>(p) + gc::SystemPageSize();
|
||||
uint8_t* base = buffer - sizeof(SharedArrayRawBuffer);
|
||||
SharedArrayRawBuffer* rawbuf = new (base) SharedArrayRawBuffer(buffer, length, preparedForAsmJS);
|
||||
MOZ_ASSERT(rawbuf->length == length); // Deallocation needs this
|
||||
SharedArrayRawBuffer* rawbuf =
|
||||
new (base) SharedArrayRawBuffer(buffer, length, maxLength, growable, preparedForAsmJS);
|
||||
MOZ_ASSERT(rawbuf->allocatedByteLength() == allocationLength); // Deallocation needs this.
|
||||
return rawbuf;
|
||||
}
|
||||
|
||||
@@ -201,7 +207,7 @@ SharedArrayRawBuffer::dropReference()
|
||||
MOZ_ASSERT(p.asValue() % gc::SystemPageSize() == 0);
|
||||
|
||||
uint8_t* address = p.unwrap(/*safe - only reference*/);
|
||||
uint32_t allocSize = SharedArrayAllocSize(this->length);
|
||||
uint32_t allocSize = SharedArrayAllocSize(this->allocatedByteLength());
|
||||
|
||||
if (this->preparedForAsmJS) {
|
||||
uint32_t mappedSize = SharedArrayMappedSize(allocSize);
|
||||
@@ -221,6 +227,23 @@ SharedArrayRawBuffer::dropReference()
|
||||
numLive--;
|
||||
}
|
||||
|
||||
bool
|
||||
SharedArrayRawBuffer::growTo(uint32_t newLength)
|
||||
{
|
||||
MOZ_ASSERT(growable);
|
||||
MOZ_ASSERT(newLength <= maxLength);
|
||||
|
||||
for (;;) {
|
||||
uint32_t oldLength = length;
|
||||
if (newLength < oldLength)
|
||||
return false;
|
||||
if (newLength == oldLength)
|
||||
return true;
|
||||
if (length.compareExchange(oldLength, newLength))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
SharedArrayBufferObject::byteLengthGetterImpl(JSContext* cx, const CallArgs& args)
|
||||
@@ -237,6 +260,112 @@ SharedArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, Value* v
|
||||
return CallNonGenericMethod<IsSharedArrayBuffer, byteLengthGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
SharedArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
|
||||
args.rval().setInt32(args.thisv().toObject().as<SharedArrayBufferObject>().maxByteLength());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SharedArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsSharedArrayBuffer, maxByteLengthGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
SharedArrayBufferObject::growableGetterImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
|
||||
args.rval().setBoolean(args.thisv().toObject().as<SharedArrayBufferObject>().isGrowable());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SharedArrayBufferObject::growableGetter(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsSharedArrayBuffer, growableGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
static bool
|
||||
ReportSharedArrayBufferNotGrowable(JSContext* cx)
|
||||
{
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_NOT_GROWABLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
ReportSharedArrayBufferLengthOutOfRange(JSContext* cx)
|
||||
{
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_BAD_LENGTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
GetSharedArrayBufferMaxByteLengthOption(JSContext* cx, HandleValue options,
|
||||
uint32_t byteLength, uint32_t* maxByteLength,
|
||||
bool* growable)
|
||||
{
|
||||
*maxByteLength = byteLength;
|
||||
*growable = false;
|
||||
|
||||
if (!options.isObject())
|
||||
return true;
|
||||
|
||||
RootedObject opts(cx, &options.toObject());
|
||||
RootedValue maxByteLengthValue(cx);
|
||||
if (!GetProperty(cx, opts, opts, cx->names().maxByteLength, &maxByteLengthValue))
|
||||
return false;
|
||||
|
||||
if (maxByteLengthValue.isUndefined())
|
||||
return true;
|
||||
|
||||
uint64_t max;
|
||||
if (!ToIndex(cx, maxByteLengthValue, &max))
|
||||
return false;
|
||||
|
||||
if (max > INT32_MAX || max < byteLength)
|
||||
return ReportSharedArrayBufferLengthOutOfRange(cx);
|
||||
|
||||
*maxByteLength = uint32_t(max);
|
||||
*growable = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
SharedArrayBufferObject::fun_grow_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
|
||||
|
||||
Rooted<SharedArrayBufferObject*> buffer(cx,
|
||||
&args.thisv().toObject().as<SharedArrayBufferObject>());
|
||||
if (!buffer->isGrowable())
|
||||
return ReportSharedArrayBufferNotGrowable(cx);
|
||||
|
||||
uint64_t newByteLength;
|
||||
if (!ToIndex(cx, args.get(0), &newByteLength))
|
||||
return false;
|
||||
if (newByteLength > INT32_MAX)
|
||||
return ReportSharedArrayBufferLengthOutOfRange(cx);
|
||||
|
||||
uint32_t newLength = uint32_t(newByteLength);
|
||||
if (newLength > buffer->maxByteLength() || !buffer->growTo(newLength))
|
||||
return ReportSharedArrayBufferLengthOutOfRange(cx);
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SharedArrayBufferObject::fun_grow(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsSharedArrayBuffer, fun_grow_impl>(cx, args);
|
||||
}
|
||||
|
||||
bool
|
||||
SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -245,11 +374,19 @@ SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value*
|
||||
if (!ThrowIfNotConstructing(cx, args, "SharedArrayBuffer"))
|
||||
return false;
|
||||
|
||||
uint64_t length64;
|
||||
if (!ToIndex(cx, args.get(0), &length64))
|
||||
return false;
|
||||
// Bugs 1068458, 1161298: Limit length to 2^31-1.
|
||||
uint32_t length;
|
||||
bool overflow_unused;
|
||||
if (!ToLengthClamped(cx, args.get(0), &length, &overflow_unused) || length > INT32_MAX) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_BAD_LENGTH);
|
||||
if (length64 > INT32_MAX)
|
||||
return ReportSharedArrayBufferLengthOutOfRange(cx);
|
||||
uint32_t length = uint32_t(length64);
|
||||
|
||||
uint32_t maxByteLength;
|
||||
bool growable;
|
||||
if (!GetSharedArrayBufferMaxByteLengthOption(cx, args.get(1), length,
|
||||
&maxByteLength, &growable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -258,7 +395,7 @@ SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value*
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
|
||||
return false;
|
||||
|
||||
JSObject* bufobj = New(cx, length, proto);
|
||||
JSObject* bufobj = New(cx, length, maxByteLength, growable, proto);
|
||||
if (!bufobj)
|
||||
return false;
|
||||
args.rval().setObject(*bufobj);
|
||||
@@ -266,9 +403,10 @@ SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value*
|
||||
}
|
||||
|
||||
SharedArrayBufferObject*
|
||||
SharedArrayBufferObject::New(JSContext* cx, uint32_t length, HandleObject proto)
|
||||
SharedArrayBufferObject::New(JSContext* cx, uint32_t length, uint32_t maxLength, bool growable,
|
||||
HandleObject proto)
|
||||
{
|
||||
SharedArrayRawBuffer* buffer = SharedArrayRawBuffer::New(cx, length);
|
||||
SharedArrayRawBuffer* buffer = SharedArrayRawBuffer::New(cx, length, maxLength, growable);
|
||||
if (!buffer)
|
||||
return nullptr;
|
||||
|
||||
@@ -341,7 +479,7 @@ SharedArrayBufferObject::addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSi
|
||||
// just live with the risk.
|
||||
const SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>();
|
||||
info->objectsNonHeapElementsShared +=
|
||||
buf.byteLength() / buf.rawBufferObject()->refcount();
|
||||
buf.rawBufferObject()->allocatedByteLength() / buf.rawBufferObject()->refcount();
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
@@ -409,12 +547,15 @@ static const JSPropertySpec static_properties[] = {
|
||||
};
|
||||
|
||||
static const JSFunctionSpec prototype_functions[] = {
|
||||
JS_FN("grow", SharedArrayBufferObject::fun_grow, 1, 0),
|
||||
JS_SELF_HOSTED_FN("slice", "SharedArrayBufferSlice", 2, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
static const JSPropertySpec prototype_properties[] = {
|
||||
JS_PSG("byteLength", SharedArrayBufferObject::byteLengthGetter, 0),
|
||||
JS_PSG("growable", SharedArrayBufferObject::growableGetter, 0),
|
||||
JS_PSG("maxByteLength", SharedArrayBufferObject::maxByteLengthGetter, 0),
|
||||
JS_STRING_SYM_PS(toStringTag, "SharedArrayBuffer", JSPROP_READONLY),
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
@@ -44,7 +44,9 @@ class SharedArrayRawBuffer
|
||||
{
|
||||
private:
|
||||
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> refcount_;
|
||||
uint32_t length;
|
||||
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> length;
|
||||
uint32_t maxLength;
|
||||
bool growable;
|
||||
bool preparedForAsmJS;
|
||||
|
||||
// A list of structures representing tasks waiting on some
|
||||
@@ -52,9 +54,12 @@ class SharedArrayRawBuffer
|
||||
FutexWaiter* waiters_;
|
||||
|
||||
protected:
|
||||
SharedArrayRawBuffer(uint8_t* buffer, uint32_t length, bool preparedForAsmJS)
|
||||
SharedArrayRawBuffer(uint8_t* buffer, uint32_t length, uint32_t maxLength, bool growable,
|
||||
bool preparedForAsmJS)
|
||||
: refcount_(1),
|
||||
length(length),
|
||||
maxLength(maxLength),
|
||||
growable(growable),
|
||||
preparedForAsmJS(preparedForAsmJS),
|
||||
waiters_(nullptr)
|
||||
{
|
||||
@@ -62,7 +67,11 @@ class SharedArrayRawBuffer
|
||||
}
|
||||
|
||||
public:
|
||||
static SharedArrayRawBuffer* New(JSContext* cx, uint32_t length);
|
||||
static SharedArrayRawBuffer* New(JSContext* cx, uint32_t length, uint32_t maxLength,
|
||||
bool growable);
|
||||
static SharedArrayRawBuffer* New(JSContext* cx, uint32_t length) {
|
||||
return New(cx, length, length, false);
|
||||
}
|
||||
|
||||
// This may be called from multiple threads. The caller must take
|
||||
// care of mutual exclusion.
|
||||
@@ -85,6 +94,20 @@ class SharedArrayRawBuffer
|
||||
return length;
|
||||
}
|
||||
|
||||
uint32_t maxByteLength() const {
|
||||
return growable ? maxLength : byteLength();
|
||||
}
|
||||
|
||||
uint32_t allocatedByteLength() const {
|
||||
return growable ? maxLength : byteLength();
|
||||
}
|
||||
|
||||
bool isGrowable() const {
|
||||
return growable;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool growTo(uint32_t newLength);
|
||||
|
||||
bool isPreparedForAsmJS() const {
|
||||
return preparedForAsmJS;
|
||||
}
|
||||
@@ -117,6 +140,9 @@ class SharedArrayRawBuffer
|
||||
class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
{
|
||||
static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool growableGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool fun_grow_impl(JSContext* cx, const CallArgs& args);
|
||||
|
||||
public:
|
||||
// RAWBUF_SLOT holds a pointer (as "private" data) to the
|
||||
@@ -128,13 +154,23 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
static const Class class_;
|
||||
|
||||
static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool growableGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool fun_grow(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
// Create a SharedArrayBufferObject with a new SharedArrayRawBuffer.
|
||||
static SharedArrayBufferObject* New(JSContext* cx,
|
||||
uint32_t length,
|
||||
uint32_t maxLength,
|
||||
bool growable,
|
||||
HandleObject proto = nullptr);
|
||||
static SharedArrayBufferObject* New(JSContext* cx,
|
||||
uint32_t length,
|
||||
HandleObject proto = nullptr) {
|
||||
return New(cx, length, length, false, proto);
|
||||
}
|
||||
|
||||
// Create a SharedArrayBufferObject using an existing SharedArrayRawBuffer.
|
||||
static SharedArrayBufferObject* New(JSContext* cx,
|
||||
@@ -164,6 +200,15 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
uint32_t byteLength() const {
|
||||
return rawBufferObject()->byteLength();
|
||||
}
|
||||
uint32_t maxByteLength() const {
|
||||
return rawBufferObject()->maxByteLength();
|
||||
}
|
||||
bool isGrowable() const {
|
||||
return rawBufferObject()->isGrowable();
|
||||
}
|
||||
[[nodiscard]] bool growTo(uint32_t newLength) {
|
||||
return rawBufferObject()->growTo(newLength);
|
||||
}
|
||||
bool isPreparedForAsmJS() const {
|
||||
return rawBufferObject()->isPreparedForAsmJS();
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "builtin/MapObject.h"
|
||||
#include "js/Date.h"
|
||||
#include "js/GCHashTable.h"
|
||||
#include "vm/ArrayBufferObject-inl.h"
|
||||
#include "vm/SavedFrame.h"
|
||||
#include "vm/SharedArrayObject.h"
|
||||
#include "vm/TypedArrayObject.h"
|
||||
|
||||
@@ -761,16 +761,40 @@ class TypedArrayMethods
|
||||
if (!ToInt32(cx, args[1], &offset))
|
||||
return false;
|
||||
|
||||
if (offset < 0 || uint32_t(offset) > target->length()) {
|
||||
// the given offset is bogus
|
||||
if (offset < 0) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (target->hasDetachedBuffer()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
if (target->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t targetLength = target->length();
|
||||
if (uint32_t(offset) > targetLength) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject arg0(cx, &args[0].toObject());
|
||||
if (arg0->is<TypedArrayObject>()) {
|
||||
if (arg0->as<TypedArrayObject>().length() > target->length() - offset) {
|
||||
Rooted<TypedArrayObject*> source(cx, &arg0->as<TypedArrayObject>());
|
||||
if (source->hasDetachedBuffer()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
if (source->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (source->length() > targetLength - offset) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
|
||||
return false;
|
||||
}
|
||||
@@ -782,7 +806,7 @@ class TypedArrayMethods
|
||||
if (!GetLengthProperty(cx, arg0, &len))
|
||||
return false;
|
||||
|
||||
if (uint32_t(offset) > target->length() || len > target->length() - offset) {
|
||||
if (len > targetLength - offset) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
|
||||
return false;
|
||||
}
|
||||
@@ -795,13 +819,32 @@ class TypedArrayMethods
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
setFromTypedArray(JSContext* cx, Handle<SomeTypedArray*> target, HandleObject source,
|
||||
uint32_t offset = 0)
|
||||
{
|
||||
MOZ_ASSERT(source->is<TypedArrayObject>(), "use setFromNonTypedArray");
|
||||
static bool
|
||||
setFromTypedArray(JSContext* cx, Handle<SomeTypedArray*> target, HandleObject source,
|
||||
uint32_t offset = 0)
|
||||
{
|
||||
MOZ_ASSERT(source->is<TypedArrayObject>(), "use setFromNonTypedArray");
|
||||
|
||||
bool isShared = target->isSharedMemory() || source->as<TypedArrayObject>().isSharedMemory();
|
||||
if (target->hasDetachedBuffer()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
if (target->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<TypedArrayObject*> sourceArray(cx, &source->as<TypedArrayObject>());
|
||||
if (sourceArray->hasDetachedBuffer()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
if (sourceArray->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isShared = target->isSharedMemory() || sourceArray->isSharedMemory();
|
||||
|
||||
switch (target->type()) {
|
||||
case Scalar::Int8:
|
||||
|
||||
+102
-32
@@ -482,8 +482,9 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
}
|
||||
|
||||
static TypedArrayObject*
|
||||
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, uint32_t byteOffset, uint32_t len,
|
||||
HandleObject proto)
|
||||
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
|
||||
uint32_t byteOffset, uint32_t len, HandleObject proto,
|
||||
bool lengthTracking = false)
|
||||
{
|
||||
MOZ_ASSERT_IF(!buffer, byteOffset == 0);
|
||||
|
||||
@@ -508,12 +509,19 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
return nullptr;
|
||||
|
||||
bool isSharedMemory = buffer && IsSharedArrayBuffer(buffer.get());
|
||||
bool hasResizableOrGrowableBuffer =
|
||||
buffer &&
|
||||
((buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
|
||||
(buffer->is<SharedArrayBufferObject>() &&
|
||||
buffer->as<SharedArrayBufferObject>().isGrowable()));
|
||||
|
||||
obj->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectOrNullValue(buffer));
|
||||
// This is invariant. Self-hosting code that sets BUFFER_SLOT
|
||||
// (if it does) must maintain it, should it need to.
|
||||
if (isSharedMemory)
|
||||
obj->setIsSharedMemory();
|
||||
if (hasResizableOrGrowableBuffer)
|
||||
obj->setHasResizableOrGrowableBuffer();
|
||||
|
||||
if (buffer) {
|
||||
obj->initViewData(buffer->dataPointerEither() + byteOffset);
|
||||
@@ -547,7 +555,9 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
#endif
|
||||
}
|
||||
|
||||
obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(len));
|
||||
obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT,
|
||||
Int32Value(lengthTracking ? TypedArrayObject::LENGTH_TRACKING
|
||||
: int32_t(len)));
|
||||
obj->setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset));
|
||||
|
||||
#ifdef DEBUG
|
||||
@@ -896,6 +906,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
return nullptr; // invalid byteOffset
|
||||
}
|
||||
|
||||
bool lengthTracking = false;
|
||||
uint32_t len;
|
||||
if (lengthInt == -1) {
|
||||
len = (buffer->byteLength() - byteOffset) / sizeof(NativeType);
|
||||
@@ -904,6 +915,12 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
|
||||
return nullptr; // given byte array doesn't map exactly to sizeof(NativeType) * N
|
||||
}
|
||||
if ((buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
|
||||
(buffer->is<SharedArrayBufferObject>() &&
|
||||
buffer->as<SharedArrayBufferObject>().isGrowable()))
|
||||
{
|
||||
lengthTracking = true;
|
||||
}
|
||||
} else {
|
||||
len = uint32_t(lengthInt);
|
||||
}
|
||||
@@ -922,7 +939,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
return nullptr; // byteOffset + len is too big for the arraybuffer
|
||||
}
|
||||
|
||||
return makeInstance(cx, buffer, byteOffset, len, proto);
|
||||
return makeInstance(cx, buffer, byteOffset, len, proto, lengthTracking);
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -1318,6 +1335,10 @@ TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other, b
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return nullptr;
|
||||
}
|
||||
if (srcArray->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 9.
|
||||
uint32_t elementLength = srcArray->length();
|
||||
@@ -1857,7 +1878,8 @@ DataViewNewObjectKind(JSContext* cx, uint32_t byteLength, JSObject* proto)
|
||||
|
||||
DataViewObject*
|
||||
DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
|
||||
Handle<ArrayBufferObject*> arrayBuffer, JSObject* protoArg)
|
||||
Handle<ArrayBufferObjectMaybeShared*> arrayBuffer, JSObject* protoArg,
|
||||
bool lengthTracking)
|
||||
{
|
||||
if (arrayBuffer->isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
@@ -1866,8 +1888,18 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
|
||||
|
||||
MOZ_ASSERT(byteOffset <= INT32_MAX);
|
||||
MOZ_ASSERT(byteLength <= INT32_MAX);
|
||||
MOZ_ASSERT(byteOffset + byteLength < UINT32_MAX);
|
||||
MOZ_ASSERT(!arrayBuffer || !arrayBuffer->is<SharedArrayBufferObject>());
|
||||
|
||||
uint32_t bufferByteLength = arrayBuffer->byteLength();
|
||||
if (byteOffset > bufferByteLength ||
|
||||
(!lengthTracking && byteLength > bufferByteLength - byteOffset))
|
||||
{
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
|
||||
"1");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (lengthTracking)
|
||||
byteLength = bufferByteLength - byteOffset;
|
||||
|
||||
RootedObject proto(cx, protoArg);
|
||||
RootedObject obj(cx);
|
||||
@@ -1891,57 +1923,72 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
|
||||
}
|
||||
}
|
||||
|
||||
// Caller should have established these preconditions, and no
|
||||
// (non-self-hosted) JS code has had an opportunity to run so nothing can
|
||||
// have invalidated them.
|
||||
MOZ_ASSERT(byteOffset <= arrayBuffer->byteLength());
|
||||
MOZ_ASSERT(byteOffset + byteLength <= arrayBuffer->byteLength());
|
||||
|
||||
DataViewObject& dvobj = obj->as<DataViewObject>();
|
||||
dvobj.setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset));
|
||||
dvobj.setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(byteLength));
|
||||
dvobj.setFixedSlot(TypedArrayObject::LENGTH_SLOT,
|
||||
Int32Value(lengthTracking ? TypedArrayObject::LENGTH_TRACKING
|
||||
: int32_t(byteLength)));
|
||||
dvobj.setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*arrayBuffer));
|
||||
dvobj.initPrivate(arrayBuffer->dataPointer() + byteOffset);
|
||||
auto dataPointer = arrayBuffer->dataPointerEither();
|
||||
dvobj.initPrivate((dataPointer + byteOffset).unwrap(/*safe - stored as private data*/));
|
||||
|
||||
// Include a barrier if the data view's data pointer is in the nursery, as
|
||||
// is done for typed arrays.
|
||||
if (!IsInsideNursery(obj) && cx->runtime()->gc.nursery.isInside(arrayBuffer->dataPointer()))
|
||||
if (arrayBuffer->is<ArrayBufferObject>() &&
|
||||
!IsInsideNursery(obj) &&
|
||||
cx->runtime()->gc.nursery.isInside(dataPointer))
|
||||
{
|
||||
cx->runtime()->gc.storeBuffer.putWholeCell(obj);
|
||||
}
|
||||
|
||||
// Verify that the private slot is at the expected place
|
||||
MOZ_ASSERT(dvobj.numFixedSlots() == TypedArrayObject::DATA_SLOT);
|
||||
|
||||
if (!arrayBuffer->addView(cx, &dvobj))
|
||||
return nullptr;
|
||||
if (arrayBuffer->is<ArrayBufferObject>()) {
|
||||
if (!arrayBuffer->as<ArrayBufferObject>().addView(cx, &dvobj))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &dvobj;
|
||||
}
|
||||
|
||||
bool
|
||||
DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, const CallArgs& args,
|
||||
uint32_t* byteOffsetPtr, uint32_t* byteLengthPtr)
|
||||
uint32_t* byteOffsetPtr, uint32_t* byteLengthPtr,
|
||||
bool* lengthTrackingPtr)
|
||||
{
|
||||
if (!IsArrayBuffer(bufobj)) {
|
||||
if (!IsAnyArrayBuffer(bufobj)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
|
||||
"DataView", "ArrayBuffer", bufobj->getClass()->name);
|
||||
"DataView", "ArrayBuffer or SharedArrayBuffer",
|
||||
bufobj->getClass()->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<ArrayBufferObject*> buffer(cx, &AsArrayBuffer(bufobj));
|
||||
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, &bufobj->as<ArrayBufferObjectMaybeShared>());
|
||||
uint32_t byteOffset = 0;
|
||||
uint32_t byteLength = buffer->byteLength();
|
||||
bool isResizableOrGrowable =
|
||||
(buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
|
||||
(buffer->is<SharedArrayBufferObject>() &&
|
||||
buffer->as<SharedArrayBufferObject>().isGrowable());
|
||||
bool lengthTracking = isResizableOrGrowable && !args.hasDefined(2);
|
||||
|
||||
if (args.length() > 1) {
|
||||
if (!ToUint32(cx, args[1], &byteOffset))
|
||||
uint64_t offset;
|
||||
if (!ToIndex(cx, args[1], &offset))
|
||||
return false;
|
||||
if (byteOffset > INT32_MAX) {
|
||||
if (offset > INT32_MAX) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
|
||||
"1");
|
||||
return false;
|
||||
}
|
||||
byteOffset = uint32_t(offset);
|
||||
}
|
||||
|
||||
if (buffer->isDetached()) {
|
||||
if (buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
@@ -1955,14 +2002,18 @@ DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, cons
|
||||
|
||||
if (args.get(2).isUndefined()) {
|
||||
byteLength -= byteOffset;
|
||||
lengthTracking = isResizableOrGrowable;
|
||||
} else {
|
||||
if (!ToUint32(cx, args[2], &byteLength))
|
||||
uint64_t viewByteLength;
|
||||
if (!ToIndex(cx, args[2], &viewByteLength))
|
||||
return false;
|
||||
if (byteLength > INT32_MAX) {
|
||||
lengthTracking = false;
|
||||
if (viewByteLength > INT32_MAX) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_ARG_INDEX_OUT_OF_RANGE, "2");
|
||||
return false;
|
||||
}
|
||||
byteLength = uint32_t(viewByteLength);
|
||||
|
||||
MOZ_ASSERT(byteOffset + byteLength >= byteOffset,
|
||||
"can't overflow: both numbers are less than INT32_MAX");
|
||||
@@ -1981,6 +2032,7 @@ DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, cons
|
||||
|
||||
*byteOffsetPtr = byteOffset;
|
||||
*byteLengthPtr = byteLength;
|
||||
*lengthTrackingPtr = lengthTracking;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1992,7 +2044,8 @@ DataViewObject::constructSameCompartment(JSContext* cx, HandleObject bufobj, con
|
||||
assertSameCompartment(cx, bufobj);
|
||||
|
||||
uint32_t byteOffset, byteLength;
|
||||
if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength))
|
||||
bool lengthTracking;
|
||||
if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength, &lengthTracking))
|
||||
return false;
|
||||
|
||||
RootedObject proto(cx);
|
||||
@@ -2000,8 +2053,9 @@ DataViewObject::constructSameCompartment(JSContext* cx, HandleObject bufobj, con
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
|
||||
return false;
|
||||
|
||||
Rooted<ArrayBufferObject*> buffer(cx, &AsArrayBuffer(bufobj));
|
||||
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto);
|
||||
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, &bufobj->as<ArrayBufferObjectMaybeShared>());
|
||||
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto,
|
||||
lengthTracking);
|
||||
if (!obj)
|
||||
return false;
|
||||
args.rval().setObject(*obj);
|
||||
@@ -2041,8 +2095,12 @@ DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallA
|
||||
|
||||
// NB: This entails the IsArrayBuffer check
|
||||
uint32_t byteOffset, byteLength;
|
||||
if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset, &byteLength))
|
||||
bool lengthTracking;
|
||||
if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset, &byteLength,
|
||||
&lengthTracking))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure to get the [[Prototype]] for the created view from this
|
||||
// compartment.
|
||||
@@ -2058,11 +2116,12 @@ DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallA
|
||||
return false;
|
||||
}
|
||||
|
||||
FixedInvokeArgs<3> args2(cx);
|
||||
FixedInvokeArgs<4> args2(cx);
|
||||
|
||||
args2[0].set(PrivateUint32Value(byteOffset));
|
||||
args2[1].set(PrivateUint32Value(byteLength));
|
||||
args2[2].setObject(*proto);
|
||||
args2[3].setBoolean(lengthTracking);
|
||||
|
||||
RootedValue fval(cx, global->createDataViewForThis());
|
||||
RootedValue thisv(cx, ObjectValue(*bufobj));
|
||||
@@ -2090,6 +2149,11 @@ template <typename NativeType>
|
||||
/* static */ uint8_t*
|
||||
DataViewObject::getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, uint64_t offset)
|
||||
{
|
||||
if (obj->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATA_VIEW_OUT_OF_BOUNDS);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const size_t TypeSize = sizeof(NativeType);
|
||||
if (offset > UINT32_MAX - TypeSize || offset + TypeSize > obj->byteLength()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
|
||||
@@ -2194,7 +2258,7 @@ DataViewObject::read(JSContext* cx, Handle<DataViewObject*> obj,
|
||||
bool isLittleEndian = args.length() >= 2 && ToBoolean(args[1]);
|
||||
|
||||
// Steps 6-7.
|
||||
if (obj->arrayBuffer().isDetached()) {
|
||||
if (obj->arrayBufferEither().isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
@@ -2293,7 +2357,7 @@ DataViewObject::write(JSContext* cx, Handle<DataViewObject*> obj,
|
||||
bool isLittleEndian = args.length() >= 3 && ToBoolean(args[2]);
|
||||
|
||||
// Steps 7-8.
|
||||
if (obj->arrayBuffer().isDetached()) {
|
||||
if (obj->arrayBufferEither().isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
@@ -3118,7 +3182,13 @@ template<Value ValueGetter(DataViewObject* view)>
|
||||
bool
|
||||
DataViewObject::getterImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
args.rval().set(ValueGetter(&args.thisv().toObject().as<DataViewObject>()));
|
||||
Rooted<DataViewObject*> view(cx, &args.thisv().toObject().as<DataViewObject>());
|
||||
if (ValueGetter != bufferValue && view->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATA_VIEW_OUT_OF_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().set(ValueGetter(view));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+121
-20
@@ -52,6 +52,8 @@ class TypedArrayObject : public NativeObject
|
||||
"right buffer slot");
|
||||
|
||||
// Slot containing length of the view in number of typed elements.
|
||||
// Length-tracking views on resizable/growable buffers store
|
||||
// LENGTH_TRACKING here and compute their visible length from the buffer.
|
||||
static const size_t LENGTH_SLOT = 1;
|
||||
static_assert(LENGTH_SLOT == JS_TYPEDARRAYLAYOUT_LENGTH_SLOT,
|
||||
"self-hosted code with burned-in constants must get the "
|
||||
@@ -65,6 +67,8 @@ class TypedArrayObject : public NativeObject
|
||||
|
||||
static const size_t RESERVED_SLOTS = 3;
|
||||
|
||||
static const int32_t LENGTH_TRACKING = -1;
|
||||
|
||||
#ifdef DEBUG
|
||||
static const uint8_t ZeroLengthArrayData = 0x4A;
|
||||
#endif
|
||||
@@ -139,15 +143,13 @@ class TypedArrayObject : public NativeObject
|
||||
return tarr->getFixedSlot(BUFFER_SLOT);
|
||||
}
|
||||
static Value byteOffsetValue(TypedArrayObject* tarr) {
|
||||
Value v = tarr->getFixedSlot(BYTEOFFSET_SLOT);
|
||||
MOZ_ASSERT(v.toInt32() >= 0);
|
||||
return v;
|
||||
return Int32Value(tarr->byteOffset());
|
||||
}
|
||||
static Value byteLengthValue(TypedArrayObject* tarr) {
|
||||
return Int32Value(tarr->getFixedSlot(LENGTH_SLOT).toInt32() * tarr->bytesPerElement());
|
||||
return Int32Value(tarr->byteLength());
|
||||
}
|
||||
static Value lengthValue(TypedArrayObject* tarr) {
|
||||
return tarr->getFixedSlot(LENGTH_SLOT);
|
||||
return Int32Value(tarr->length());
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -159,14 +161,70 @@ class TypedArrayObject : public NativeObject
|
||||
JSObject* bufferObject() const {
|
||||
return bufferValue(const_cast<TypedArrayObject*>(this)).toObjectOrNull();
|
||||
}
|
||||
bool isLengthTracking() const {
|
||||
return getFixedSlot(LENGTH_SLOT).toInt32() == LENGTH_TRACKING;
|
||||
}
|
||||
bool hasResizableOrGrowableBuffer() const {
|
||||
if (!hasBuffer())
|
||||
return false;
|
||||
if (isSharedMemory())
|
||||
return bufferShared()->isGrowable();
|
||||
return bufferUnshared()->isResizable();
|
||||
}
|
||||
uint32_t byteOffsetMaybeOutOfBounds() const {
|
||||
Value v = getFixedSlot(BYTEOFFSET_SLOT);
|
||||
MOZ_ASSERT(v.toInt32() >= 0);
|
||||
return v.toInt32();
|
||||
}
|
||||
uint32_t fixedLengthMaybeOutOfBounds() const {
|
||||
int32_t length = getFixedSlot(LENGTH_SLOT).toInt32();
|
||||
MOZ_ASSERT(length >= 0);
|
||||
return length;
|
||||
}
|
||||
uint32_t bufferByteLength() const {
|
||||
MOZ_ASSERT(hasBuffer());
|
||||
if (isSharedMemory())
|
||||
return bufferShared()->byteLength();
|
||||
return bufferUnshared()->byteLength();
|
||||
}
|
||||
bool isOutOfBounds() const {
|
||||
if (!hasBuffer())
|
||||
return false;
|
||||
if (hasDetachedBuffer())
|
||||
return true;
|
||||
|
||||
uint32_t bufferByteLength = this->bufferByteLength();
|
||||
uint32_t offset = byteOffsetMaybeOutOfBounds();
|
||||
if (offset > bufferByteLength)
|
||||
return true;
|
||||
|
||||
if (isLengthTracking())
|
||||
return false;
|
||||
|
||||
uint32_t byteLength = fixedLengthMaybeOutOfBounds() * bytesPerElement();
|
||||
return byteLength > bufferByteLength - offset;
|
||||
}
|
||||
uint32_t byteOffset() const {
|
||||
return byteOffsetValue(const_cast<TypedArrayObject*>(this)).toInt32();
|
||||
if (isOutOfBounds())
|
||||
return 0;
|
||||
return byteOffsetMaybeOutOfBounds();
|
||||
}
|
||||
uint32_t byteLength() const {
|
||||
return byteLengthValue(const_cast<TypedArrayObject*>(this)).toInt32();
|
||||
return length() * bytesPerElement();
|
||||
}
|
||||
uint32_t length() const {
|
||||
return lengthValue(const_cast<TypedArrayObject*>(this)).toInt32();
|
||||
if (!isLengthTracking()) {
|
||||
if (isOutOfBounds())
|
||||
return 0;
|
||||
return fixedLengthMaybeOutOfBounds();
|
||||
}
|
||||
|
||||
if (isOutOfBounds())
|
||||
return 0;
|
||||
|
||||
uint32_t bufferByteLength = this->bufferByteLength();
|
||||
uint32_t offset = byteOffsetMaybeOutOfBounds();
|
||||
return (bufferByteLength - offset) / bytesPerElement();
|
||||
}
|
||||
|
||||
bool hasInlineElements() const;
|
||||
@@ -466,28 +524,26 @@ class DataViewObject : public NativeObject
|
||||
defineGetter(JSContext* cx, PropertyName* name, HandleNativeObject proto);
|
||||
|
||||
static bool getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, const CallArgs& args,
|
||||
uint32_t *byteOffset, uint32_t* byteLength);
|
||||
uint32_t *byteOffset, uint32_t* byteLength,
|
||||
bool* lengthTracking);
|
||||
static bool constructSameCompartment(JSContext* cx, HandleObject bufobj, const CallArgs& args);
|
||||
static bool constructWrapped(JSContext* cx, HandleObject bufobj, const CallArgs& args);
|
||||
|
||||
friend bool ArrayBufferObject::createDataViewForThisImpl(JSContext* cx, const CallArgs& args);
|
||||
static DataViewObject*
|
||||
create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
|
||||
Handle<ArrayBufferObject*> arrayBuffer, JSObject* proto);
|
||||
Handle<ArrayBufferObjectMaybeShared*> arrayBuffer, JSObject* proto,
|
||||
bool lengthTracking = false);
|
||||
|
||||
public:
|
||||
static const Class class_;
|
||||
|
||||
static Value byteOffsetValue(DataViewObject* view) {
|
||||
Value v = view->getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT);
|
||||
MOZ_ASSERT(v.toInt32() >= 0);
|
||||
return v;
|
||||
return Int32Value(view->byteOffset());
|
||||
}
|
||||
|
||||
static Value byteLengthValue(DataViewObject* view) {
|
||||
Value v = view->getFixedSlot(TypedArrayObject::LENGTH_SLOT);
|
||||
MOZ_ASSERT(v.toInt32() >= 0);
|
||||
return v;
|
||||
return Int32Value(view->byteLength());
|
||||
}
|
||||
|
||||
static Value bufferValue(DataViewObject* view) {
|
||||
@@ -495,21 +551,66 @@ class DataViewObject : public NativeObject
|
||||
}
|
||||
|
||||
uint32_t byteOffset() const {
|
||||
return byteOffsetValue(const_cast<DataViewObject*>(this)).toInt32();
|
||||
if (isOutOfBounds())
|
||||
return 0;
|
||||
return byteOffsetMaybeOutOfBounds();
|
||||
}
|
||||
|
||||
uint32_t byteLength() const {
|
||||
return byteLengthValue(const_cast<DataViewObject*>(this)).toInt32();
|
||||
if (isOutOfBounds())
|
||||
return 0;
|
||||
if (isLengthTracking())
|
||||
return arrayBufferEither().byteLength() - byteOffsetMaybeOutOfBounds();
|
||||
return fixedByteLengthMaybeOutOfBounds();
|
||||
}
|
||||
|
||||
ArrayBufferObject& arrayBuffer() const {
|
||||
return bufferValue(const_cast<DataViewObject*>(this)).toObject().as<ArrayBufferObject>();
|
||||
ArrayBufferObjectMaybeShared& arrayBufferEither() const {
|
||||
return bufferValue(const_cast<DataViewObject*>(this)).
|
||||
toObject().as<ArrayBufferObjectMaybeShared>();
|
||||
}
|
||||
|
||||
bool isSharedMemory() const {
|
||||
return bufferValue(const_cast<DataViewObject*>(this)).
|
||||
toObject().is<SharedArrayBufferObject>();
|
||||
}
|
||||
|
||||
void* dataPointer() const {
|
||||
return getPrivate();
|
||||
}
|
||||
|
||||
bool isLengthTracking() const {
|
||||
return getFixedSlot(TypedArrayObject::LENGTH_SLOT).toInt32() ==
|
||||
TypedArrayObject::LENGTH_TRACKING;
|
||||
}
|
||||
|
||||
uint32_t byteOffsetMaybeOutOfBounds() const {
|
||||
Value v = getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT);
|
||||
MOZ_ASSERT(v.toInt32() >= 0);
|
||||
return v.toInt32();
|
||||
}
|
||||
|
||||
uint32_t fixedByteLengthMaybeOutOfBounds() const {
|
||||
int32_t length = getFixedSlot(TypedArrayObject::LENGTH_SLOT).toInt32();
|
||||
MOZ_ASSERT(length >= 0);
|
||||
return length;
|
||||
}
|
||||
|
||||
bool isOutOfBounds() const {
|
||||
const ArrayBufferObjectMaybeShared& buffer = arrayBufferEither();
|
||||
if (buffer.isDetached())
|
||||
return true;
|
||||
|
||||
uint32_t bufferByteLength = buffer.byteLength();
|
||||
uint32_t offset = byteOffsetMaybeOutOfBounds();
|
||||
if (offset > bufferByteLength)
|
||||
return true;
|
||||
|
||||
if (isLengthTracking())
|
||||
return false;
|
||||
|
||||
return fixedByteLengthMaybeOutOfBounds() > bufferByteLength - offset;
|
||||
}
|
||||
|
||||
static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
static bool getInt8Impl(JSContext* cx, const CallArgs& args);
|
||||
|
||||
@@ -1442,7 +1442,6 @@ ReloadPrefsCallback(const char* pref, void* data)
|
||||
bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
|
||||
|
||||
bool streams = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams");
|
||||
bool weakRefs = Preferences::GetBool(JS_OPTIONS_DOT_STR "weakrefs");
|
||||
|
||||
bool unboxedObjects = Preferences::GetBool(JS_OPTIONS_DOT_STR "unboxed_objects");
|
||||
|
||||
@@ -1469,8 +1468,7 @@ ReloadPrefsCallback(const char* pref, void* data)
|
||||
.setWerror(werror)
|
||||
.setExtraWarnings(extraWarnings)
|
||||
.setArrayProtoValues(arrayProtoValues)
|
||||
.setStreams(streams)
|
||||
.setWeakRefs(weakRefs);
|
||||
.setStreams(streams);
|
||||
|
||||
JS_SetParallelParsingEnabled(cx, parallelParsing);
|
||||
JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
function assertThrowsTypeError(fn) {
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
do_check_true(e instanceof TypeError);
|
||||
return;
|
||||
}
|
||||
do_throw("expected TypeError");
|
||||
}
|
||||
|
||||
add_task(function* test_finalization_registry_api_surface() {
|
||||
do_check_eq(typeof FinalizationRegistry, "function");
|
||||
do_check_eq(FinalizationRegistry.name, "FinalizationRegistry");
|
||||
do_check_eq(FinalizationRegistry.length, 1);
|
||||
|
||||
assertThrowsTypeError(() => FinalizationRegistry(function() {}));
|
||||
assertThrowsTypeError(() => new FinalizationRegistry(1));
|
||||
|
||||
let registry = new FinalizationRegistry(function() {});
|
||||
do_check_eq(Object.prototype.toString.call(registry), "[object FinalizationRegistry]");
|
||||
do_check_eq(typeof FinalizationRegistry.prototype.register, "function");
|
||||
do_check_eq(FinalizationRegistry.prototype.register.length, 2);
|
||||
do_check_eq(typeof FinalizationRegistry.prototype.unregister, "function");
|
||||
do_check_eq(FinalizationRegistry.prototype.unregister.length, 1);
|
||||
do_check_eq(FinalizationRegistry.prototype.cleanupSome, undefined);
|
||||
|
||||
let desc = Object.getOwnPropertyDescriptor(FinalizationRegistry.prototype,
|
||||
Symbol.toStringTag);
|
||||
do_check_eq(desc.value, "FinalizationRegistry");
|
||||
do_check_eq(desc.writable, false);
|
||||
do_check_eq(desc.enumerable, false);
|
||||
do_check_eq(desc.configurable, true);
|
||||
});
|
||||
|
||||
add_task(function* test_register_validation_and_unregister() {
|
||||
let registry = new FinalizationRegistry(function() {});
|
||||
let target = {};
|
||||
let token = {};
|
||||
|
||||
do_check_eq(registry.register(target, "held"), undefined);
|
||||
do_check_eq(registry.register({}, "held", token), undefined);
|
||||
do_check_eq(registry.unregister(token), true);
|
||||
do_check_eq(registry.unregister({}), false);
|
||||
|
||||
assertThrowsTypeError(() => registry.register(1, "held"));
|
||||
assertThrowsTypeError(() => registry.register(target, target));
|
||||
assertThrowsTypeError(() => registry.register(target, "held", 1));
|
||||
assertThrowsTypeError(() => registry.register(Symbol.for("registered"), "held"));
|
||||
assertThrowsTypeError(() => registry.unregister(undefined));
|
||||
|
||||
do_check_eq(registry.register(Symbol("target"), "held"), undefined);
|
||||
let symbolToken = Symbol("token");
|
||||
do_check_eq(registry.register({}, "held", symbolToken), undefined);
|
||||
do_check_eq(registry.unregister(symbolToken), true);
|
||||
});
|
||||
|
||||
add_task(function* test_proto_from_constructor_realm() {
|
||||
let other = new Components.utils.Sandbox("http://example.com", { freshZone: true });
|
||||
Components.utils.evalInSandbox("var newTarget = new Function();", other);
|
||||
|
||||
for (let proto of [undefined, null, true, "", Symbol(), 1]) {
|
||||
other.newTarget.prototype = proto;
|
||||
let registry = Reflect.construct(FinalizationRegistry, [function() {}],
|
||||
other.newTarget);
|
||||
do_check_true(Object.getPrototypeOf(registry) ===
|
||||
other.FinalizationRegistry.prototype);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_cleanup_callback_after_gc() {
|
||||
let cleaned = [];
|
||||
let registry = new FinalizationRegistry(value => cleaned.push(value));
|
||||
let token = {};
|
||||
|
||||
(function() {
|
||||
let target = {};
|
||||
registry.register(target, "held", token);
|
||||
})();
|
||||
|
||||
for (let i = 0; i < 4 && cleaned.length == 0; i++) {
|
||||
Components.utils.forceGC();
|
||||
yield Promise.resolve();
|
||||
}
|
||||
|
||||
do_check_eq(cleaned.length, 1);
|
||||
do_check_eq(cleaned[0], "held");
|
||||
do_check_eq(registry.unregister(token), false);
|
||||
});
|
||||
|
||||
add_task(function* test_unregister_prevents_cleanup() {
|
||||
let cleaned = [];
|
||||
let registry = new FinalizationRegistry(value => cleaned.push(value));
|
||||
let token = {};
|
||||
|
||||
(function() {
|
||||
let target = {};
|
||||
registry.register(target, "held", token);
|
||||
})();
|
||||
|
||||
do_check_eq(registry.unregister(token), true);
|
||||
Components.utils.forceGC();
|
||||
yield Promise.resolve();
|
||||
do_check_eq(cleaned.length, 0);
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
function assertThrowsTypeError(fn) {
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
do_check_true(e instanceof TypeError);
|
||||
return;
|
||||
}
|
||||
do_throw("expected TypeError");
|
||||
}
|
||||
|
||||
add_task(function* test_weakref_api_surface() {
|
||||
do_check_eq(typeof WeakRef, "function");
|
||||
do_check_eq(WeakRef.name, "WeakRef");
|
||||
do_check_eq(WeakRef.length, 1);
|
||||
|
||||
assertThrowsTypeError(() => WeakRef({}));
|
||||
assertThrowsTypeError(() => new WeakRef(1));
|
||||
assertThrowsTypeError(() => new WeakRef(Symbol.for("registered")));
|
||||
|
||||
let target = {};
|
||||
let ref = new WeakRef(target);
|
||||
do_check_eq(Object.prototype.toString.call(ref), "[object WeakRef]");
|
||||
do_check_eq(ref.deref(), target);
|
||||
do_check_eq(typeof WeakRef.prototype.deref, "function");
|
||||
do_check_eq(WeakRef.prototype.deref.length, 0);
|
||||
|
||||
let desc = Object.getOwnPropertyDescriptor(WeakRef.prototype,
|
||||
Symbol.toStringTag);
|
||||
do_check_eq(desc.value, "WeakRef");
|
||||
do_check_eq(desc.writable, false);
|
||||
do_check_eq(desc.enumerable, false);
|
||||
do_check_eq(desc.configurable, true);
|
||||
});
|
||||
|
||||
add_task(function* test_newtarget_prototype_is_not_object() {
|
||||
function newTarget() {}
|
||||
|
||||
for (let proto of [undefined, null, true, "", Symbol(), 1]) {
|
||||
newTarget.prototype = proto;
|
||||
let ref = Reflect.construct(WeakRef, [{}], newTarget);
|
||||
do_check_true(Object.getPrototypeOf(ref) === WeakRef.prototype);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_proto_from_constructor_realm() {
|
||||
let other = new Components.utils.Sandbox("http://example.com", { freshZone: true });
|
||||
Components.utils.evalInSandbox("var newTarget = new Function();", other);
|
||||
|
||||
for (let proto of [undefined, null, true, "", Symbol(), 1]) {
|
||||
other.newTarget.prototype = proto;
|
||||
let ref = Reflect.construct(WeakRef, [{}], other.newTarget);
|
||||
do_check_true(Object.getPrototypeOf(ref) === other.WeakRef.prototype);
|
||||
}
|
||||
});
|
||||
@@ -71,7 +71,9 @@ support-files =
|
||||
[test_import_fail.js]
|
||||
[test_interposition.js]
|
||||
[test_isModuleLoaded.js]
|
||||
[test_finalization_registry.js]
|
||||
[test_js_weak_references.js]
|
||||
[test_weakref.js]
|
||||
[test_onGarbageCollection-01.js]
|
||||
head = head_ongc.js
|
||||
[test_onGarbageCollection-02.js]
|
||||
|
||||
@@ -1339,9 +1339,6 @@ pref("javascript.options.dynamicImport", true);
|
||||
// Streams API
|
||||
pref("javascript.options.streams", true);
|
||||
|
||||
// Enable garbage collection of weakrefed objects
|
||||
pref("javascript.options.weakrefs", false);
|
||||
|
||||
// advanced prefs
|
||||
pref("advanced.mailftp", false);
|
||||
pref("image.animation_mode", "normal");
|
||||
|
||||
@@ -1479,6 +1479,8 @@ CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
|
||||
|
||||
// Step 4.2 Execute any events that were waiting for a stable state.
|
||||
ProcessStableStateQueue();
|
||||
|
||||
JS::ClearWeakRefKeptObjects(mJSContext);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1517,6 +1519,8 @@ CycleCollectedJSContext::AfterProcessMicrotasks()
|
||||
});
|
||||
RunInStableState(cleanupRunnable.forget());
|
||||
};
|
||||
|
||||
JS::ClearWeakRefKeptObjects(mJSContext);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
|
||||
Reference in New Issue
Block a user