Commit fc277117 authored by Sven Sauleau's avatar Sven Sauleau Committed by Commit Bot

[wasm] fix js-api table/grow

Fix WebAssembly's table/grow js-api. The argument is a unsigned long,
this change refactors most of arithmetic and bounds checks type from
int64 to uint32_t, according to the spec.

Bug: v8:8319
Cq-Include-Trybots: luci.chromium.try:linux-blink-rel
Change-Id: Ia29121c930d7fb930668e54a5a769dae25234f2c
Reviewed-on: https://chromium-review.googlesource.com/c/1351006
Commit-Queue: Sven Sauleau <ssauleau@igalia.com>
Reviewed-by: 's avatarAdam Klein <adamk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#58936}
parent c6168d1e
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "src/wasm/wasm-js.h" #include "src/wasm/wasm-js.h"
#include <string>
#include "src/api-inl.h" #include "src/api-inl.h"
#include "src/api-natives.h" #include "src/api-natives.h"
#include "src/assert-scope.h" #include "src/assert-scope.h"
...@@ -438,40 +440,46 @@ class AsyncInstantiateCompileResultResolver ...@@ -438,40 +440,46 @@ class AsyncInstantiateCompileResultResolver
constexpr char AsyncInstantiateCompileResultResolver::kGlobalPromiseHandle[]; constexpr char AsyncInstantiateCompileResultResolver::kGlobalPromiseHandle[];
constexpr char AsyncInstantiateCompileResultResolver::kGlobalImportsHandle[]; constexpr char AsyncInstantiateCompileResultResolver::kGlobalImportsHandle[];
} // namespace
std::string ToString(const char* name) { return std::string(name); }
std::string ToString(const i::Handle<i::String> name) {
return std::string("Property '") + name->ToCString().get() + "'";
}
// Web IDL: '[EnforceRange] unsigned long' // Web IDL: '[EnforceRange] unsigned long'
// Previously called ToNonWrappingUint32 in the draft WebAssembly JS spec. // Previously called ToNonWrappingUint32 in the draft WebAssembly JS spec.
// https://heycam.github.io/webidl/#EnforceRange // https://heycam.github.io/webidl/#EnforceRange
bool EnforceUint32(i::Handle<i::String> argument_name, Local<v8::Value> v, template <typename T>
Local<Context> context, ErrorThrower* thrower, bool EnforceUint32(T argument_name, Local<v8::Value> v, Local<Context> context,
uint32_t* res) { ErrorThrower* thrower, uint32_t* res) {
double double_number; double double_number;
if (!v->NumberValue(context).To(&double_number)) { if (!v->NumberValue(context).To(&double_number)) {
thrower->TypeError("Property '%s' must be convertible to a number", thrower->TypeError("%s must be convertible to a number",
argument_name->ToCString().get()); ToString(argument_name).c_str());
return false; return false;
} }
if (!std::isfinite(double_number)) { if (!std::isfinite(double_number)) {
thrower->TypeError("Property '%s' must be convertible to a valid number", thrower->TypeError("%s must be convertible to a valid number",
argument_name->ToCString().get()); ToString(argument_name).c_str());
return false; return false;
} }
if (double_number < 0) { if (double_number < 0) {
thrower->TypeError("Property '%s' must be non-negative", thrower->TypeError("%s must be non-negative",
argument_name->ToCString().get()); ToString(argument_name).c_str());
return false; return false;
} }
if (double_number > std::numeric_limits<uint32_t>::max()) { if (double_number > std::numeric_limits<uint32_t>::max()) {
thrower->TypeError("Property '%s' must be in the unsigned long range", thrower->TypeError("%s must be in the unsigned long range",
argument_name->ToCString().get()); ToString(argument_name).c_str());
return false; return false;
} }
*res = static_cast<uint32_t>(double_number); *res = static_cast<uint32_t>(double_number);
return true; return true;
} }
} // namespace
// WebAssembly.compile(bytes) -> Promise // WebAssembly.compile(bytes) -> Promise
void WebAssemblyCompile(const v8::FunctionCallbackInfo<v8::Value>& args) { void WebAssemblyCompile(const v8::FunctionCallbackInfo<v8::Value>& args) {
...@@ -1301,31 +1309,39 @@ void WebAssemblyTableGrow(const v8::FunctionCallbackInfo<v8::Value>& args) { ...@@ -1301,31 +1309,39 @@ void WebAssemblyTableGrow(const v8::FunctionCallbackInfo<v8::Value>& args) {
Local<Context> context = isolate->GetCurrentContext(); Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(receiver, WasmTableObject); EXTRACT_THIS(receiver, WasmTableObject);
int64_t grow_by = 0; uint32_t grow_by;
if (!args[0]->IntegerValue(context).To(&grow_by)) return; if (!EnforceUint32("Argument 0", args[0], context, &thrower, &grow_by)) {
return;
}
i::Handle<i::FixedArray> old_array(receiver->functions(), i_isolate); i::Handle<i::FixedArray> old_array(receiver->functions(), i_isolate);
int old_size = old_array->length(); uint32_t old_size = static_cast<uint32_t>(old_array->length());
int64_t max_size64 = receiver->maximum_length()->Number(); uint64_t max_size64 = receiver->maximum_length()->Number();
if (max_size64 < 0 || max_size64 > i::FLAG_wasm_max_table_size) { if (max_size64 > i::FLAG_wasm_max_table_size) {
max_size64 = i::FLAG_wasm_max_table_size; max_size64 = i::FLAG_wasm_max_table_size;
} }
if (grow_by < 0 || grow_by > max_size64 - old_size) { DCHECK_LE(max_size64, std::numeric_limits<uint32_t>::max());
thrower.RangeError(grow_by < 0 ? "trying to shrink table"
: "maximum table size exceeded"); uint64_t new_size64 =
static_cast<uint64_t>(old_size) + static_cast<uint64_t>(grow_by);
if (new_size64 > max_size64) {
thrower.RangeError("maximum table size exceeded");
return; return;
} }
uint32_t new_size = static_cast<uint32_t>(new_size64);
int new_size = static_cast<int>(old_size + grow_by);
receiver->Grow(i_isolate, static_cast<uint32_t>(new_size - old_size));
if (new_size != old_size) { if (new_size != old_size) {
receiver->Grow(i_isolate, new_size - old_size);
i::Handle<i::FixedArray> new_array = i::Handle<i::FixedArray> new_array =
i_isolate->factory()->NewFixedArray(new_size); i_isolate->factory()->NewFixedArray(new_size);
for (int i = 0; i < old_size; ++i) new_array->set(i, old_array->get(i)); for (uint32_t i = 0; i < old_size; ++i) {
new_array->set(i, old_array->get(i));
}
i::Object null = i::ReadOnlyRoots(i_isolate).null_value(); i::Object null = i::ReadOnlyRoots(i_isolate).null_value();
for (int i = old_size; i < new_size; ++i) new_array->set(i, null); for (uint32_t i = old_size; i < new_size; ++i) new_array->set(i, null);
receiver->set_functions(*new_array); receiver->set_functions(*new_array);
} }
...@@ -1343,15 +1359,19 @@ void WebAssemblyTableGet(const v8::FunctionCallbackInfo<v8::Value>& args) { ...@@ -1343,15 +1359,19 @@ void WebAssemblyTableGet(const v8::FunctionCallbackInfo<v8::Value>& args) {
Local<Context> context = isolate->GetCurrentContext(); Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(receiver, WasmTableObject); EXTRACT_THIS(receiver, WasmTableObject);
i::Handle<i::FixedArray> array(receiver->functions(), i_isolate); i::Handle<i::FixedArray> array(receiver->functions(), i_isolate);
int64_t i = 0;
if (!args[0]->IntegerValue(context).To(&i)) return; uint32_t index;
if (!EnforceUint32("Argument 0", args[0], context, &thrower, &index)) {
return;
}
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue(); v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
if (i < 0 || i >= array->length()) { if (index >= static_cast<uint32_t>(array->length())) {
thrower.RangeError("index out of bounds"); thrower.RangeError("Index out of bounds");
return; return;
} }
i::Handle<i::Object> value(array->get(static_cast<int>(i)), i_isolate); i::Handle<i::Object> value(array->get(static_cast<int>(index)), i_isolate);
return_value.Set(Utils::ToLocal(value)); return_value.Set(Utils::ToLocal(value));
} }
......
...@@ -14,4 +14,4 @@ let table = new WebAssembly.Table({element: "anyfunc", ...@@ -14,4 +14,4 @@ let table = new WebAssembly.Table({element: "anyfunc",
initial: 1, maximum:1000000}); initial: 1, maximum:1000000});
let instance = new WebAssembly.Instance(module, {x: {table:table}}); let instance = new WebAssembly.Instance(module, {x: {table:table}});
assertThrows(() => table.grow(Infinity), RangeError); assertThrows(() => table.grow(Infinity), TypeError);
...@@ -651,7 +651,8 @@ assertErrorMessage( ...@@ -651,7 +651,8 @@ assertErrorMessage(
() => get.call(), TypeError, /called on incompatible undefined/); () => get.call(), TypeError, /called on incompatible undefined/);
assertErrorMessage( assertErrorMessage(
() => get.call({}), TypeError, /called on incompatible Object/); () => get.call({}), TypeError, /called on incompatible Object/);
assertEq(get.call(tbl1), null); assertErrorMessage(
() => get.call(tbl1), TypeError, /Argument 0 must be convertible to a valid number/);
assertEq(get.call(tbl1, 0), null); assertEq(get.call(tbl1, 0), null);
assertEq(get.call(tbl1, 0, Infinity), null); assertEq(get.call(tbl1, 0, Infinity), null);
assertEq(get.call(tbl1, 1), null); assertEq(get.call(tbl1, 1), null);
...@@ -659,9 +660,9 @@ assertEq(get.call(tbl1, 1.5), null); ...@@ -659,9 +660,9 @@ assertEq(get.call(tbl1, 1.5), null);
assertErrorMessage(() => get.call(tbl1, 2), RangeError, /bad Table get index/); assertErrorMessage(() => get.call(tbl1, 2), RangeError, /bad Table get index/);
assertErrorMessage( assertErrorMessage(
() => get.call(tbl1, 2.5), RangeError, /bad Table get index/); () => get.call(tbl1, 2.5), RangeError, /bad Table get index/);
assertErrorMessage(() => get.call(tbl1, -1), RangeError, /bad Table get index/); assertErrorMessage(() => get.call(tbl1, -1), TypeError, /bad Table get index/);
assertErrorMessage( assertErrorMessage(
() => get.call(tbl1, Math.pow(2, 33)), RangeError, /bad Table get index/); () => get.call(tbl1, Math.pow(2, 33)), TypeError, /bad Table get index/);
assertErrorMessage( assertErrorMessage(
() => get.call(tbl1, {valueOf() { throw new Error('hi') }}), Error, 'hi'); () => get.call(tbl1, {valueOf() { throw new Error('hi') }}), Error, 'hi');
...@@ -729,27 +730,26 @@ assertErrorMessage( ...@@ -729,27 +730,26 @@ assertErrorMessage(
assertErrorMessage( assertErrorMessage(
() => tblGrow.call({}), TypeError, /called on incompatible Object/); () => tblGrow.call({}), TypeError, /called on incompatible Object/);
assertErrorMessage( assertErrorMessage(
() => tblGrow.call(tbl1, -1), RangeError, /bad Table grow delta/); () => tblGrow.call(tbl1, -1), TypeError, /bad Table grow delta/);
assertErrorMessage( assertErrorMessage(
() => tblGrow.call(tbl1, Math.pow(2, 32)), RangeError, () => tblGrow.call(tbl1, Math.pow(2, 32)), TypeError,
/bad Table grow delta/); /bad Table grow delta/);
var tbl = new Table({element: 'anyfunc', initial: 1, maximum: 2}); var tbl = new Table({element: 'anyfunc', initial: 1, maximum: 2});
assertEq(tbl.length, 1); assertEq(tbl.length, 1);
assertErrorMessage( assertErrorMessage(
() => tbl.grow(Infinity), RangeError, /failed to grow table/); () => tbl.grow(Infinity), TypeError, /failed to grow table/);
assertErrorMessage( assertErrorMessage(
() => tbl.grow(-Infinity), RangeError, /failed to grow table/); () => tbl.grow(-Infinity), TypeError, /failed to grow table/);
assertEq(tbl.grow(0), 1); assertEq(tbl.grow(0), 1);
assertEq(tbl.length, 1); assertEq(tbl.length, 1);
assertEq(tbl.grow(1, 4), 1); assertEq(tbl.grow(1, 4), 1);
assertEq(tbl.length, 2); assertEq(tbl.length, 2);
assertEq(tbl.grow(), 2);
assertEq(tbl.length, 2); assertEq(tbl.length, 2);
assertErrorMessage(() => tbl.grow(1), Error, /failed to grow table/); assertErrorMessage(() => tbl.grow(1), Error, /failed to grow table/);
assertErrorMessage( assertErrorMessage(
() => tbl.grow(Infinity), RangeError, /failed to grow table/); () => tbl.grow(Infinity), TypeError, /failed to grow table/);
assertErrorMessage( assertErrorMessage(
() => tbl.grow(-Infinity), RangeError, /failed to grow table/); () => tbl.grow(-Infinity), TypeError, /failed to grow table/);
// 'WebAssembly.validate' function // 'WebAssembly.validate' function
assertErrorMessage(() => WebAssembly.validate(), TypeError); assertErrorMessage(() => WebAssembly.validate(), TypeError);
......
...@@ -141,10 +141,13 @@ function assertTableIsValid(table, length) { ...@@ -141,10 +141,13 @@ function assertTableIsValid(table, length) {
assertEquals(null, table.get(i)); assertEquals(null, table.get(i));
assertEquals(null, table.get(String(i))); assertEquals(null, table.get(String(i)));
} }
for (let key of [0.4, "", NaN, {}, [], () => {}]) { for (let key of [0.4, "", []]) {
assertEquals(null, table.get(key)); assertEquals(null, table.get(key));
} }
for (let key of [-1, table.length, table.length * 10]) { for (let key of [-1, NaN, {}, () => {}]) {
assertThrows(() => table.get(key), TypeError);
}
for (let key of [table.length, table.length * 10]) {
assertThrows(() => table.get(key), RangeError); assertThrows(() => table.get(key), RangeError);
} }
assertThrows(() => table.get(Symbol()), TypeError); assertThrows(() => table.get(Symbol()), TypeError);
...@@ -214,7 +217,12 @@ function assertTableIsValid(table, length) { ...@@ -214,7 +217,12 @@ function assertTableIsValid(table, length) {
assertSame(f, table[i]); assertSame(f, table[i]);
} }
for (let key of [0.4, "", NaN, {}, [], () => {}]) { for (let key of [NaN, {}, () => {}]) {
assertSame(f, table[key] = f);
assertSame(f, table[key]);
assertThrows(() => table.get(key), TypeError);
}
for (let key of [0.4, "", []]) {
assertSame(f, table[key] = f); assertSame(f, table[key] = f);
assertSame(f, table[key]); assertSame(f, table[key]);
assertSame(null, table.get(key)); assertSame(null, table.get(key));
...@@ -246,7 +254,7 @@ function assertTableIsValid(table, length) { ...@@ -246,7 +254,7 @@ function assertTableIsValid(table, length) {
check(table); check(table);
table.grow(10); table.grow(10);
check(table); check(table);
assertThrows(() => table.grow(-10), RangeError); assertThrows(() => table.grow(-10), TypeError);
table = new WebAssembly.Table({element: "anyfunc", initial: 20, maximum: 25}); table = new WebAssembly.Table({element: "anyfunc", initial: 20, maximum: 25});
init(table); init(table);
...@@ -258,7 +266,7 @@ function assertTableIsValid(table, length) { ...@@ -258,7 +266,7 @@ function assertTableIsValid(table, length) {
table.grow(0); table.grow(0);
check(table); check(table);
assertThrows(() => table.grow(1), RangeError); assertThrows(() => table.grow(1), RangeError);
assertThrows(() => table.grow(-10), RangeError); assertThrows(() => table.grow(-10), TypeError);
assertThrows(() => WebAssembly.Table.prototype.grow.call([], 0), TypeError); assertThrows(() => WebAssembly.Table.prototype.grow.call([], 0), TypeError);
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
[ALWAYS, { [ALWAYS, {
# https://bugs.chromium.org/p/v8/issues/detail?id=8319 # https://bugs.chromium.org/p/v8/issues/detail?id=8319
'memory/grow': [FAIL], 'memory/grow': [FAIL],
'table/grow': [FAIL],
'table/get-set': [FAIL], 'table/get-set': [FAIL],
'module/customSections': [FAIL], 'module/customSections': [FAIL],
}], # ALWAYS }], # ALWAYS
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment