mirror of
https://github.com/nodejs/node.git
synced 2025-05-15 10:01:49 +00:00

PR-URL: https://github.com/nodejs/node/pull/54077 Reviewed-By: Jiawen Geng <technicalcute@gmail.com> Reviewed-By: Richard Lau <rlau@redhat.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
278 lines
7.7 KiB
JavaScript
278 lines
7.7 KiB
JavaScript
// Copyright 2024 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// Flags: --experimental-wasm-jspi --wasm-stack-switching-stack-size=100 --async-stack-traces
|
|
|
|
utils.load('test/inspector/wasm-inspector-test.js');
|
|
|
|
let {session, contextGroup, Protocol} = InspectorTest.start(
|
|
'Test async stack traces with wasm jspi');
|
|
|
|
// test.js
|
|
|
|
function instantiateWasm(bytes) {
|
|
const buffer = new ArrayBuffer(bytes.length);
|
|
const view = new Uint8Array(buffer);
|
|
for (let i = 0; i < bytes.length; ++i) {
|
|
view[i] = bytes[i] | 0;
|
|
}
|
|
|
|
const module = new WebAssembly.Module(buffer);
|
|
|
|
async function js_func(f) {
|
|
return await f();
|
|
};
|
|
const wasmjs_func = new WebAssembly.Function(
|
|
{parameters:['externref', 'externref'], results:['i32']},
|
|
js_func,
|
|
{suspending: 'first'});
|
|
|
|
const instance = new WebAssembly.Instance(
|
|
module, {env: {wrapped: wasmjs_func}});
|
|
const wasmWrapperFunc = new WebAssembly.Function(
|
|
{parameters: ['externref'], results:['externref']},
|
|
instance.exports.threeTimes,
|
|
{promising: 'first'});
|
|
|
|
async function wrapperFunc(f) {
|
|
// JS function that calls wasm should show up on the call stack.
|
|
firstTime = true;
|
|
const p = wasmWrapperFunc(f);
|
|
console.log('Suspending wrapperFunc');
|
|
return await p;
|
|
}
|
|
|
|
return wrapperFunc;
|
|
}
|
|
|
|
function doPause() {
|
|
console.log(`Error location: ${new Error().stack}\n`);
|
|
debugger;
|
|
}
|
|
|
|
async function testStackSwitching()
|
|
{
|
|
async function alsoSimple() {
|
|
doPause();
|
|
return 2;
|
|
}
|
|
async function thread1() {
|
|
return await wrapperFunc(testSimple);
|
|
}
|
|
async function thread2() {
|
|
return await wrapperFunc(alsoSimple);
|
|
}
|
|
async function thread3() {
|
|
await Promise.resolve();
|
|
return await testSimple() + await testSimple() + await testSimple();
|
|
}
|
|
|
|
const result = await Promise.all([thread1(), thread2(), thread3()]);
|
|
return result.toString();
|
|
}
|
|
|
|
async function testSimple() {
|
|
doPause();
|
|
return 1;
|
|
}
|
|
|
|
async function testSetTimeout() {
|
|
const result = await new Promise(r => setTimeout(() => r(1), 0));
|
|
doPause();
|
|
return result;
|
|
}
|
|
|
|
async function testDoubleNested() {
|
|
async function innerPause() {
|
|
// Pause before and after await as callstacks will be different
|
|
doPause();
|
|
await Promise.resolve();
|
|
doPause();
|
|
// Throw so we don't do this nine times
|
|
throw 'early return';
|
|
}
|
|
|
|
try {
|
|
await wrapperFunc(innerPause);
|
|
} catch (e) {}
|
|
return 1;
|
|
}
|
|
|
|
async function testSyncThrow() {
|
|
throw 'fail';
|
|
}
|
|
|
|
async function testAsyncThrow() {
|
|
await Promise.resolve();
|
|
throw 'fail';
|
|
}
|
|
|
|
async function testSyncThrowAfterResume() {
|
|
if (firstTime) {
|
|
firstTime = false;
|
|
return await Promise.resolve(1);
|
|
} else {
|
|
throw 'fail';
|
|
}
|
|
}
|
|
|
|
async function testCatch(f) {
|
|
try {
|
|
await wrapperFunc(f);
|
|
} catch (e) {
|
|
console.log('caught: ' + e);
|
|
}
|
|
}
|
|
|
|
async function testDontCatch(f) {
|
|
let resolveFunc = null;
|
|
const done = new Promise(res => {resolveFunc = res});
|
|
wrapperFunc(f).finally(()=>resolveFunc());
|
|
await done;
|
|
}
|
|
|
|
// Generate Wasm module
|
|
|
|
const kSig_i_rr = makeSig([kWasmExternRef, kWasmExternRef], [kWasmI32]);
|
|
|
|
const builder = new WasmModuleBuilder();
|
|
|
|
// All functions take a suspender, the js async function to call, and
|
|
// return int.
|
|
|
|
// Add two functions that will be suspended by calling an async
|
|
// JS function
|
|
|
|
const wrapped_js = builder.addImport('env', 'wrapped', kSig_i_rr);
|
|
const wrapped_wasm = builder.addFunction(
|
|
'wrappedWasm', kSig_i_rr, ['suspender', 'js_func'])
|
|
.addBody([
|
|
// Load a parameter and call the import.
|
|
kExprLocalGet, 0,
|
|
kExprLocalGet, 1,
|
|
kExprCallFunction, wrapped_js,
|
|
]);
|
|
|
|
const main = builder.addFunction('threeTimes', kSig_i_rr)
|
|
.addBody([
|
|
// Call function 'wrappedWasm' three times.
|
|
kExprLocalGet, 0,
|
|
kExprLocalGet, 1,
|
|
kExprCallFunction, 1,
|
|
kExprLocalGet, 0,
|
|
kExprLocalGet, 1,
|
|
kExprCallFunction, wrapped_wasm.index,
|
|
kExprLocalGet, 0,
|
|
kExprLocalGet, 1,
|
|
kExprCallFunction, 1,
|
|
kExprI32Add,
|
|
kExprI32Add,
|
|
])
|
|
.exportFunc();
|
|
|
|
const module_bytes = builder.toArray();
|
|
|
|
// Create debuggee script
|
|
|
|
const helpers = [instantiateWasm, doPause, testStackSwitching];
|
|
const testPauseFunctions = [testSimple, testSetTimeout, testDoubleNested];
|
|
const testThrowFunctions = [
|
|
testSyncThrow, testAsyncThrow, testSyncThrowAfterResume];
|
|
const testCatchFunctions = [testCatch, testDontCatch];
|
|
|
|
const file = [
|
|
...helpers,
|
|
...testPauseFunctions,
|
|
...testThrowFunctions,
|
|
...testCatchFunctions].join('\n\n') + `
|
|
const wrapperFunc = instantiateWasm(${JSON.stringify(module_bytes)});
|
|
let firstTime = false;
|
|
Error.stackTraceLimit = 30;
|
|
`;
|
|
const startLine = 14; // Should match first line of first function
|
|
contextGroup.addScript(file, startLine, 0, 'test.js');
|
|
|
|
// Initialize debugger
|
|
|
|
let predictedUncaught = null;
|
|
let actuallyUncaught = null;
|
|
|
|
session.setupScriptMap();
|
|
Protocol.Runtime.enable();
|
|
Protocol.Debugger.enable();
|
|
Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 6});
|
|
Protocol.Debugger.setPauseOnExceptions({state: 'all'});
|
|
Protocol.Debugger.onPaused(message => {
|
|
predictedUncaught = message.params.data?.uncaught;
|
|
InspectorTest.log('Debugger paused on ' + (
|
|
((predictedUncaught && 'uncaught exception') ?? 'debugger statement')
|
|
|| 'caught exception'));
|
|
session.logCallFrames(message.params.callFrames);
|
|
session.logAsyncStackTrace(message.params.asyncStackTrace);
|
|
InspectorTest.log('');
|
|
Protocol.Debugger.resume();
|
|
});
|
|
|
|
Protocol.Console.enable();
|
|
Protocol.Console.onMessageAdded(event => {
|
|
InspectorTest.log('console: ' + event.params.message.text);
|
|
});
|
|
|
|
Protocol.Runtime.onExceptionRevoked(event => {
|
|
actuallyUncaught = false;
|
|
});
|
|
Protocol.Runtime.onExceptionThrown(event => {
|
|
actuallyUncaught = true;
|
|
});
|
|
Protocol.Runtime.enable();
|
|
|
|
// Run tests
|
|
|
|
InspectorTest.runAsyncTestSuite([
|
|
async function testAsyncStackTracesOnPauseAndError() {
|
|
for (const testFunc of testPauseFunctions) {
|
|
InspectorTest.log(
|
|
`Testing async callstacks in JSPI with test function ${testFunc.name}`);
|
|
const {result} = await Protocol.Runtime.evaluate({
|
|
expression: `wrapperFunc(${testFunc.name})//# sourceURL=test_framework.js`,
|
|
awaitPromise: true});
|
|
|
|
InspectorTest.log(`Returned result ${JSON.stringify(result)}`);
|
|
InspectorTest.log('');
|
|
}
|
|
},
|
|
async function testAsyncStackTracesWithSwitching() {
|
|
InspectorTest.log(
|
|
`Testing async callstacks in JSPI with stack switching`);
|
|
const {result} = await Protocol.Runtime.evaluate({
|
|
expression: `testStackSwitching()//# sourceURL=test_framework.js`,
|
|
awaitPromise: true});
|
|
|
|
InspectorTest.log(`Returned result ${JSON.stringify(result)}`);
|
|
InspectorTest.log('');
|
|
},
|
|
async function testCatchPrediction() {
|
|
for (const testCatchFunc of testCatchFunctions) {
|
|
for (const testThrowFunc of testThrowFunctions) {
|
|
InspectorTest.log(
|
|
`Testing catch prediction through JSPI throwing from ${testThrowFunc.name} to ${testCatchFunc.name}`);
|
|
actuallyUncaught = false;
|
|
predictedUncaught = null;
|
|
const {result} = await Protocol.Runtime.evaluate({
|
|
expression: `${testCatchFunc.name}(${testThrowFunc.name})//# sourceURL=test_framework.js`,
|
|
awaitPromise: true});
|
|
InspectorTest.log(`Returned result ${JSON.stringify(result)}`);
|
|
if (actuallyUncaught) {
|
|
InspectorTest.log('Exception was not caught');
|
|
}
|
|
if (actuallyUncaught !== predictedUncaught) {
|
|
InspectorTest.log(
|
|
`PREDICTION MISMATCH: predicted uncaught=${predictedUncaught} actual uncaught=${actuallyUncaught}`);
|
|
}
|
|
InspectorTest.log('');
|
|
}
|
|
}
|
|
}
|
|
]);
|