[wasm][stack-switching] Throw on re-entrant suspender

Throw a wasm trap when trying to re-enter a suspender that is active or
suspended.

R=ahaas@chromium.org

Bug: v8:12191
Change-Id: Ic448a15db29de14fb8d6bb8408af8fbaae82a2b4
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3716481
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81326}
This commit is contained in:
Thibaud Michaud 2022-06-21 17:39:09 +02:00 committed by V8 LUCI CQ
parent e35039e773
commit 2071ce3b6b
4 changed files with 52 additions and 2 deletions

View File

@ -3017,7 +3017,10 @@ void AllocateContinuation(MacroAssembler* masm, Register function_data,
__ Push(wasm_instance);
__ Push(function_data);
__ Push(suspender); // Argument.
__ Move(kContextRegister, Smi::zero());
__ LoadAnyTaggedField(
kContextRegister,
MemOperand(wasm_instance, wasm::ObjectAccess::ToTagged(
WasmInstanceObject::kNativeContextOffset)));
__ CallRuntime(Runtime::kWasmAllocateContinuation);
__ Pop(function_data);
__ Pop(wasm_instance);
@ -3228,7 +3231,7 @@ void GenericJSToWasmWrapperHelper(MacroAssembler* masm, bool stack_switch) {
__ LoadRoot(active_continuation, RootIndex::kActiveContinuation);
SaveState(masm, active_continuation, rcx, &suspend);
AllocateContinuation(masm, function_data, wasm_instance);
Register target_continuation = rax; /* fixed */
Register target_continuation = rax; // fixed
// Save the old stack's rbp in r9, and use it to access the parameters in
// the parent frame.
// We also distribute the spill slots across the two stacks as needed by

View File

@ -650,6 +650,7 @@ namespace internal {
T(WasmTrapStringOffsetOutOfBounds, "string offset out of bounds") \
T(WasmTrapStringIsolatedSurrogate, \
"Failed to encode string as UTF-8: contains unpaired surrogate") \
T(WasmTrapReentrantSuspender, "re-entering an active/suspended suspender") \
T(WasmExceptionError, "wasm exception") \
/* Asm.js validation related */ \
T(AsmJsInvalid, "Invalid asm.js: %") \

View File

@ -798,6 +798,11 @@ RUNTIME_FUNCTION(Runtime_WasmAllocateContinuation) {
HandleScope scope(isolate);
Handle<WasmSuspenderObject> suspender = args.at<WasmSuspenderObject>(0);
if (suspender->state() != WasmSuspenderObject::kInactive) {
return ThrowWasmError(isolate,
MessageTemplate::kWasmTrapReentrantSuspender);
}
// Update the continuation state.
auto parent = handle(WasmContinuationObject::cast(
isolate->root(RootIndex::kActiveContinuation)),

View File

@ -323,3 +323,44 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
let combined_promise = wrapped_export();
assertPromiseResult(combined_promise, v => assertEquals(v, 42));
})();
(function TestReenterActiveSuspenderFails() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let import_index = builder.addImport("m", "i", kSig_v_v);
builder.addFunction("test", kSig_i_v)
.addBody([
kExprCallFunction, import_index,
kExprI32Const, 0
]).exportFunc();
let wrapped_export;
function js_import() {
wrapped_export(); // Re-enter the same wrapped export.
}
let instance = builder.instantiate({m: {i: js_import}});
let suspender = new WebAssembly.Suspender();
wrapped_export = suspender.returnPromiseOnSuspend(instance.exports.test);
assertThrows(wrapped_export, WebAssembly.RuntimeError,
/re-entering an active\/suspended suspender/);
})();
(function TestReenterSuspendedSuspenderFails() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let import_index = builder.addImport("m", "i", kSig_v_v);
builder.addFunction("test", kSig_i_v)
.addBody([
kExprCallFunction, import_index,
kExprI32Const, 0
]).exportFunc();
function js_import() {
return Promise.resolve(0);
}
let instance = builder.instantiate({m: {i: js_import}});
let suspender = new WebAssembly.Suspender();
let wrapped_export = suspender.returnPromiseOnSuspend(instance.exports.test);
let promise1 = wrapped_export();
// Re-enter the suspender before resolving the promise.
assertThrows(wrapped_export, WebAssembly.RuntimeError,
/re-entering an active\/suspended suspender/);
})();