WebAssembly JS API: throw when a promise can't be created
authorjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 17 Nov 2017 21:57:09 +0000 (21:57 +0000)
committerjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 17 Nov 2017 21:57:09 +0000 (21:57 +0000)
https://bugs.webkit.org/show_bug.cgi?id=179826
<rdar://problem/35455813>

Reviewed by Mark Lam.

JSTests:

Test WebAssembly.{compile,instantiate} where promise creation
fails because of a stack overflow.

* wasm/js-api/promise-stack-overflow.js: Added.
(const.runNearStackLimit.f.const.t):
(async.testCompile):
(async.testInstantiate):

Source/JavaScriptCore:

Failure *in* a promise causes rejection, but failure to create a
promise (because of stack overflow) isn't really spec'd (as all
stack things JS). This applies to WebAssembly.compile and
WebAssembly.instantiate.

Dan's current proposal says:

    https://littledan.github.io/spec/document/js-api/index.html#stack-overflow

    Whenever a stack overflow occurs in WebAssembly code, the same
    class of exception is thrown as for a stack overflow in
    JavaScript. The particular exception here is
    implementation-defined in both cases.

    Note: ECMAScript doesn’t specify any sort of behavior on stack
    overflow; implementations have been observed to throw RangeError,
    InternalError or Error. Any is valid here.

This is for general stack overflow within WebAssembly, not
specifically for promise creation within JavaScript, but it seems
like a stack overflow in promise creation should follow the same
rule instead of, say, swallowing the overflow and returning
undefined.

* wasm/js/WebAssemblyPrototype.cpp:
(JSC::webAssemblyCompileFunc):
(JSC::webAssemblyInstantiateFunc):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@224985 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/wasm/js-api/promise-stack-overflow.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/wasm/js/WebAssemblyPrototype.cpp

index c1ed223..509dfd7 100644 (file)
@@ -1,3 +1,19 @@
+2017-11-17  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly JS API: throw when a promise can't be created
+        https://bugs.webkit.org/show_bug.cgi?id=179826
+        <rdar://problem/35455813>
+
+        Reviewed by Mark Lam.
+
+        Test WebAssembly.{compile,instantiate} where promise creation
+        fails because of a stack overflow.
+
+        * wasm/js-api/promise-stack-overflow.js: Added.
+        (const.runNearStackLimit.f.const.t):
+        (async.testCompile):
+        (async.testInstantiate):
+
 2017-11-16  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         Unreviewed, mark regress-178385.js as memory exhausting
diff --git a/JSTests/wasm/js-api/promise-stack-overflow.js b/JSTests/wasm/js-api/promise-stack-overflow.js
new file mode 100644 (file)
index 0000000..bc92d5d
--- /dev/null
@@ -0,0 +1,37 @@
+import * as assert from '../assert.js';
+
+const module = Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x1, 0x00, 0x00, 0x00);
+
+let promises = [];
+
+const runNearStackLimit = f => {
+    const t = () => {
+        try {
+            return t();
+        } catch (e) {
+            return f();
+        }
+    };
+    return t();
+};
+
+const touchArgument = arg => promises.push(arg);
+
+const compileMe = () => touchArgument(WebAssembly.compile(module));
+
+async function testCompile() {
+    await touchArgument(async function() {
+        runNearStackLimit(compileMe);
+    }());
+}
+
+const instantiateMe = () => touchArgument(WebAssembly.instantiate(module));
+
+async function testInstantiate() {
+    await touchArgument(async function() {
+        runNearStackLimit(instantiateMe);
+    }());
+}
+
+assert.asyncTest(testCompile());
+assert.asyncTest(testInstantiate());
index d67b28b..80dc195 100644 (file)
@@ -1,3 +1,39 @@
+2017-11-17  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly JS API: throw when a promise can't be created
+        https://bugs.webkit.org/show_bug.cgi?id=179826
+        <rdar://problem/35455813>
+
+        Reviewed by Mark Lam.
+
+        Failure *in* a promise causes rejection, but failure to create a
+        promise (because of stack overflow) isn't really spec'd (as all
+        stack things JS). This applies to WebAssembly.compile and
+        WebAssembly.instantiate.
+
+        Dan's current proposal says:
+
+            https://littledan.github.io/spec/document/js-api/index.html#stack-overflow
+
+            Whenever a stack overflow occurs in WebAssembly code, the same
+            class of exception is thrown as for a stack overflow in
+            JavaScript. The particular exception here is
+            implementation-defined in both cases.
+
+            Note: ECMAScript doesn’t specify any sort of behavior on stack
+            overflow; implementations have been observed to throw RangeError,
+            InternalError or Error. Any is valid here.
+
+        This is for general stack overflow within WebAssembly, not
+        specifically for promise creation within JavaScript, but it seems
+        like a stack overflow in promise creation should follow the same
+        rule instead of, say, swallowing the overflow and returning
+        undefined.
+
+        * wasm/js/WebAssemblyPrototype.cpp:
+        (JSC::webAssemblyCompileFunc):
+        (JSC::webAssemblyInstantiateFunc):
+
 2017-11-16  Daniel Bates  <dabates@apple.com>
 
         Add feature define for alternative presentation button element
index 6f9f6ac..f01e2e1 100644 (file)
@@ -40,6 +40,7 @@
 #include "ObjectConstructor.h"
 #include "PromiseDeferredTimer.h"
 #include "StrongInlines.h"
+#include "ThrowScope.h"
 #include "WasmBBQPlan.h"
 #include "WasmToJS.h"
 #include "WasmWorklist.h"
@@ -81,38 +82,42 @@ static void reject(ExecState* exec, CatchScope& catchScope, JSPromiseDeferred* p
 static EncodedJSValue JSC_HOST_CALL webAssemblyCompileFunc(ExecState* exec)
 {
     VM& vm = exec->vm();
-    auto scope = DECLARE_CATCH_SCOPE(vm);
+    auto throwScope = DECLARE_THROW_SCOPE(vm);
     auto* globalObject = exec->lexicalGlobalObject();
 
     JSPromiseDeferred* promise = JSPromiseDeferred::create(exec, globalObject);
-    CLEAR_AND_RETURN_IF_EXCEPTION(scope, encodedJSValue());
-
-    Vector<Strong<JSCell>> dependencies;
-    dependencies.append(Strong<JSCell>(vm, globalObject));
-    vm.promiseDeferredTimer->addPendingPromise(promise, WTFMove(dependencies));
+    RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+
+    {
+        auto catchScope = DECLARE_CATCH_SCOPE(vm);
+
+        Vector<Strong<JSCell>> dependencies;
+        dependencies.append(Strong<JSCell>(vm, globalObject));
+        vm.promiseDeferredTimer->addPendingPromise(promise, WTFMove(dependencies));
+
+        Vector<uint8_t> source = createSourceBufferFromValue(vm, exec, exec->argument(0));
+
+        if (UNLIKELY(catchScope.exception()))
+            reject(exec, catchScope, promise);
+        else {
+            Wasm::Module::validateAsync(&vm.wasmContext, WTFMove(source), createSharedTask<Wasm::Module::CallbackType>([promise, globalObject, &vm] (Wasm::Module::ValidationResult&& result) mutable {
+                vm.promiseDeferredTimer->scheduleWorkSoon(promise, [promise, globalObject, result = WTFMove(result), &vm] () mutable {
+                    auto scope = DECLARE_CATCH_SCOPE(vm);
+                    ExecState* exec = globalObject->globalExec();
+                    JSValue module = JSWebAssemblyModule::createStub(vm, exec, globalObject->WebAssemblyModuleStructure(), WTFMove(result));
+                    if (UNLIKELY(scope.exception())) {
+                        reject(exec, scope, promise);
+                        return;
+                    }
+
+                    promise->resolve(exec, module);
+                    CLEAR_AND_RETURN_IF_EXCEPTION(scope, void());
+                });
+            }));
+        }
 
-    Vector<uint8_t> source = createSourceBufferFromValue(vm, exec, exec->argument(0));
-
-    if (UNLIKELY(scope.exception()))
-        reject(exec, scope, promise);
-    else {
-        Wasm::Module::validateAsync(&vm.wasmContext, WTFMove(source), createSharedTask<Wasm::Module::CallbackType>([promise, globalObject, &vm] (Wasm::Module::ValidationResult&& result) mutable {
-            vm.promiseDeferredTimer->scheduleWorkSoon(promise, [promise, globalObject, result = WTFMove(result), &vm] () mutable {
-                auto scope = DECLARE_CATCH_SCOPE(vm);
-                ExecState* exec = globalObject->globalExec();
-                JSValue module = JSWebAssemblyModule::createStub(vm, exec, globalObject->WebAssemblyModuleStructure(), WTFMove(result));
-                if (UNLIKELY(scope.exception())) {
-                    reject(exec, scope, promise);
-                    return;
-                }
-
-                promise->resolve(exec, module);
-                CLEAR_AND_RETURN_IF_EXCEPTION(scope, void());
-            });
-        }));
+        return JSValue::encode(promise->promise());
     }
-
-    return JSValue::encode(promise->promise());
 }
 
 enum class Resolve { WithInstance, WithModuleAndInstance };
@@ -172,7 +177,7 @@ static void compileAndInstantiate(VM& vm, ExecState* exec, JSPromiseDeferred* pr
             auto scope = DECLARE_CATCH_SCOPE(vm);
             ExecState* exec = globalObject->globalExec();
             JSWebAssemblyModule* module = JSWebAssemblyModule::createStub(vm, exec, globalObject->WebAssemblyModuleStructure(), WTFMove(result));
-            if (scope.exception())
+            if (UNLIKELY(scope.exception()))
                 return reject(exec, scope, promise);
 
             instantiate(vm, exec, promise, module, importObject, Resolve::WithModuleAndInstance);
@@ -183,27 +188,31 @@ static void compileAndInstantiate(VM& vm, ExecState* exec, JSPromiseDeferred* pr
 static EncodedJSValue JSC_HOST_CALL webAssemblyInstantiateFunc(ExecState* exec)
 {
     VM& vm = exec->vm();
-    auto scope = DECLARE_CATCH_SCOPE(vm);
+    auto throwScope = DECLARE_THROW_SCOPE(vm);
+    auto* globalObject = exec->lexicalGlobalObject();
 
-    JSPromiseDeferred* promise = JSPromiseDeferred::create(exec, exec->lexicalGlobalObject());
-    CLEAR_AND_RETURN_IF_EXCEPTION(scope, encodedJSValue());
+    JSPromiseDeferred* promise = JSPromiseDeferred::create(exec, globalObject);
+    RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+
+    {
+        auto catchScope = DECLARE_CATCH_SCOPE(vm);
+
+        JSValue importArgument = exec->argument(1);
+        JSObject* importObject = importArgument.getObject();
+        if (UNLIKELY(!importArgument.isUndefined() && !importObject)) {
+            promise->reject(exec, createTypeError(exec,
+                ASCIILiteral("second argument to WebAssembly.instantiate must be undefined or an Object"), defaultSourceAppender, runtimeTypeForValue(importArgument)));
+            CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, JSValue::encode(promise->promise()));
+        } else {
+            JSValue firstArgument = exec->argument(0);
+            if (auto* module = jsDynamicCast<JSWebAssemblyModule*>(vm, firstArgument))
+                instantiate(vm, exec, promise, module, importObject, Resolve::WithInstance);
+            else
+                compileAndInstantiate(vm, exec, promise, firstArgument, importObject);
+        }
 
-    JSValue importArgument = exec->argument(1);
-    JSObject* importObject = importArgument.getObject();
-    if (!importArgument.isUndefined() && !importObject) {
-        promise->reject(exec, createTypeError(exec,
-            ASCIILiteral("second argument to WebAssembly.instantiate must be undefined or an Object"), defaultSourceAppender, runtimeTypeForValue(importArgument)));
-        CLEAR_AND_RETURN_IF_EXCEPTION(scope, encodedJSValue());
         return JSValue::encode(promise->promise());
     }
-
-    JSValue firstArgument = exec->argument(0);
-    if (auto* module = jsDynamicCast<JSWebAssemblyModule*>(vm, firstArgument))
-        instantiate(vm, exec, promise, module, importObject, Resolve::WithInstance);
-    else
-        compileAndInstantiate(vm, exec, promise, firstArgument, importObject);
-
-    return JSValue::encode(promise->promise());
 }
 
 static EncodedJSValue JSC_HOST_CALL webAssemblyValidateFunc(ExecState* exec)