Bug 1629998 - Use correct prototype for Wasm JS-API objects when subclassed. r=jwalden
☠☠ backed out by fe5f608d8c54 ☠ ☠
authorRyan Hunt <rhunt@eqrion.net>
Tue, 21 Apr 2020 02:34:45 +0000
changeset 526266 af13eadc637026cc8316eb7d83aec79347a5dc12
parent 526265 0608a6f212a18ec39304ab4ba1990d2528683ca8
child 526267 d1cdf76164822ead2ad82be4a4090b0f8c88fc80
push id114158
push userrhunt@eqrion.net
push dateMon, 27 Apr 2020 15:21:41 +0000
treeherderautoland@af13eadc6370 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden
bugs1629998
milestone77.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1629998 - Use correct prototype for Wasm JS-API objects when subclassed. r=jwalden Wasm JS-API objects should be subclassable, and the following should work: ```js class M extends WebAssembly.Module {}; m = new M(...) m instanceof M // true ``` The current code will always set the prototype to the original Wasm prototype, and not the derived prototype. This commit was written by following the example of `ArrayBufferObject::class_constructor` which handles this situation. Differential Revision: https://phabricator.services.mozilla.com/D70965
js/src/jit-test/tests/wasm/prototypes.js
js/src/wasm/WasmJS.cpp
js/src/wasm/WasmJS.h
js/src/wasm/WasmModule.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/prototypes.js
@@ -0,0 +1,27 @@
+class _Module extends WebAssembly.Module {}
+class _Instance extends WebAssembly.Instance {}
+class _Memory extends WebAssembly.Memory {}
+class _Table extends WebAssembly.Table {}
+class _Global extends WebAssembly.Global {}
+
+let binary = wasmTextToBinary('(module)');
+
+let module = new _Module(binary);
+assertEq(module instanceof _Module, true);
+assertEq(module instanceof WebAssembly.Module, true);
+
+let instance = new _Instance(module);
+assertEq(instance instanceof _Instance, true);
+assertEq(instance instanceof WebAssembly.Instance, true);
+
+let memory = new _Memory({initial: 0, maximum: 1});
+assertEq(memory instanceof _Memory, true);
+assertEq(memory instanceof WebAssembly.Memory, true);
+
+let table = new _Table({initial: 0, element: 'anyfunc'});
+assertEq(table instanceof _Table, true);
+assertEq(table instanceof WebAssembly.Table, true);
+
+let global = new _Global({value: 'i32', mutable: false}, 0);
+assertEq(global instanceof _Global, true);
+assertEq(global instanceof WebAssembly.Global, true);
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -890,16 +890,17 @@ const JSClassOps WasmModuleObject::class
     nullptr,                     // construct
     nullptr,                     // trace
 };
 
 const JSClass WasmModuleObject::class_ = {
     "WebAssembly.Module",
     JSCLASS_DELAY_METADATA_BUILDER |
         JSCLASS_HAS_RESERVED_SLOTS(WasmModuleObject::RESERVED_SLOTS) |
+        JSCLASS_HAS_CACHED_PROTO(JSProto_WasmModule) |
         JSCLASS_FOREGROUND_FINALIZE,
     &WasmModuleObject::classOps_,
     &WasmModuleObject::classSpec_,
 };
 
 const JSClass& WasmModuleObject::protoClass_ = PlainObject::class_;
 
 static constexpr char WasmModuleName[] = "Module";
@@ -1277,17 +1278,17 @@ bool WasmModuleObject::customSections(JS
   args.rval().setObject(*arr);
   return true;
 }
 
 /* static */
 WasmModuleObject* WasmModuleObject::create(JSContext* cx, const Module& module,
                                            HandleObject proto) {
   AutoSetNewObjectMetadata metadata(cx);
-  auto* obj = NewObjectWithGivenProto<WasmModuleObject>(cx, proto);
+  auto* obj = NewObjectWithClassProto<WasmModuleObject>(cx, proto);
   if (!obj) {
     return nullptr;
   }
 
   // This accounts for module allocation size (excluding code which is handled
   // separately - see below). This assumes that the size of associated data
   // doesn't change for the life of the WasmModuleObject. The size is counted
   // once per WasmModuleObject referencing a Module.
@@ -1400,18 +1401,22 @@ bool WasmModuleObject::construct(JSConte
     ReportOutOfMemory(cx);
     return false;
   }
 
   if (!ReportCompileWarnings(cx, warnings)) {
     return false;
   }
 
-  RootedObject proto(
-      cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
+  RootedObject proto(cx);
+  if (!GetPrototypeFromBuiltinConstructor(cx, callArgs, JSProto_WasmModule,
+                                          &proto)) {
+    return false;
+  }
+
   RootedObject moduleObj(cx, WasmModuleObject::create(cx, *module, proto));
   if (!moduleObj) {
     return false;
   }
 
   Log(cx, "sync new Module() succeded");
 
   callArgs.rval().setObject(*moduleObj);
@@ -1439,16 +1444,17 @@ const JSClassOps WasmInstanceObject::cla
     nullptr,                       // construct
     WasmInstanceObject::trace,     // trace
 };
 
 const JSClass WasmInstanceObject::class_ = {
     "WebAssembly.Instance",
     JSCLASS_DELAY_METADATA_BUILDER |
         JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceObject::RESERVED_SLOTS) |
+        JSCLASS_HAS_CACHED_PROTO(JSProto_WasmInstance) |
         JSCLASS_FOREGROUND_FINALIZE,
     &WasmInstanceObject::classOps_,
     &WasmInstanceObject::classSpec_,
 };
 
 const JSClass& WasmInstanceObject::protoClass_ = PlainObject::class_;
 
 static constexpr char WasmInstanceName[] = "Instance";
@@ -1570,17 +1576,17 @@ WasmInstanceObject* WasmInstanceObject::
   Instance* instance = nullptr;
   RootedWasmInstanceObject obj(cx);
 
   {
     // We must delay creating metadata for this object until after all its
     // slots have been initialized. We must also create the metadata before
     // calling Instance::init as that may allocate new objects.
     AutoSetNewObjectMetadata metadata(cx);
-    obj = NewObjectWithGivenProto<WasmInstanceObject>(cx, proto);
+    obj = NewObjectWithClassProto<WasmInstanceObject>(cx, proto);
     if (!obj) {
       return nullptr;
     }
 
     MOZ_ASSERT(obj->isTenured(), "assumed by WasmTableObject write barriers");
 
     // Finalization assumes these slots are always initialized:
     InitReservedSlot(obj, EXPORTS_SLOT, exports.release(),
@@ -1656,18 +1662,21 @@ bool WasmInstanceObject::construct(JSCon
     return false;
   }
 
   RootedObject importObj(cx);
   if (!GetImportArg(cx, args, &importObj)) {
     return false;
   }
 
-  RootedObject instanceProto(
-      cx, &cx->global()->getPrototype(JSProto_WasmInstance).toObject());
+  RootedObject instanceProto(cx);
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmInstance,
+                                          &instanceProto)) {
+    return false;
+  }
 
   Rooted<ImportValues> imports(cx);
   if (!GetImports(cx, *module, importObj, imports.address())) {
     return false;
   }
 
   RootedWasmInstanceObject instanceObj(cx);
   if (!module->instantiate(cx, imports.get(), instanceProto, &instanceObj)) {
@@ -1894,16 +1903,17 @@ const JSClassOps WasmMemoryObject::class
     nullptr,                     // construct
     nullptr,                     // trace
 };
 
 const JSClass WasmMemoryObject::class_ = {
     "WebAssembly.Memory",
     JSCLASS_DELAY_METADATA_BUILDER |
         JSCLASS_HAS_RESERVED_SLOTS(WasmMemoryObject::RESERVED_SLOTS) |
+        JSCLASS_HAS_CACHED_PROTO(JSProto_WasmMemory) |
         JSCLASS_FOREGROUND_FINALIZE,
     &WasmMemoryObject::classOps_, &WasmMemoryObject::classSpec_};
 
 const JSClass& WasmMemoryObject::protoClass_ = PlainObject::class_;
 
 static constexpr char WasmMemoryName[] = "Memory";
 
 const ClassSpec WasmMemoryObject::classSpec_ = {
@@ -1924,17 +1934,17 @@ void WasmMemoryObject::finalize(JSFreeOp
   }
 }
 
 /* static */
 WasmMemoryObject* WasmMemoryObject::create(
     JSContext* cx, HandleArrayBufferObjectMaybeShared buffer,
     HandleObject proto) {
   AutoSetNewObjectMetadata metadata(cx);
-  auto* obj = NewObjectWithGivenProto<WasmMemoryObject>(cx, proto);
+  auto* obj = NewObjectWithClassProto<WasmMemoryObject>(cx, proto);
   if (!obj) {
     return nullptr;
   }
 
   obj->initReservedSlot(BUFFER_SLOT, ObjectValue(*buffer));
   MOZ_ASSERT(!obj->hasObservers());
   return obj;
 }
@@ -1966,18 +1976,22 @@ bool WasmMemoryObject::construct(JSConte
 
   ConvertMemoryPagesToBytes(&limits);
 
   RootedArrayBufferObjectMaybeShared buffer(cx);
   if (!CreateWasmBuffer(cx, limits, &buffer)) {
     return false;
   }
 
-  RootedObject proto(
-      cx, &cx->global()->getPrototype(JSProto_WasmMemory).toObject());
+  RootedObject proto(cx);
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmMemory,
+                                          &proto)) {
+    return false;
+  }
+
   RootedWasmMemoryObject memoryObj(cx,
                                    WasmMemoryObject::create(cx, buffer, proto));
   if (!memoryObj) {
     return false;
   }
 
   args.rval().setObject(*memoryObj);
   return true;
@@ -2263,16 +2277,17 @@ const JSClassOps WasmTableObject::classO
     nullptr,                    // construct
     WasmTableObject::trace,     // trace
 };
 
 const JSClass WasmTableObject::class_ = {
     "WebAssembly.Table",
     JSCLASS_DELAY_METADATA_BUILDER |
         JSCLASS_HAS_RESERVED_SLOTS(WasmTableObject::RESERVED_SLOTS) |
+        JSCLASS_HAS_CACHED_PROTO(JSProto_WasmTable) |
         JSCLASS_FOREGROUND_FINALIZE,
     &WasmTableObject::classOps_, &WasmTableObject::classSpec_};
 
 const JSClass& WasmTableObject::protoClass_ = PlainObject::class_;
 
 static constexpr char WasmTableName[] = "Table";
 
 const ClassSpec WasmTableObject::classSpec_ = {
@@ -2304,23 +2319,21 @@ void WasmTableObject::trace(JSTracer* tr
   WasmTableObject& tableObj = obj->as<WasmTableObject>();
   if (!tableObj.isNewborn()) {
     tableObj.table().tracePrivate(trc);
   }
 }
 
 /* static */
 WasmTableObject* WasmTableObject::create(JSContext* cx, const Limits& limits,
-                                         TableKind tableKind) {
-  RootedObject proto(cx,
-                     &cx->global()->getPrototype(JSProto_WasmTable).toObject());
-
+                                         TableKind tableKind,
+                                         HandleObject proto) {
   AutoSetNewObjectMetadata metadata(cx);
   RootedWasmTableObject obj(
-      cx, NewObjectWithGivenProto<WasmTableObject>(cx, proto));
+      cx, NewObjectWithClassProto<WasmTableObject>(cx, proto));
   if (!obj) {
     return nullptr;
   }
 
   MOZ_ASSERT(obj->isNewborn());
 
   TableDesc td(tableKind, limits, /*importedOrExported=*/true);
 
@@ -2408,18 +2421,24 @@ bool WasmTableObject::construct(JSContex
   }
 
   Limits limits;
   if (!GetLimits(cx, obj, MaxTableInitialLength, MaxTableLength, "Table",
                  &limits, Shareable::False)) {
     return false;
   }
 
-  RootedWasmTableObject table(cx,
-                              WasmTableObject::create(cx, limits, tableKind));
+  RootedObject proto(cx);
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmTable,
+                                          &proto)) {
+    return false;
+  }
+
+  RootedWasmTableObject table(
+      cx, WasmTableObject::create(cx, limits, tableKind, proto));
   if (!table) {
     return false;
   }
 
   args.rval().setObject(*table);
   return true;
 }
 
@@ -2651,16 +2670,17 @@ const JSClassOps WasmGlobalObject::class
     nullptr,                     // hasInstance
     nullptr,                     // construct
     WasmGlobalObject::trace,     // trace
 };
 
 const JSClass WasmGlobalObject::class_ = {
     "WebAssembly.Global",
     JSCLASS_HAS_RESERVED_SLOTS(WasmGlobalObject::RESERVED_SLOTS) |
+        JSCLASS_HAS_CACHED_PROTO(JSProto_WasmGlobal) |
         JSCLASS_BACKGROUND_FINALIZE,
     &WasmGlobalObject::classOps_, &WasmGlobalObject::classSpec_};
 
 const JSClass& WasmGlobalObject::protoClass_ = PlainObject::class_;
 
 static constexpr char WasmGlobalName[] = "Global";
 
 const ClassSpec WasmGlobalObject::classSpec_ = {
@@ -2715,23 +2735,20 @@ void WasmGlobalObject::finalize(JSFreeOp
   WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj);
   if (!global->isNewborn()) {
     fop->delete_(obj, global->cell(), MemoryUse::WasmGlobalCell);
   }
 }
 
 /* static */
 WasmGlobalObject* WasmGlobalObject::create(JSContext* cx, HandleVal hval,
-                                           bool isMutable) {
-  RootedObject proto(
-      cx, &cx->global()->getPrototype(JSProto_WasmGlobal).toObject());
-
+                                           bool isMutable, HandleObject proto) {
   AutoSetNewObjectMetadata metadata(cx);
   RootedWasmGlobalObject obj(
-      cx, NewObjectWithGivenProto<WasmGlobalObject>(cx, proto));
+      cx, NewObjectWithClassProto<WasmGlobalObject>(cx, proto));
   if (!obj) {
     return nullptr;
   }
 
   MOZ_ASSERT(obj->isNewborn());
   MOZ_ASSERT(obj->isTenured(), "assumed by global.set post barriers");
 
   // It's simpler to initialize the cell after the object has been created,
@@ -2901,17 +2918,24 @@ bool WasmGlobalObject::construct(JSConte
   RootedValue valueVal(cx, args.get(1));
   if (!valueVal.isUndefined() ||
       (args.length() >= 2 && globalType.isReference())) {
     if (!ToWebAssemblyValue(cx, globalType, valueVal, &globalVal)) {
       return false;
     }
   }
 
-  WasmGlobalObject* global = WasmGlobalObject::create(cx, globalVal, isMutable);
+  RootedObject proto(cx);
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmGlobal,
+                                          &proto)) {
+    return false;
+  }
+
+  WasmGlobalObject* global =
+      WasmGlobalObject::create(cx, globalVal, isMutable, proto);
   if (!global) {
     return false;
   }
 
   args.rval().setObject(*global);
   return true;
 }
 
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -244,17 +244,17 @@ class WasmGlobalObject : public NativeOb
   static const JSClass class_;
   static const JSClass& protoClass_;
   static const JSPropertySpec properties[];
   static const JSFunctionSpec methods[];
   static const JSFunctionSpec static_methods[];
   static bool construct(JSContext*, unsigned, Value*);
 
   static WasmGlobalObject* create(JSContext* cx, wasm::HandleVal value,
-                                  bool isMutable);
+                                  bool isMutable, HandleObject proto);
   bool isNewborn() { return getReservedSlot(CELL_SLOT).isUndefined(); }
 
   wasm::ValType type() const;
   void setVal(JSContext* cx, wasm::HandleVal value);
   void val(wasm::MutableHandleVal outval) const;
   bool isMutable() const;
   bool value(JSContext* cx, MutableHandleValue out);
   Cell* cell() const;
@@ -432,15 +432,15 @@ class WasmTableObject : public NativeObj
   static const JSFunctionSpec methods[];
   static const JSFunctionSpec static_methods[];
   static bool construct(JSContext*, unsigned, Value*);
 
   // Note that, after creation, a WasmTableObject's table() is not initialized
   // and must be initialized before use.
 
   static WasmTableObject* create(JSContext* cx, const wasm::Limits& limits,
-                                 wasm::TableKind tableKind);
+                                 wasm::TableKind tableKind, HandleObject proto);
   wasm::Table& table() const;
 };
 
 }  // namespace js
 
 #endif  // wasm_js_h
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -804,19 +804,21 @@ bool Module::instantiateImportedTable(JS
 
   return true;
 }
 
 bool Module::instantiateLocalTable(JSContext* cx, const TableDesc& td,
                                    WasmTableObjectVector* tableObjs,
                                    SharedTableVector* tables) const {
   SharedTable table;
+  RootedObject proto(cx,
+                     &cx->global()->getPrototype(JSProto_WasmTable).toObject());
   Rooted<WasmTableObject*> tableObj(cx);
   if (td.importedOrExported) {
-    tableObj.set(WasmTableObject::create(cx, td.limits, td.kind));
+    tableObj.set(WasmTableObject::create(cx, td.limits, td.kind, proto));
     if (!tableObj) {
       return false;
     }
     table = &tableObj->table();
   } else {
     table = Table::create(cx, td, /* HandleWasmTableObject = */ nullptr);
     if (!table) {
       return false;
@@ -877,18 +879,20 @@ static bool EnsureExportedGlobalObject(J
     val.set(Val(globalImportValues[globalIndex]));
   } else {
     // If this is not an import, then the initial value will be set by
     // Instance::init() for indirect globals or else by CreateExportObject().
     // In either case, we initialize with a default value here.
     val.set(Val(global.type()));
   }
 
+  RootedObject proto(
+      cx, &cx->global()->getPrototype(JSProto_WasmGlobal).toObject());
   RootedWasmGlobalObject go(
-      cx, WasmGlobalObject::create(cx, val, global.isMutable()));
+      cx, WasmGlobalObject::create(cx, val, global.isMutable(), proto));
   if (!go) {
     return false;
   }
 
   if (globalObjs.length() <= globalIndex &&
       !globalObjs.resize(globalIndex + 1)) {
     ReportOutOfMemory(cx);
     return false;