LibWeb: Throw range error when initial is greater than maximum

When constructing WebAssembly.Memory if initial is greater than maximum
a range error will be thrown.

Fixes "Initial value exceeds maximum" in
https://wpt.fyi/results/wasm/jsapi/memory/constructor.any.worker.html?product=ladybird
This commit is contained in:
me-it-is 2025-09-07 17:34:45 -07:00 committed by Ali Mohammad Pur
parent 6d3fd2b543
commit 62fe795c9b
4 changed files with 189 additions and 0 deletions

View File

@ -23,6 +23,11 @@ WebIDL::ExceptionOr<GC::Ref<Memory>> Memory::construct_impl(JS::Realm& realm, Me
auto& vm = realm.vm();
// https://webassembly.github.io/threads/js-api/index.html#dom-memory-memory
// 3. If maximum is not empty and maximum < initial, throw a RangeError exception.
if (descriptor.maximum.has_value() && descriptor.maximum.value() < descriptor.initial) {
return vm.throw_completion<JS::RangeError>("Initial is larger than maximum."sv);
}
// 4. Let share be shared if descriptor["shared"] is true and unshared otherwise.
// 5. If share is shared and maximum is empty, throw a TypeError exception.
auto shared = descriptor.shared.value_or(false);

View File

@ -0,0 +1,29 @@
Harness status: OK
Found 24 tests
24 Pass
Pass name
Pass length
Pass No arguments
Pass Calling
Pass Invalid descriptor argument
Pass Undefined initial value in descriptor
Pass Out-of-range initial value in descriptor: NaN
Pass Out-of-range maximum value in descriptor: NaN
Pass Out-of-range initial value in descriptor: Infinity
Pass Out-of-range maximum value in descriptor: Infinity
Pass Out-of-range initial value in descriptor: -Infinity
Pass Out-of-range maximum value in descriptor: -Infinity
Pass Out-of-range initial value in descriptor: -1
Pass Out-of-range maximum value in descriptor: -1
Pass Out-of-range initial value in descriptor: 4294967296
Pass Out-of-range maximum value in descriptor: 4294967296
Pass Out-of-range initial value in descriptor: 68719476736
Pass Out-of-range maximum value in descriptor: 68719476736
Pass Initial value exceeds maximum
Pass Proxy descriptor
Pass Order of evaluation for descriptor
Pass Zero initial
Pass Non-zero initial
Pass Stray argument

View File

@ -0,0 +1,16 @@
<!doctype html>
<meta charset=utf-8>
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../wasm/jsapi/assertions.js"></script>
<script src="../../../wasm/jsapi/memory/assertions.js"></script>
<div id=log></div>
<script src="../../../wasm/jsapi/memory/constructor.any.js"></script>

View File

@ -0,0 +1,139 @@
// META: global=window,dedicatedworker,jsshell,shadowrealm
// META: script=/wasm/jsapi/assertions.js
// META: script=/wasm/jsapi/memory/assertions.js
test(() => {
assert_function_name(WebAssembly.Memory, "Memory", "WebAssembly.Memory");
}, "name");
test(() => {
assert_function_length(WebAssembly.Memory, 1, "WebAssembly.Memory");
}, "length");
test(() => {
assert_throws_js(TypeError, () => new WebAssembly.Memory());
}, "No arguments");
test(() => {
const argument = { "initial": 0 };
assert_throws_js(TypeError, () => WebAssembly.Memory(argument));
}, "Calling");
test(() => {
const invalidArguments = [
undefined,
null,
false,
true,
"",
"test",
Symbol(),
1,
NaN,
{},
];
for (const invalidArgument of invalidArguments) {
assert_throws_js(TypeError,
() => new WebAssembly.Memory(invalidArgument),
`new Memory(${format_value(invalidArgument)})`);
}
}, "Invalid descriptor argument");
test(() => {
assert_throws_js(TypeError, () => new WebAssembly.Memory({ "initial": undefined }));
}, "Undefined initial value in descriptor");
const outOfRangeValues = [
NaN,
Infinity,
-Infinity,
-1,
0x100000000,
0x1000000000,
];
for (const value of outOfRangeValues) {
test(() => {
assert_throws_js(TypeError, () => new WebAssembly.Memory({ "initial": value }));
}, `Out-of-range initial value in descriptor: ${format_value(value)}`);
test(() => {
assert_throws_js(TypeError, () => new WebAssembly.Memory({ "initial": 0, "maximum": value }));
}, `Out-of-range maximum value in descriptor: ${format_value(value)}`);
}
test(() => {
assert_throws_js(RangeError, () => new WebAssembly.Memory({ "initial": 10, "maximum": 9 }));
}, "Initial value exceeds maximum");
test(() => {
const proxy = new Proxy({}, {
has(o, x) {
assert_unreached(`Should not call [[HasProperty]] with ${x}`);
},
get(o, x) {
// Due to the requirement not to supply both minimum and initial, we need to ignore one of them.
switch (x) {
case "shared":
return false;
case "initial":
case "maximum":
return 0;
default:
return undefined;
}
},
});
new WebAssembly.Memory(proxy);
}, "Proxy descriptor");
test(() => {
const order = [];
new WebAssembly.Memory({
get maximum() {
order.push("maximum");
return {
valueOf() {
order.push("maximum valueOf");
return 1;
},
};
},
get initial() {
order.push("initial");
return {
valueOf() {
order.push("initial valueOf");
return 1;
},
};
},
});
assert_array_equals(order, [
"initial",
"initial valueOf",
"maximum",
"maximum valueOf",
]);
}, "Order of evaluation for descriptor");
test(() => {
const argument = { "initial": 0 };
const memory = new WebAssembly.Memory(argument);
assert_Memory(memory, { "size": 0 });
}, "Zero initial");
test(() => {
const argument = { "initial": 4 };
const memory = new WebAssembly.Memory(argument);
assert_Memory(memory, { "size": 4 });
}, "Non-zero initial");
test(() => {
const argument = { "initial": 0 };
const memory = new WebAssembly.Memory(argument, {});
assert_Memory(memory, { "size": 0 });
}, "Stray argument");