[wasm][eh] Encode values in WebAssembly.Exception

R=jkummerow@chromium.org

Bug: v8:11992
Change-Id: If62f2cdc080364dec796a836321110bf571769ef
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3049075
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75937}
This commit is contained in:
Thibaud Michaud 2021-07-27 14:45:07 +02:00 committed by V8 LUCI CQ
parent d938c10891
commit b86db1396a
5 changed files with 247 additions and 40 deletions

View File

@ -20,6 +20,7 @@
#include "src/handles/handles.h"
#include "src/heap/factory.h"
#include "src/init/v8.h"
#include "src/objects/fixed-array.h"
#include "src/objects/js-promise-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/templates.h"
@ -1231,6 +1232,48 @@ bool GetValueType(Isolate* isolate, MaybeLocal<Value> maybe,
return true;
}
namespace {
bool ToI32(Local<v8::Value> value, Local<Context> context, int32_t* i32_value) {
if (!value->IsUndefined()) {
v8::Local<v8::Int32> int32_value;
if (!value->ToInt32(context).ToLocal(&int32_value)) return false;
if (!int32_value->Int32Value(context).To(i32_value)) return false;
}
return true;
}
bool ToI64(Local<v8::Value> value, Local<Context> context, int64_t* i64_value) {
if (!value->IsUndefined()) {
v8::Local<v8::BigInt> bigint_value;
if (!value->ToBigInt(context).ToLocal(&bigint_value)) return false;
*i64_value = bigint_value->Int64Value();
}
return true;
}
bool ToF32(Local<v8::Value> value, Local<Context> context, float* f32_value) {
if (!value->IsUndefined()) {
double f64_value = 0;
v8::Local<v8::Number> number_value;
if (!value->ToNumber(context).ToLocal(&number_value)) return false;
if (!number_value->NumberValue(context).To(&f64_value)) return false;
*f32_value = i::DoubleToFloat32(f64_value);
}
return true;
}
bool ToF64(Local<v8::Value> value, Local<Context> context, double* f64_value) {
if (!value->IsUndefined()) {
v8::Local<v8::Number> number_value;
if (!value->ToNumber(context).ToLocal(&number_value)) return false;
if (!number_value->NumberValue(context).To(f64_value)) return false;
}
return true;
}
} // namespace
// WebAssembly.Global
void WebAssemblyGlobal(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
@ -1296,43 +1339,25 @@ void WebAssemblyGlobal(const v8::FunctionCallbackInfo<v8::Value>& args) {
switch (type.kind()) {
case i::wasm::kI32: {
int32_t i32_value = 0;
if (!value->IsUndefined()) {
v8::Local<v8::Int32> int32_value;
if (!value->ToInt32(context).ToLocal(&int32_value)) return;
if (!int32_value->Int32Value(context).To(&i32_value)) return;
}
if (!ToI32(value, context, &i32_value)) return;
global_obj->SetI32(i32_value);
break;
}
case i::wasm::kI64: {
int64_t i64_value = 0;
if (!value->IsUndefined()) {
v8::Local<v8::BigInt> bigint_value;
if (!value->ToBigInt(context).ToLocal(&bigint_value)) return;
i64_value = bigint_value->Int64Value();
}
if (!ToI64(value, context, &i64_value)) return;
global_obj->SetI64(i64_value);
break;
}
case i::wasm::kF32: {
float f32_value = 0;
if (!value->IsUndefined()) {
double f64_value = 0;
v8::Local<v8::Number> number_value;
if (!value->ToNumber(context).ToLocal(&number_value)) return;
if (!number_value->NumberValue(context).To(&f64_value)) return;
f32_value = i::DoubleToFloat32(f64_value);
}
if (!ToF32(value, context, &f32_value)) return;
global_obj->SetF32(f32_value);
break;
}
case i::wasm::kF64: {
double f64_value = 0;
if (!value->IsUndefined()) {
v8::Local<v8::Number> number_value;
if (!value->ToNumber(context).ToLocal(&number_value)) return;
if (!number_value->NumberValue(context).To(&f64_value)) return;
}
if (!ToF64(value, context, &f64_value)) return;
global_obj->SetF64(f64_value);
break;
}
@ -1471,6 +1496,93 @@ void WebAssemblyTag(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(Utils::ToLocal(exception));
}
namespace {
uint32_t GetEncodedSize(i::Handle<i::WasmExceptionObject> tag_object) {
auto serialized_sig = tag_object->serialized_signature();
i::wasm::WasmExceptionSig sig{0, static_cast<size_t>(serialized_sig.length()),
reinterpret_cast<i::wasm::ValueType*>(
serialized_sig.GetDataStartAddress())};
i::wasm::WasmException exception(&sig);
return i::WasmExceptionPackage::GetEncodedSize(&exception);
}
void EncodeExceptionValues(v8::Isolate* isolate,
i::PodArray<i::wasm::ValueType> signature,
const Local<Value>& arg,
ScheduledErrorThrower* thrower,
i::Handle<i::FixedArray> values_out) {
Local<Context> context = isolate->GetCurrentContext();
uint32_t index = 0;
if (!arg->IsObject()) {
thrower->TypeError("Exception values must be an iterable object");
return;
}
auto values = arg.As<Object>();
for (int i = 0; i < signature.length(); ++i) {
MaybeLocal<Value> maybe_value = values->Get(context, i);
Local<Value> value = maybe_value.ToLocalChecked();
i::wasm::ValueType type = signature.get(i);
switch (type.kind()) {
case i::wasm::kI32: {
int32_t i32 = 0;
if (!ToI32(value, context, &i32)) return;
i::EncodeI32ExceptionValue(values_out, &index, i32);
break;
}
case i::wasm::kI64: {
int64_t i64 = 0;
if (!ToI64(value, context, &i64)) return;
i::EncodeI64ExceptionValue(values_out, &index, i64);
break;
}
case i::wasm::kF32: {
float f32 = 0;
if (!ToF32(value, context, &f32)) return;
int32_t i32 = bit_cast<int32_t>(f32);
i::EncodeI32ExceptionValue(values_out, &index, i32);
break;
}
case i::wasm::kF64: {
double f64 = 0;
if (!ToF64(value, context, &f64)) return;
int64_t i64 = bit_cast<int64_t>(f64);
i::EncodeI64ExceptionValue(values_out, &index, i64);
break;
}
case i::wasm::kRef:
case i::wasm::kOptRef:
switch (type.heap_representation()) {
case i::wasm::HeapType::kExtern:
case i::wasm::HeapType::kFunc:
case i::wasm::HeapType::kAny:
case i::wasm::HeapType::kEq:
case i::wasm::HeapType::kI31:
case i::wasm::HeapType::kData:
values_out->set(index++, *Utils::OpenHandle(*value));
break;
case internal::wasm::HeapType::kBottom:
UNREACHABLE();
default:
// TODO(7748): Add support for custom struct/array types.
UNIMPLEMENTED();
}
break;
case i::wasm::kRtt:
case i::wasm::kRttWithDepth:
case i::wasm::kI8:
case i::wasm::kI16:
case i::wasm::kVoid:
case i::wasm::kBottom:
case i::wasm::kS128:
UNREACHABLE();
break;
}
}
}
} // namespace
void WebAssemblyException(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
@ -1493,10 +1605,16 @@ void WebAssemblyException(const v8::FunctionCallbackInfo<v8::Value>& args) {
auto tag_object = i::Handle<i::WasmExceptionObject>::cast(arg0);
auto tag = i::Handle<i::WasmExceptionTag>(
i::WasmExceptionTag::cast(tag_object->exception_tag()), i_isolate);
// TODO(thibaudm): Encode arguments in the exception package.
uint32_t size = 0;
uint32_t size = GetEncodedSize(tag_object);
i::Handle<i::WasmExceptionPackage> runtime_exception =
i::WasmExceptionPackage::New(i_isolate, tag, size);
// The constructor above should guarantee that the cast below succeeds.
auto values = i::Handle<i::FixedArray>::cast(
i::WasmExceptionPackage::GetExceptionValues(i_isolate,
runtime_exception));
auto signature = tag_object->serialized_signature();
EncodeExceptionValues(isolate, signature, args[1], &thrower, values);
if (thrower.error()) return;
args.GetReturnValue().Set(
Utils::ToLocal(i::Handle<i::Object>::cast(runtime_exception)));
}

View File

@ -1812,6 +1812,20 @@ Handle<Object> WasmExceptionPackage::GetExceptionValues(
return ReadOnlyRoots(isolate).undefined_value_handle();
}
void EncodeI32ExceptionValue(Handle<FixedArray> encoded_values,
uint32_t* encoded_index, uint32_t value) {
encoded_values->set((*encoded_index)++, Smi::FromInt(value >> 16));
encoded_values->set((*encoded_index)++, Smi::FromInt(value & 0xffff));
}
void EncodeI64ExceptionValue(Handle<FixedArray> encoded_values,
uint32_t* encoded_index, uint64_t value) {
EncodeI32ExceptionValue(encoded_values, encoded_index,
static_cast<uint32_t>(value >> 32));
EncodeI32ExceptionValue(encoded_values, encoded_index,
static_cast<uint32_t>(value));
}
#ifdef DEBUG
namespace {

View File

@ -590,6 +590,12 @@ class V8_EXPORT_PRIVATE WasmExceptionPackage : public JSObject {
OBJECT_CONSTRUCTORS(WasmExceptionPackage, JSObject);
};
void V8_EXPORT_PRIVATE EncodeI32ExceptionValue(
Handle<FixedArray> encoded_values, uint32_t* encoded_index, uint32_t value);
void V8_EXPORT_PRIVATE EncodeI64ExceptionValue(
Handle<FixedArray> encoded_values, uint32_t* encoded_index, uint64_t value);
// A Wasm function that is wrapped and exported to JavaScript.
// Representation of WebAssembly.Function JavaScript-level object.
class WasmExportedFunction : public JSFunction {

View File

@ -3083,20 +3083,6 @@ class WasmInterpreterInternals {
return false;
}
void EncodeI32ExceptionValue(Handle<FixedArray> encoded_values,
uint32_t* encoded_index, uint32_t value) {
encoded_values->set((*encoded_index)++, Smi::FromInt(value >> 16));
encoded_values->set((*encoded_index)++, Smi::FromInt(value & 0xffff));
}
void EncodeI64ExceptionValue(Handle<FixedArray> encoded_values,
uint32_t* encoded_index, uint64_t value) {
EncodeI32ExceptionValue(encoded_values, encoded_index,
static_cast<uint32_t>(value >> 32));
EncodeI32ExceptionValue(encoded_values, encoded_index,
static_cast<uint32_t>(value));
}
// Allocate, initialize and throw a new exception. The exception values are
// being popped off the operand stack. Returns true if the exception is being
// handled locally by the interpreter, false otherwise (interpreter exits).

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-eh
// Flags: --experimental-wasm-eh --experimental-wasm-reftypes
load("test/mjsunit/wasm/wasm-module-builder.js");
@ -69,7 +69,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
/Argument 0 must be a WebAssembly tag/);
assertThrows(() => WebAssembly.Exception(js_tag), TypeError,
/WebAssembly.Exception must be invoked with 'new'/);
let js_exception = new WebAssembly.Exception(js_tag);
let js_exception = new WebAssembly.Exception(js_tag, []);
// Check prototype.
assertSame(WebAssembly.Exception.prototype, js_exception.__proto__);
@ -87,3 +87,86 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
assertTrue(e instanceof WebAssembly.Exception);
}
})();
(function TestExceptionConstructorWithPayload() {
print(arguments.callee.name);
let tag = new WebAssembly.Tag(
{parameters: ['i32', 'f32', 'i64', 'f64', 'externref']});
assertThrows(() => new WebAssembly.Exception(
tag, [1n, 2, 3n, 4, {}]), TypeError);
assertDoesNotThrow(() => new WebAssembly.Exception(tag, [3, 4, 5n, 6, {}]));
})();
(function TestCatchJSException() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let js_tag = new WebAssembly.Tag({parameters: []});
let js_func_index = builder.addImport('m', 'js_func', kSig_v_v);
let js_tag_index = builder.addImportedException("m", "js_tag", kSig_v_v);
let tag_index = builder.addException(kSig_v_v);
builder.addExportOfKind("wasm_tag", kExternalException, tag_index);
builder.addFunction("catch", kSig_i_v)
.addBody([
kExprTry, kWasmI32,
kExprCallFunction, js_func_index,
kExprI32Const, 0,
kExprCatch, js_tag_index,
kExprI32Const, 1,
kExprCatch, tag_index,
kExprI32Const, 2,
kExprEnd
]).exportFunc();
let tag;
function js_func() {
throw new WebAssembly.Exception(tag, []);
}
let instance = builder.instantiate({m: {js_func, js_tag}});
tag = js_tag;
assertEquals(1, instance.exports.catch());
tag = instance.exports.wasm_tag;
assertEquals(2, instance.exports.catch());
})();
function TestCatchJS(types_str, types, values) {
// Create a JS exception, catch it in wasm and check the unpacked value(s).
let builder = new WasmModuleBuilder();
let js_tag = new WebAssembly.Tag({parameters: types_str});
let js_func_index = builder.addImport('m', 'js_func', kSig_v_v);
let sig1 = makeSig(types, []);
let sig2 = makeSig([], types);
let js_tag_index = builder.addImportedException("m", "js_tag", sig1);
let tag_index = builder.addException(sig1);
let return_type = builder.addType(sig2);
builder.addExportOfKind("wasm_tag", kExternalException, tag_index);
builder.addFunction("catch", sig2)
.addBody([
kExprTry, return_type,
kExprCallFunction, js_func_index,
kExprUnreachable,
kExprCatch, js_tag_index,
kExprCatch, tag_index,
kExprEnd
]).exportFunc();
let exception;
function js_func() {
throw exception;
}
let expected = values.length == 1 ? values[0] : values;
let instance = builder.instantiate({m: {js_func, js_tag}});
exception = new WebAssembly.Exception(js_tag, values);
assertEquals(expected, instance.exports.catch());
exception = new WebAssembly.Exception(instance.exports.wasm_tag, values);
assertEquals(expected, instance.exports.catch());
}
(function TestCatchJSExceptionWithPayload() {
print(arguments.callee.name);
TestCatchJS(['i32'], [kWasmI32], [1]);
TestCatchJS(['i64'], [kWasmI64], [2n]);
TestCatchJS(['f32'], [kWasmF32], [3]);
TestCatchJS(['f64'], [kWasmF64], [4]);
TestCatchJS(['externref'], [kWasmExternRef], [{value: 5}]);
TestCatchJS(['i32', 'i64', 'f32', 'f64', 'externref'],
[kWasmI32, kWasmI64, kWasmF32, kWasmF64, kWasmExternRef],
[6, 7n, 8, 9, {value: 10}]);
})();