1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 08:17:45 +00:00

LibJS: Stub out Atomics.wait and Atomics.waitAsync

We don't have the facilities to implement these methods fully (namely, a
fully realized SharedArrayBuffer). But we can implement enough to
validate the values passed in by the user.
This commit is contained in:
Timothy Flynn 2023-11-15 16:03:27 -05:00 committed by Tim Flynn
parent a7073c3f1f
commit 78edaad97d
6 changed files with 241 additions and 0 deletions

View file

@ -16,10 +16,12 @@
#include <AK/ByteBuffer.h>
#include <AK/Endian.h>
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/Agent.h>
#include <LibJS/Runtime/AtomicsObject.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
namespace JS {
@ -110,6 +112,61 @@ static ThrowCompletionOr<Value> atomic_read_modify_write(VM& vm, TypedArrayBase&
return typed_array.get_modify_set_value_in_buffer(indexed_position, value_to_set, move(operation));
}
enum class WaitMode {
Sync,
Async,
};
// 25.4.3.14 DoWait ( mode, typedArray, index, value, timeout ), https://tc39.es/ecma262/#sec-dowait
static ThrowCompletionOr<Value> do_wait(VM& vm, WaitMode mode, TypedArrayBase& typed_array, Value index_value, Value expected_value, Value timeout_value)
{
// 1. Let iieoRecord be ? ValidateIntegerTypedArray(typedArray, true).
// 2. Let buffer be iieoRecord.[[Object]].[[ViewedArrayBuffer]].
// FIXME: An IIEO record is a new structure from the resizable array buffer proposal. Use it when the proposal is implemented.
auto* buffer = TRY(validate_integer_typed_array(vm, typed_array, true));
// 3. If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception.
if (!buffer->is_shared_array_buffer())
return vm.throw_completion<TypeError>(ErrorType::NotASharedArrayBuffer);
// 4. Let i be ? ValidateAtomicAccess(iieoRecord, index).
auto index = TRY(validate_atomic_access(vm, typed_array, index_value));
// 5. Let arrayTypeName be typedArray.[[TypedArrayName]].
auto const& array_type_name = typed_array.element_name();
// 6. If arrayTypeName is "BigInt64Array", let v be ? ToBigInt64(value).
i64 value = 0;
if (array_type_name == vm.names.BigInt64Array.as_string())
value = TRY(expected_value.to_bigint_int64(vm));
// 7. Else, let v be ? ToInt32(value).
else
value = TRY(expected_value.to_i32(vm));
// 8. Let q be ? ToNumber(timeout).
auto timeout_number = TRY(timeout_value.to_number(vm));
// 9. If q is either NaN or +∞𝔽, let t be +∞; else if q is -∞𝔽, let t be 0; else let t be max((q), 0).
double timeout = 0;
if (timeout_number.is_nan() || timeout_number.is_positive_infinity())
timeout = js_infinity().as_double();
else if (timeout_number.is_negative_infinity())
timeout = 0.0;
else
timeout = max(timeout_number.as_double(), 0.0);
// 10. If mode is sync and AgentCanSuspend() is false, throw a TypeError exception.
if (mode == WaitMode::Sync && !agent_can_suspend())
return vm.throw_completion<TypeError>(ErrorType::AgentCannotSuspend);
// FIXME: Implement the remaining steps when we support SharedArrayBuffer.
(void)index;
(void)value;
(void)timeout;
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, "SharedArrayBuffer"sv);
}
template<typename T, typename AtomicFunction>
static ThrowCompletionOr<Value> perform_atomic_operation(VM& vm, TypedArrayBase& typed_array, AtomicFunction&& operation)
{
@ -154,6 +211,8 @@ void AtomicsObject::initialize(Realm& realm)
define_native_function(realm, vm.names.or_, or_, 3, attr);
define_native_function(realm, vm.names.store, store, 3, attr);
define_native_function(realm, vm.names.sub, sub, 3, attr);
define_native_function(realm, vm.names.wait, wait, 4, attr);
define_native_function(realm, vm.names.waitAsync, wait_async, 4, attr);
define_native_function(realm, vm.names.xor_, xor_, 3, attr);
// 25.4.15 Atomics [ @@toStringTag ], https://tc39.es/ecma262/#sec-atomics-@@tostringtag
@ -403,6 +462,30 @@ JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::sub)
VERIFY_NOT_REACHED();
}
// 25.4.13 Atomics.wait ( typedArray, index, value, timeout ), https://tc39.es/ecma262/#sec-atomics.wait
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::wait)
{
auto* typed_array = TRY(typed_array_from(vm, vm.argument(0)));
auto index = vm.argument(1);
auto value = vm.argument(2);
auto timeout = vm.argument(3);
// 1. Return ? DoWait(sync, typedArray, index, value, timeout).
return TRY(do_wait(vm, WaitMode::Sync, *typed_array, index, value, timeout));
}
// 25.4.14 Atomics.waitAsync ( typedArray, index, value, timeout ), https://tc39.es/ecma262/#sec-atomics.waitasync
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::wait_async)
{
auto* typed_array = TRY(typed_array_from(vm, vm.argument(0)));
auto index = vm.argument(1);
auto value = vm.argument(2);
auto timeout = vm.argument(3);
// 1. Return ? DoWait(async, typedArray, index, value, timeout).
return TRY(do_wait(vm, WaitMode::Async, *typed_array, index, value, timeout));
}
// 25.4.14 Atomics.xor ( typedArray, index, value ), https://tc39.es/ecma262/#sec-atomics.xor
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::xor_)
{

View file

@ -30,6 +30,8 @@ private:
JS_DECLARE_NATIVE_FUNCTION(or_);
JS_DECLARE_NATIVE_FUNCTION(store);
JS_DECLARE_NATIVE_FUNCTION(sub);
JS_DECLARE_NATIVE_FUNCTION(wait);
JS_DECLARE_NATIVE_FUNCTION(wait_async);
JS_DECLARE_NATIVE_FUNCTION(xor_);
};

View file

@ -575,6 +575,8 @@ namespace JS {
P(value) \
P(valueOf) \
P(values) \
P(wait) \
P(waitAsync) \
P(warn) \
P(weekOfYear) \
P(weekday) \

View file

@ -12,6 +12,7 @@
M(ArrayMaxSize, "Maximum array size exceeded") \
M(AccessorBadField, "Accessor descriptor's '{}' field must be a function or undefined") \
M(AccessorValueOrWritable, "Accessor property descriptor cannot specify a value or writable key") \
M(AgentCannotSuspend, "Agent is not allowed to suspend") \
M(BigIntBadOperator, "Cannot use {} operator with BigInt") \
M(BigIntBadOperatorOtherType, "Cannot use {} operator with BigInt and other type") \
M(BigIntFromNonIntegral, "Cannot convert non-integral number to BigInt") \
@ -94,6 +95,7 @@
M(NotAnObjectOfType, "Not an object of type {}") \
M(NotAnObjectOrNull, "{} is neither an object nor null") \
M(NotAnObjectOrString, "{} is neither an object nor a string") \
M(NotASharedArrayBuffer, "The TypedArray's underlying buffer must be a SharedArrayBuffer") \
M(NotAString, "{} is not a string") \
M(NotASymbol, "{} is not a symbol") \
M(NotImplemented, "TODO({} is not implemented in LibJS)") \

View file

@ -0,0 +1,76 @@
describe("errors", () => {
test("called on non-TypedArray", () => {
expect(() => {
Atomics.wait(Symbol.hasInstance, 0, 0, 0);
}).toThrowWithMessage(TypeError, "Not an object of type TypedArray");
});
test("detached buffer", () => {
expect(() => {
const typedArray = new Int32Array(4);
detachArrayBuffer(typedArray.buffer);
Atomics.wait(typedArray, 0, 0, 0);
}).toThrowWithMessage(TypeError, "ArrayBuffer is detached");
});
test("invalid TypedArray type", () => {
expect(() => {
const typedArray = new Float32Array(4);
Atomics.wait(typedArray, 0, 0, 0);
}).toThrowWithMessage(
TypeError,
"Typed array Float32Array element type is not Int32 or BigInt64"
);
});
test("non-shared ArrayBuffer", () => {
expect(() => {
const typedArray = new Int32Array(4);
Atomics.wait(typedArray, 0, 0, 0);
}).toThrowWithMessage(
TypeError,
"The TypedArray's underlying buffer must be a SharedArrayBuffer"
);
});
test("invalid index", () => {
expect(() => {
const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
const typedArray = new Int32Array(buffer);
Atomics.wait(typedArray, 4, 0, 0);
}).toThrowWithMessage(RangeError, "Index 4 is out of range of array length 4");
});
test("invalid value", () => {
expect(() => {
const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
const typedArray = new Int32Array(buffer);
Atomics.wait(typedArray, 0, Symbol.hasInstance, 0);
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
expect(() => {
const buffer = new SharedArrayBuffer(4 * BigInt64Array.BYTES_PER_ELEMENT);
const typedArray = new BigInt64Array(buffer);
Atomics.wait(typedArray, 0, Symbol.hasInstance, 0);
}).toThrowWithMessage(TypeError, "Cannot convert symbol to BigInt");
});
test("invalid timeout", () => {
expect(() => {
const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
const typedArray = new Int32Array(buffer);
Atomics.wait(typedArray, 0, 0, Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
});
});
test("basic functionality", () => {
test("invariants", () => {
expect(Atomics.wait).toHaveLength(4);
});
});

View file

@ -0,0 +1,76 @@
describe("errors", () => {
test("called on non-TypedArray", () => {
expect(() => {
Atomics.waitAsync(Symbol.hasInstance, 0, 0, 0);
}).toThrowWithMessage(TypeError, "Not an object of type TypedArray");
});
test("detached buffer", () => {
expect(() => {
const typedArray = new Int32Array(4);
detachArrayBuffer(typedArray.buffer);
Atomics.waitAsync(typedArray, 0, 0, 0);
}).toThrowWithMessage(TypeError, "ArrayBuffer is detached");
});
test("invalid TypedArray type", () => {
expect(() => {
const typedArray = new Float32Array(4);
Atomics.waitAsync(typedArray, 0, 0, 0);
}).toThrowWithMessage(
TypeError,
"Typed array Float32Array element type is not Int32 or BigInt64"
);
});
test("non-shared ArrayBuffer", () => {
expect(() => {
const typedArray = new Int32Array(4);
Atomics.waitAsync(typedArray, 0, 0, 0);
}).toThrowWithMessage(
TypeError,
"The TypedArray's underlying buffer must be a SharedArrayBuffer"
);
});
test("invalid index", () => {
expect(() => {
const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
const typedArray = new Int32Array(buffer);
Atomics.waitAsync(typedArray, 4, 0, 0);
}).toThrowWithMessage(RangeError, "Index 4 is out of range of array length 4");
});
test("invalid value", () => {
expect(() => {
const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
const typedArray = new Int32Array(buffer);
Atomics.waitAsync(typedArray, 0, Symbol.hasInstance, 0);
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
expect(() => {
const buffer = new SharedArrayBuffer(4 * BigInt64Array.BYTES_PER_ELEMENT);
const typedArray = new BigInt64Array(buffer);
Atomics.waitAsync(typedArray, 0, Symbol.hasInstance, 0);
}).toThrowWithMessage(TypeError, "Cannot convert symbol to BigInt");
});
test("invalid timeout", () => {
expect(() => {
const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
const typedArray = new Int32Array(buffer);
Atomics.waitAsync(typedArray, 0, 0, Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
});
});
test("basic functionality", () => {
test("invariants", () => {
expect(Atomics.waitAsync).toHaveLength(4);
});
});