Bug 1732782 - Fix Wasm exceptions JS API for layout changes. r=rhunt

Fixes how exception payloads are written to and read from
exception objects using the JS API, which was broken when
the Wasm instructions transitioned to using an alignment-aware
tag type to find the offsets into the payload.

Differential Revision: https://phabricator.services.mozilla.com/D126853
This commit is contained in:
Asumu Takikawa 2021-10-01 17:53:22 +00:00
Родитель 8e59b5f49b
Коммит b0627b0ec2
8 изменённых файлов: 233 добавлений и 85 удалений

Просмотреть файл

@ -96,7 +96,7 @@ assertErrorMessage(
/first argument must be a WebAssembly.Tag/
);
const { tag1, tag2, tag3, tag4, tag5, tag6, tag7 } = wasmEvalText(
const { tag1, tag2, tag3, tag4, tag5, tag6, tag7, tag8, tag9 } = wasmEvalText(
`(module
(tag (export "tag1") (param))
(tag (export "tag2") (param i32))
@ -104,7 +104,9 @@ const { tag1, tag2, tag3, tag4, tag5, tag6, tag7 } = wasmEvalText(
(tag (export "tag4") (param i32 externref i32))
(tag (export "tag5") (param i32 externref i32 externref))
(tag (export "tag6") (param funcref))
(tag (export "tag7") (param i64)))`
(tag (export "tag7") (param i64))
(tag (export "tag8") (param i32 f64))
(tag (export "tag9") (param externref funcref)))`
).exports;
new WebAssembly.Exception(tag1, []);
@ -168,25 +170,23 @@ assertErrorMessage(
const exn2 = new WebAssembly.Exception(tag2, [3]);
assertEq(exn2.getArg(tag2, 0), 3);
assertEq(
new WebAssembly.Exception(tag2, [undefined]).getArg(tag2, 0),
0
);
assertEq(new WebAssembly.Exception(tag2, [undefined]).getArg(tag2, 0), 0);
const exn4 = new WebAssembly.Exception(tag4, [3, "foo", 4]);
assertEq(exn4.getArg(tag4, 0), 3);
assertEq(exn4.getArg(tag4, 1), "foo");
assertEq(exn4.getArg(tag4, 2), 4);
const exn5 = new WebAssembly.Exception(
tag5,
[3,
"foo",
4,
"bar"]
);
const exn5 = new WebAssembly.Exception(tag5, [3, "foo", 4, "bar"]);
assertEq(exn5.getArg(tag5, 3), "bar");
const { funcref } = wasmEvalText(
`(module (func (export "funcref")))`
).exports;
const exn9 = new WebAssembly.Exception(tag9, ["foo", funcref]);
assertEq(exn9.getArg(tag9, 0), "foo");
assertEq(exn9.getArg(tag9, 1), funcref);
assertErrorMessage(
() => exn2.getArg(),
TypeError,
@ -257,6 +257,30 @@ assertEqArray(
[42, 5.5]
);
assertEqArray(
wasmEvalText(
`(module
(import "m" "exn" (tag $exn (param i32 f64)))
(import "m" "f" (func $f))
(func (export "f") (result i32 f64)
try (result i32 f64)
call $f
(i32.const 0)
(f64.const 0)
catch $exn
end))`,
{
m: {
exn: tag8,
f: () => {
throw new WebAssembly.Exception(tag8, [9999, 9999]);
},
},
}
).exports.f(),
[9999, 9999]
);
assertEqArray(
wasmEvalText(
`(module
@ -282,6 +306,61 @@ assertEqArray(
[42, "foo", 42]
);
assertEqArray(
wasmEvalText(
`(module
(import "m" "exn" (tag $exn (param i32 externref i32 externref)))
(import "m" "f" (func $f))
(func (export "f") (result i32 externref i32 externref)
try (result i32 externref i32 externref)
call $f
(i32.const 0)
(ref.null extern)
(i32.const 0)
(ref.null extern)
catch $exn
end))`,
{
m: {
exn: tag5,
f: () => {
throw new WebAssembly.Exception(tag5, [42, "foo", 42, "bar"]);
},
},
}
).exports.f(),
[42, "foo", 42, "bar"]
);
{
const { funcref } = wasmEvalText(
`(module (func (export "funcref")))`
).exports;
assertEqArray(
wasmEvalText(
`(module
(import "m" "exn" (tag $exn (param externref funcref)))
(import "m" "f" (func $f))
(func (export "f") (result externref funcref)
try (result externref funcref)
call $f
(ref.null extern)
(ref.null func)
catch $exn
end))`,
{
m: {
exn: tag9,
f: () => {
throw new WebAssembly.Exception(tag9, ["foo", funcref]);
},
},
}
).exports.f(),
["foo", funcref]
);
}
assertEq(
wasmEvalText(
`(module
@ -323,7 +402,7 @@ assertEq(
end))`,
{
m: {
exn: exn,
exn,
f: () => {
throw new WebAssembly.Exception(exn, [42]);
},
@ -362,3 +441,49 @@ assertEq(
1
);
}
// Test `getArg` on a Wasm-thrown exception.
assertEq(
(() => {
try {
wasmEvalText(
`(module
(import "m" "exn" (tag $exn (param i32 f64)))
(func (export "f")
(i32.const 9999)
(f64.const 9999)
throw $exn))`,
{ m: { exn: tag8 } }
).exports.f();
} catch (exn) {
return exn.getArg(tag8, 1);
}
})(),
9999
);
assertEqArray(
(() => {
try {
wasmEvalText(
`(module
(import "m" "exn" (tag $exn (param i32 externref i32 externref)))
(func (export "f") (param externref externref)
(i32.const 1)
(local.get 0)
(i32.const 2)
(local.get 1)
throw $exn))`,
{ m: { exn: tag5 } }
).exports.f("foo", "bar");
} catch (exn) {
return [
exn.getArg(tag5, 0),
exn.getArg(tag5, 1),
exn.getArg(tag5, 2),
exn.getArg(tag5, 3),
];
}
})(),
[1, "foo", 2, "bar"]
);

Просмотреть файл

@ -3574,9 +3574,9 @@ bool BaseCompiler::emitCatch() {
masm.bind(&tryCatch.catchInfos.back().label);
// Extract the arguments in the exception package and push them.
const TagDesc& tagDesc = moduleEnv_.tags[tagIndex];
const ValTypeVector& params = tagDesc.argTypes;
const TagOffsetVector& offsets = tagDesc.argOffsets;
const TagType& tagType = moduleEnv_.tags[tagIndex].type;
const ValTypeVector& params = tagType.argTypes;
const TagOffsetVector& offsets = tagType.argOffsets;
const uint32_t dataOffset =
NativeObject::getFixedSlotOffset(ArrayBufferObject::DATA_SLOT);
@ -3601,7 +3601,7 @@ bool BaseCompiler::emitCatch() {
masm.load32(Address(refs, NativeObject::offsetOfFixedElements() +
ObjectElements::offsetOfLength()),
scratch);
masm.branch32(Assembler::Equal, scratch, Imm32(tagDesc.refCount), &ok);
masm.branch32(Assembler::Equal, scratch, Imm32(tagType.refCount), &ok);
masm.assumeUnreachable("Array length should be equal to exn ref count.");
masm.bind(&ok);
freeI32(scratch);
@ -3955,11 +3955,11 @@ bool BaseCompiler::emitThrow() {
const TagDesc& tagDesc = moduleEnv_.tags[exnIndex];
const ResultType& params = tagDesc.resultType();
const TagOffsetVector& offsets = tagDesc.argOffsets;
const TagOffsetVector& offsets = tagDesc.type.argOffsets;
// Create the new exception object that we will throw.
pushI32(exnIndex);
pushI32(tagDesc.bufferSize);
pushI32(tagDesc.type.bufferSize);
if (!emitInstanceCall(lineOrBytecode, SASigExceptionNew)) {
return false;
}

Просмотреть файл

@ -1463,7 +1463,7 @@ bool WasmModuleObject::imports(JSContext* cx, unsigned argc, Value* vp) {
case DefinitionKind::Tag: {
size_t tagIndex = numTagImport++;
const TagDesc& tag = metadata.tags[tagIndex];
typeObj = TagTypeToObject(cx, tag.argTypes);
typeObj = TagTypeToObject(cx, tag.type.argTypes);
break;
}
# endif // ENABLE_WASM_EXCEPTIONS
@ -1568,7 +1568,7 @@ bool WasmModuleObject::exports(JSContext* cx, unsigned argc, Value* vp) {
# ifdef ENABLE_WASM_EXCEPTIONS
case DefinitionKind::Tag: {
const TagDesc& tag = metadata.tags[exp.tagIndex()];
typeObj = TagTypeToObject(cx, tag.argTypes);
typeObj = TagTypeToObject(cx, tag.type.argTypes);
break;
}
# endif // ENABLE_WASM_EXCEPTIONS
@ -3710,7 +3710,7 @@ void WasmTagObject::finalize(JSFreeOp* fop, JSObject* obj) {
WasmTagObject& exnObj = obj->as<WasmTagObject>();
if (!exnObj.isNewborn()) {
fop->release(obj, &exnObj.tag(), MemoryUse::WasmTagTag);
fop->delete_(obj, &exnObj.valueTypes(), MemoryUse::WasmTagType);
fop->delete_(obj, &exnObj.tagType(), MemoryUse::WasmTagType);
}
}
@ -3745,6 +3745,15 @@ bool WasmTagObject::construct(JSContext* cx, unsigned argc, Value* vp) {
if (!ParseValTypes(cx, paramsVal, params)) {
return false;
}
TagOffsetVector offsets;
if (!offsets.resize(params.length())) {
ReportOutOfMemory(cx);
return false;
}
TagType tagType = TagType(std::move(params), std::move(offsets));
if (!tagType.computeLayout()) {
return false;
}
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmTag, &proto)) {
@ -3754,7 +3763,7 @@ bool WasmTagObject::construct(JSContext* cx, unsigned argc, Value* vp) {
proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmTag);
}
RootedWasmTagObject tagObj(cx, WasmTagObject::create(cx, params, proto));
RootedWasmTagObject tagObj(cx, WasmTagObject::create(cx, tagType, proto));
if (!tagObj) {
return false;
}
@ -3764,7 +3773,8 @@ bool WasmTagObject::construct(JSContext* cx, unsigned argc, Value* vp) {
}
/* static */
WasmTagObject* WasmTagObject::create(JSContext* cx, const ValTypeVector& type,
WasmTagObject* WasmTagObject::create(JSContext* cx,
const wasm::TagType& tagType,
HandleObject proto) {
AutoSetNewObjectMetadata metadata(cx);
RootedWasmTagObject obj(cx,
@ -3783,13 +3793,12 @@ WasmTagObject* WasmTagObject::create(JSContext* cx, const ValTypeVector& type,
InitReservedSlot(obj, TAG_SLOT, tag.forget().take(), MemoryUse::WasmTagTag);
wasm::ValTypeVector* newValueTypes = js_new<ValTypeVector>();
for (auto t : type) {
if (!newValueTypes->append(t)) {
return nullptr;
}
TagType* newType = js_new<TagType>();
if (!newType || !newType->clone(tagType)) {
ReportOutOfMemory(cx);
return nullptr;
}
InitReservedSlot(obj, TYPE_SLOT, newValueTypes, MemoryUse::WasmTagType);
InitReservedSlot(obj, TYPE_SLOT, newType, MemoryUse::WasmTagType);
MOZ_ASSERT(!obj->isNewborn());
@ -3832,8 +3841,12 @@ const JSFunctionSpec WasmTagObject::methods[] = {
const JSFunctionSpec WasmTagObject::static_methods[] = {JS_FS_END};
TagType& WasmTagObject::tagType() const {
return *(TagType*)getFixedSlot(TYPE_SLOT).toPrivate();
};
wasm::ValTypeVector& WasmTagObject::valueTypes() const {
return *(ValTypeVector*)getFixedSlot(TYPE_SLOT).toPrivate();
return tagType().argTypes;
};
wasm::ResultType WasmTagObject::resultType() const {
@ -3923,25 +3936,24 @@ bool WasmExceptionObject::construct(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
wasm::ValTypeVector& params = exnTag->valueTypes();
const wasm::TagType& tagType = exnTag->tagType();
const wasm::ValTypeVector& params = tagType.argTypes;
const wasm::TagOffsetVector& offsets = tagType.argOffsets;
// This is pre-sizing the data buffer for the exception object.
size_t nbytes = 0;
for (const ValType param : params) {
if (!param.isReference()) {
nbytes += SizeOf(param);
}
}
RootedArrayBufferObject buf(cx, ArrayBufferObject::createZeroed(cx, nbytes));
RootedArrayBufferObject buf(
cx, ArrayBufferObject::createZeroed(cx, tagType.bufferSize));
if (!buf) {
return false;
}
RootedArrayObject refs(cx, NewDenseEmptyArray(cx));
RootedArrayObject refs(cx, NewDenseFullyAllocatedArray(cx, tagType.refCount));
if (!refs) {
return false;
}
refs->setDenseInitializedLength(tagType.refCount);
for (int i = 0; i < tagType.refCount; i++) {
refs->initDenseElement(i, UndefinedValue());
}
uint8_t* bufPtr = buf->dataPointer();
RootedValue nextArg(cx);
@ -3961,18 +3973,19 @@ bool WasmExceptionObject::construct(JSContext* cx, unsigned argc, Value* vp) {
}
if (params[i].isReference()) {
RootedObject objPtr(cx);
if (!ToWebAssemblyValue(cx, nextArg, params[i], objPtr.address(), true)) {
return false;
}
if (!NewbornArrayPush(cx, refs, ObjectValue(*objPtr))) {
ASSERT_ANYREF_IS_JSOBJECT;
RootedAnyRef anyref(cx, AnyRef::null());
if (!ToWebAssemblyValue(cx, nextArg, params[i],
anyref.get().asJSObjectAddress(), true)) {
return false;
}
refs->setDenseElement(offsets[i] / sizeof(Value),
ObjectValue(*anyref.get().asJSObject()));
} else {
if (!ToWebAssemblyValue(cx, nextArg, params[i], bufPtr, true)) {
if (!ToWebAssemblyValue(cx, nextArg, params[i], bufPtr + offsets[i],
true)) {
return false;
}
bufPtr += SizeOf(params[i]);
}
}
@ -4080,25 +4093,17 @@ bool WasmExceptionObject::getArgImpl(JSContext* cx, const CallArgs& args) {
return false;
}
uint32_t offset = exnTag->tagType().argOffsets[index];
RootedValue result(cx);
if (params[index].isReference()) {
uint32_t refIndex = 0;
for (size_t i = 0; i < index; i++) {
if (params[i].isReference()) {
refIndex++;
}
}
JSObject* ref = &exnObj->refs().getDenseElement(refIndex).toObject();
if (!ToJSValue(cx, &ref, params[index], &result)) {
ASSERT_ANYREF_IS_JSOBJECT;
RootedValue val(cx, exnObj->refs().getDenseElement(offset / sizeof(Value)));
RootedAnyRef anyref(cx, AnyRef::fromJSObject(val.toObjectOrNull()));
if (!ToJSValue(cx, anyref.get().asJSObjectAddress(), params[index],
&result)) {
return false;
}
} else {
uint32_t offset = 0;
for (size_t i = 0; i < index; i++) {
if (!params[i].isReference()) {
offset += SizeOf(params[i]);
}
}
if (!ToJSValue(cx, exnObj->values().dataPointer() + offset, params[index],
&result)) {
return false;

Просмотреть файл

@ -506,10 +506,11 @@ class WasmTagObject : public NativeObject {
static const JSFunctionSpec static_methods[];
static bool construct(JSContext*, unsigned, Value*);
static WasmTagObject* create(JSContext* cx, const wasm::ValTypeVector& type,
static WasmTagObject* create(JSContext* cx, const wasm::TagType& tagType,
HandleObject proto);
bool isNewborn() const;
wasm::TagType& tagType() const;
wasm::ValTypeVector& valueTypes() const;
wasm::ResultType resultType() const;
wasm::ExceptionTag& tag() const;

Просмотреть файл

@ -840,8 +840,7 @@ bool Module::instantiateLocalTag(JSContext* cx, const TagDesc& ed,
// If the tag description is exported, create an export tag
// object for it.
RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTag));
RootedWasmTagObject tagObj(cx,
WasmTagObject::create(cx, ed.argTypes, proto));
RootedWasmTagObject tagObj(cx, WasmTagObject::create(cx, ed.type, proto));
if (!tagObj) {
return false;
}

Просмотреть файл

@ -181,7 +181,7 @@ size_t GlobalDesc::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
}
#ifdef ENABLE_WASM_EXCEPTIONS
bool TagDesc::computeLayout() {
bool TagType::computeLayout() {
StructLayout layout;
int32_t refCount = 0;
for (const ValType argType : argTypes) {

Просмотреть файл

@ -286,25 +286,43 @@ using GlobalDescVector = Vector<GlobalDesc, 0, SystemAllocPolicy>;
// offset in the elements of the exception's ArrayObject.
using TagOffsetVector = Vector<int32_t, 0, SystemAllocPolicy>;
struct TagDesc {
TagKind kind;
struct TagType {
ValTypeVector argTypes;
TagOffsetVector argOffsets;
int32_t bufferSize;
int32_t refCount;
bool isExport;
TagDesc(TagKind kind, ValTypeVector&& argTypes, TagOffsetVector&& argOffsets,
bool isExport = false)
: kind(kind),
argTypes(std::move(argTypes)),
TagType() : argTypes(), argOffsets(), bufferSize(0), refCount(0) {}
TagType(ValTypeVector&& argTypes, TagOffsetVector&& argOffsets)
: argTypes(std::move(argTypes)),
argOffsets(std::move(argOffsets)),
bufferSize(0),
refCount(0),
isExport(isExport) {}
refCount(0) {}
[[nodiscard]] bool computeLayout();
ResultType resultType() const { return ResultType::Vector(argTypes); }
[[nodiscard]] bool clone(const TagType& src) {
MOZ_ASSERT(argTypes.empty());
MOZ_ASSERT(argOffsets.empty());
if (!argTypes.appendAll(src.argTypes) ||
!argOffsets.appendAll(src.argOffsets)) {
return false;
}
bufferSize = src.bufferSize;
refCount = src.refCount;
return true;
}
};
struct TagDesc {
TagKind kind;
TagType type;
bool isExport;
TagDesc(TagKind kind, TagType&& type, bool isExport = false)
: kind(kind), type(std::move(type)), isExport(isExport) {}
ResultType resultType() const { return ResultType::Vector(type.argTypes); }
};
using TagDescVector = Vector<TagDesc, 0, SystemAllocPolicy>;

Просмотреть файл

@ -2055,14 +2055,14 @@ static bool DecodeImport(Decoder& d, ModuleEnvironment* env) {
if (!offsets.resize(tagArgs.length())) {
return false;
}
if (!env->tags.emplaceBack(tagKind, std::move(tagArgs),
std::move(offsets))) {
TagType type = TagType(std::move(tagArgs), std::move(offsets));
if (!env->tags.emplaceBack(tagKind, std::move(type))) {
return false;
}
if (env->tags.length() > MaxTags) {
return d.fail("too many tags");
}
if (!env->tags.back().computeLayout()) {
if (!env->tags.back().type.computeLayout()) {
return false;
}
break;
@ -2286,9 +2286,9 @@ static bool DecodeTagSection(Decoder& d, ModuleEnvironment* env) {
if (!offsets.resize(tagArgs.length())) {
return false;
}
env->tags.infallibleEmplaceBack(tagKind, std::move(tagArgs),
std::move(offsets));
if (!env->tags.back().computeLayout()) {
TagType type = TagType(std::move(tagArgs), std::move(offsets));
env->tags.infallibleEmplaceBack(tagKind, std::move(type));
if (!env->tags.back().type.computeLayout()) {
return false;
}
}
@ -2409,7 +2409,7 @@ static bool DecodeExport(Decoder& d, ModuleEnvironment* env,
}
# ifdef WASM_PRIVATE_REFTYPES
if (!TagIsJSCompatible(d, env->tags[tagIndex].argTypes)) {
if (!TagIsJSCompatible(d, env->tags[tagIndex].type.argTypes)) {
return false;
}
# endif