Bug 1315390 - Baldr: exported wasm functions shouldn't be constructors (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Fri, 04 Nov 2016 18:44:56 -0500
changeset 351485 e2562586fd5e7367d1fb08b533a397b5e048dd05
parent 351484 4fcbd5540284da08fbee7031ac7c1717bf9c715b
child 351486 cd3851bd4184607045637de5b54d5c2c7da36b20
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1315390
milestone52.0a1
Bug 1315390 - Baldr: exported wasm functions shouldn't be constructors (r=bbouvier) MozReview-Commit-ID: ABYBHtSQni5
js/src/jit-test/tests/wasm/jsapi.js
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsscript.cpp
js/src/wasm/AsmJS.cpp
js/src/wasm/WasmInstance.cpp
js/src/wasm/WasmJS.cpp
--- a/js/src/jit-test/tests/wasm/jsapi.js
+++ b/js/src/jit-test/tests/wasm/jsapi.js
@@ -1,13 +1,13 @@
 load(libdir + 'wasm.js');
 
 const WasmPage = 64 * 1024;
 
-const emptyModule = wasmTextToBinary('(module)');
+const moduleBinary = wasmTextToBinary('(module (func (export "f") (result i32) (i32.const 42)))');
 
 // 'WebAssembly' data property on global object
 const wasmDesc = Object.getOwnPropertyDescriptor(this, 'WebAssembly');
 assertEq(typeof wasmDesc.value, "object");
 assertEq(wasmDesc.writable, true);
 assertEq(wasmDesc.enumerable, false);
 assertEq(wasmDesc.configurable, true);
 
@@ -65,34 +65,34 @@ assertEq(Module.length, 1);
 assertEq(Module.name, "Module");
 assertErrorMessage(() => Module(), TypeError, /constructor without new is forbidden/);
 assertErrorMessage(() => new Module(), TypeError, /requires more than 0 arguments/);
 assertErrorMessage(() => new Module(undefined), TypeError, "first argument must be an ArrayBuffer or typed array object");
 assertErrorMessage(() => new Module(1), TypeError, "first argument must be an ArrayBuffer or typed array object");
 assertErrorMessage(() => new Module({}), TypeError, "first argument must be an ArrayBuffer or typed array object");
 assertErrorMessage(() => new Module(new Uint8Array()), CompileError, /failed to match magic number/);
 assertErrorMessage(() => new Module(new ArrayBuffer()), CompileError, /failed to match magic number/);
-assertEq(new Module(emptyModule) instanceof Module, true);
-assertEq(new Module(emptyModule.buffer) instanceof Module, true);
+assertEq(new Module(moduleBinary) instanceof Module, true);
+assertEq(new Module(moduleBinary.buffer) instanceof Module, true);
 
 // 'WebAssembly.Module.prototype' data property
 const moduleProtoDesc = Object.getOwnPropertyDescriptor(Module, 'prototype');
 assertEq(typeof moduleProtoDesc.value, "object");
 assertEq(moduleProtoDesc.writable, false);
 assertEq(moduleProtoDesc.enumerable, false);
 assertEq(moduleProtoDesc.configurable, false);
 
 // 'WebAssembly.Module.prototype' object
 const moduleProto = Module.prototype;
 assertEq(moduleProto, moduleProtoDesc.value);
 assertEq(String(moduleProto), "[object Object]");
 assertEq(Object.getPrototypeOf(moduleProto), Object.prototype);
 
 // 'WebAssembly.Module' instance objects
-const m1 = new Module(emptyModule);
+const m1 = new Module(moduleBinary);
 assertEq(typeof m1, "object");
 assertEq(String(m1), "[object WebAssembly.Module]");
 assertEq(Object.getPrototypeOf(m1), moduleProto);
 
 // 'WebAssembly.Instance' data property
 const instanceDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'Instance');
 assertEq(typeof instanceDesc.value, "function");
 assertEq(instanceDesc.writable, true);
@@ -132,16 +132,24 @@ assertEq(Object.getPrototypeOf(i1), inst
 
 // 'WebAssembly.Instance' 'exports' data property
 const exportsDesc = Object.getOwnPropertyDescriptor(i1, 'exports');
 assertEq(typeof exportsDesc.value, "object");
 assertEq(exportsDesc.writable, true);
 assertEq(exportsDesc.enumerable, true);
 assertEq(exportsDesc.configurable, true);
 
+// Exported WebAssembly functions
+const f = i1.exports.f;
+assertEq(f instanceof Function, true);
+assertEq(f.length, 0);
+assertEq('name' in f, true);
+assertEq(Function.prototype.call.call(f), 42);
+assertErrorMessage(() => new f(), TypeError, /is not a constructor/);
+
 // 'WebAssembly.Memory' data property
 const memoryDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'Memory');
 assertEq(typeof memoryDesc.value, "function");
 assertEq(memoryDesc.writable, true);
 assertEq(memoryDesc.enumerable, false);
 assertEq(memoryDesc.configurable, true);
 
 // 'WebAssembly.Memory' constructor function
@@ -374,10 +382,10 @@ assertCompileError([{}], /first argument
 assertCompileError([new Uint8Array()], /failed to match magic number/);
 assertCompileError([new ArrayBuffer()], /failed to match magic number/);
 function assertCompileSuccess(bytes) {
     var module = null;
     compile(bytes).then(m => module = m);
     drainJobQueue();
     assertEq(module instanceof Module, true);
 }
-assertCompileSuccess(emptyModule);
-assertCompileSuccess(emptyModule.buffer);
+assertCompileSuccess(moduleBinary);
+assertCompileSuccess(moduleBinary.buffer);
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -373,17 +373,17 @@ static const JSPropertySpec function_pro
     JS_PSGS("arguments", ArgumentsGetter, ArgumentsSetter, 0),
     JS_PSGS("caller", CallerGetter, CallerSetter, 0),
     JS_PS_END
 };
 
 static bool
 ResolveInterpretedFunctionPrototype(JSContext* cx, HandleFunction fun, HandleId id)
 {
-    MOZ_ASSERT(fun->isInterpreted() || fun->isWasmNative());
+    MOZ_ASSERT(fun->isInterpreted() || fun->isAsmJSNative());
     MOZ_ASSERT(id == NameToId(cx->names().prototype));
 
     // Assert that fun is not a compiler-created function object, which
     // must never leak to script or embedding code and then be mutated.
     // Also assert that fun is not bound, per the ES5 15.3.4.5 ref above.
     MOZ_ASSERT(!IsInternalFunctionObject(*fun));
     MOZ_ASSERT(!fun->isBoundFunction());
 
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -35,17 +35,17 @@ class JSFunction : public js::NativeObje
 
     enum FunctionKind {
         NormalFunction = 0,
         Arrow,                      /* ES6 '(args) => body' syntax */
         Method,                     /* ES6 MethodDefinition */
         ClassConstructor,
         Getter,
         Setter,
-        Wasm,                       /* function is wasm module or exported function */
+        AsmJS,                      /* function is an asm.js module or exported function */
         FunctionKindLimit
     };
 
     enum Flags {
         INTERPRETED      = 0x0001,  /* function has a JSScript and environment. */
         CONSTRUCTOR      = 0x0002,  /* function that can be called as a constructor */
         EXTENDED         = 0x0004,  /* structure is FunctionExtended */
         BOUND_FUN        = 0x0008,  /* function was created with Function.prototype.bind. */
@@ -61,29 +61,29 @@ class JSFunction : public js::NativeObje
         HAS_REST         = 0x0100,  /* function has a rest (...) parameter */
         INTERPRETED_LAZY = 0x0200,  /* function is interpreted but doesn't have a script yet */
         RESOLVED_LENGTH  = 0x0400,  /* f.length has been resolved (see fun_resolve). */
         RESOLVED_NAME    = 0x0800,  /* f.name has been resolved (see fun_resolve). */
 
         FUNCTION_KIND_SHIFT = 13,
         FUNCTION_KIND_MASK  = 0x7 << FUNCTION_KIND_SHIFT,
 
-        WASM_KIND = Wasm << FUNCTION_KIND_SHIFT,
+        ASMJS_KIND = AsmJS << FUNCTION_KIND_SHIFT,
         ARROW_KIND = Arrow << FUNCTION_KIND_SHIFT,
         METHOD_KIND = Method << FUNCTION_KIND_SHIFT,
         CLASSCONSTRUCTOR_KIND = ClassConstructor << FUNCTION_KIND_SHIFT,
         GETTER_KIND = Getter << FUNCTION_KIND_SHIFT,
         SETTER_KIND = Setter << FUNCTION_KIND_SHIFT,
 
         /* Derived Flags values for convenience: */
         NATIVE_FUN = 0,
         NATIVE_CTOR = NATIVE_FUN | CONSTRUCTOR,
         NATIVE_CLASS_CTOR = NATIVE_FUN | CONSTRUCTOR | CLASSCONSTRUCTOR_KIND,
-        WASM_CTOR = WASM_KIND | NATIVE_CTOR,
-        ASMJS_LAMBDA_CTOR = WASM_KIND | NATIVE_CTOR | LAMBDA,
+        ASMJS_CTOR = ASMJS_KIND | NATIVE_CTOR,
+        ASMJS_LAMBDA_CTOR = ASMJS_KIND | NATIVE_CTOR | LAMBDA,
         INTERPRETED_METHOD = INTERPRETED | METHOD_KIND,
         INTERPRETED_METHOD_GENERATOR = INTERPRETED | METHOD_KIND,
         INTERPRETED_CLASS_CONSTRUCTOR = INTERPRETED | CLASSCONSTRUCTOR_KIND | CONSTRUCTOR,
         INTERPRETED_GETTER = INTERPRETED | GETTER_KIND,
         INTERPRETED_SETTER = INTERPRETED | SETTER_KIND,
         INTERPRETED_LAMBDA = INTERPRETED | LAMBDA | CONSTRUCTOR,
         INTERPRETED_LAMBDA_ARROW = INTERPRETED | LAMBDA | ARROW_KIND,
         INTERPRETED_LAMBDA_GENERATOR = INTERPRETED | LAMBDA,
@@ -168,17 +168,17 @@ class JSFunction : public js::NativeObje
 
     /* A function can be classified as either native (C++) or interpreted (JS): */
     bool isInterpreted()            const { return flags() & (INTERPRETED | INTERPRETED_LAZY); }
     bool isNative()                 const { return !isInterpreted(); }
 
     bool isConstructor()            const { return flags() & CONSTRUCTOR; }
 
     /* Possible attributes of a native function: */
-    bool isWasmNative()            const { return kind() == Wasm; }
+    bool isAsmJSNative()            const { return kind() == AsmJS; }
 
     /* Possible attributes of an interpreted function: */
     bool isExprBody()               const { return flags() & EXPR_BODY; }
     bool hasGuessedAtom()           const { return flags() & HAS_GUESSED_ATOM; }
     bool isLambda()                 const { return flags() & LAMBDA; }
     bool isBoundFunction()          const { return flags() & BOUND_FUN; }
     bool hasRest()                  const { return flags() & HAS_REST; }
     bool isInterpretedLazy()        const { return flags() & INTERPRETED_LAZY; }
@@ -210,17 +210,17 @@ class JSFunction : public js::NativeObje
         if (!hasScript())
             return false;
 
         return nonLazyScript()->hasBaselineScript() || nonLazyScript()->hasIonScript();
     }
 
     /* Compound attributes: */
     bool isBuiltin() const {
-        return (isNative() && !isWasmNative()) || isSelfHostedBuiltin();
+        return (isNative() && !isAsmJSNative()) || isSelfHostedBuiltin();
     }
 
     bool isNamedLambda() const {
         return isLambda() && displayAtom() && !hasGuessedAtom();
     }
 
     bool hasLexicalThis() const {
         return isArrow() || nonLazyScript()->isGeneratorExp();
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -786,17 +786,17 @@ js::XDRScript(XDRState<mode>* xdr, Handl
             if (mode == XDR_ENCODE) {
                 RootedFunction function(cx, &(*objp)->as<JSFunction>());
 
                 if (function->isInterpretedLazy()) {
                     funEnclosingScope = function->lazyScript()->enclosingScope();
                 } else if (function->isInterpreted()) {
                     funEnclosingScope = function->nonLazyScript()->enclosingScope();
                 } else {
-                    MOZ_ASSERT(function->isWasmNative());
+                    MOZ_ASSERT(function->isAsmJSNative());
                     return xdr->fail(JS::TranscodeResult_Failure_AsmJSNotSupported);
                 }
 
                 funEnclosingScopeIndex = FindScopeIndex(script, *funEnclosingScope);
             }
 
             if (!xdr->codeUint32(&funEnclosingScopeIndex))
                 return false;
@@ -3205,17 +3205,17 @@ js::detail::CopyScript(JSContext* cx, Ha
             obj = vector[i];
             clone = nullptr;
             if (obj->is<RegExpObject>()) {
                 clone = CloneScriptRegExpObject(cx, obj->as<RegExpObject>());
             } else if (obj->is<JSFunction>()) {
                 RootedFunction innerFun(cx, &obj->as<JSFunction>());
                 if (innerFun->isNative()) {
                     if (cx->compartment() != innerFun->compartment()) {
-                        MOZ_ASSERT(innerFun->isWasmNative());
+                        MOZ_ASSERT(innerFun->isAsmJSNative());
                         JS_ReportErrorASCII(cx, "AsmJS modules do not yet support cloning.");
                         return false;
                     }
                     clone = innerFun;
                 } else {
                     if (innerFun->isInterpretedLazy()) {
                         AutoCompartment ac(cx, innerFun);
                         if (!innerFun->getOrCreateScript(cx))
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -8147,17 +8147,17 @@ InstantiateAsmJS(JSContext* cx, unsigned
 }
 
 static JSFunction*
 NewAsmJSModuleFunction(ExclusiveContext* cx, JSFunction* origFun, HandleObject moduleObj)
 {
     RootedAtom name(cx, origFun->name());
 
     JSFunction::Flags flags = origFun->isLambda() ? JSFunction::ASMJS_LAMBDA_CTOR
-                                                  : JSFunction::WASM_CTOR;
+                                                  : JSFunction::ASMJS_CTOR;
     JSFunction* moduleFun =
         NewNativeConstructor(cx, InstantiateAsmJS, origFun->nargs(), name,
                              gc::AllocKind::FUNCTION_EXTENDED, TenuredObject,
                              flags);
     if (!moduleFun)
         return nullptr;
 
     moduleFun->setExtendedSlot(FunctionExtended::ASMJS_MODULE_SLOT, ObjectValue(*moduleObj));
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -651,21 +651,21 @@ Instance::callExport(JSContext* cx, uint
         JitActivation jitActivation(cx, /* active */ false);
 
         // Call the per-exported-function trampoline created by GenerateEntry.
         auto funcPtr = JS_DATA_TO_FUNC_PTR(ExportFuncPtr, codeBase() + func.entryOffset());
         if (!CALL_GENERATED_2(funcPtr, exportArgs.begin(), &tlsData_))
             return false;
     }
 
-    if (args.isConstructing()) {
-        // By spec, when a function is called as a constructor and this function
-        // returns a primary type, which is the case for all wasm exported
-        // functions, the returned value is discarded and an empty object is
-        // returned instead.
+    if (isAsmJS() && args.isConstructing()) {
+        // By spec, when a JS function is called as a constructor and this
+        // function returns a primary type, which is the case for all asm.js
+        // exported functions, the returned value is discarded and an empty
+        // object is returned instead.
         PlainObject* obj = NewBuiltinClassInstance<PlainObject>(cx);
         if (!obj)
             return false;
         args.rval().set(ObjectValue(*obj));
         return true;
     }
 
     void* retAddr = &exportArgs[0];
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -770,18 +770,25 @@ WasmInstanceObject::getExportedFunction(
     }
 
     const Instance& instance = instanceObj->instance();
     RootedAtom name(cx, instance.code().getFuncAtom(cx, funcIndex));
     if (!name)
         return false;
 
     unsigned numArgs = instance.metadata().lookupFuncExport(funcIndex).sig().args().length();
-    fun.set(NewNativeConstructor(cx, WasmCall, numArgs, name, gc::AllocKind::FUNCTION_EXTENDED,
-                                 SingletonObject, JSFunction::WASM_CTOR));
+
+    // asm.js needs to active like a normal JS function which are allowed to be
+    // used as constructors.
+    if (instance.isAsmJS()) {
+        fun.set(NewNativeConstructor(cx, WasmCall, numArgs, name, gc::AllocKind::FUNCTION_EXTENDED,
+                                     SingletonObject, JSFunction::ASMJS_CTOR));
+    } else {
+        fun.set(NewNativeFunction(cx, WasmCall, numArgs, name, gc::AllocKind::FUNCTION_EXTENDED));
+    }
     if (!fun)
         return false;
 
     fun->setExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT, ObjectValue(*instanceObj));
     fun->setExtendedSlot(FunctionExtended::WASM_FUNC_INDEX_SLOT, Int32Value(funcIndex));
 
     if (!instanceObj->exports().putNew(funcIndex, fun)) {
         ReportOutOfMemory(cx);