// Copyright 2017 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. let {session, contextGroup, Protocol} = InspectorTest.start('Tests stepping through wasm scripts'); utils.load('test/mjsunit/wasm/wasm-constants.js'); utils.load('test/mjsunit/wasm/wasm-module-builder.js'); var builder = new WasmModuleBuilder(); var func_a_idx = builder.addFunction('wasm_A', kSig_v_v).addBody([kExprNop, kExprNop]).index; // wasm_B calls wasm_A times. builder.addFunction('wasm_B', kSig_v_i) .addBody([ // clang-format off kExprLoop, kWasmStmt, // while kExprGetLocal, 0, // - kExprIf, kWasmStmt, // if != 0 kExprGetLocal, 0, // - kExprI32Const, 1, // - kExprI32Sub, // - kExprSetLocal, 0, // decrease kExprCallFunction, func_a_idx, // - kExprBr, 1, // continue kExprEnd, // - kExprEnd, // break // clang-format on ]) .exportAs('main'); var module_bytes = builder.toArray(); function instantiate(bytes) { var buffer = new ArrayBuffer(bytes.length); var view = new Uint8Array(buffer); for (var i = 0; i < bytes.length; ++i) { view[i] = bytes[i] | 0; } var module = new WebAssembly.Module(buffer); // Set global variable. instance = new WebAssembly.Instance(module); } var evalWithUrl = (code, url) => Protocol.Runtime.evaluate( {'expression': code + '\n//# sourceURL=v8://test/' + url}); Protocol.Debugger.onPaused(handlePaused); var wasm_B_scriptId; var step_actions = [ 'stepInto', // == stepOver, to call instruction 'stepInto', // into call to wasm_A 'stepOver', // over first nop 'stepOut', // out of wasm_A 'stepOut', // out of wasm_B, stop on breakpoint again 'stepOver', // to call 'stepOver', // over call 'resume', // to next breakpoint (third iteration) 'stepInto', // to call 'stepInto', // into wasm_A 'stepOut', // out to wasm_B // now step 9 times, until we are in wasm_A again. 'stepInto', 'stepInto', 'stepInto', 'stepInto', 'stepInto', 'stepInto', 'stepInto', 'stepInto', 'stepInto', // 3 more times, back to wasm_B. 'stepInto', 'stepInto', 'stepInto', // then just resume. 'resume' ]; for (var action of step_actions) { InspectorTest.logProtocolCommandCalls('Debugger.' + action) } var sources = {}; var urls = {}; var afterTwoSourcesCallback; Protocol.Debugger.enable() .then(() => InspectorTest.log('Installing code an global variable.')) .then( () => evalWithUrl('var instance;\n' + instantiate.toString(), 'setup')) .then(() => InspectorTest.log('Calling instantiate function.')) .then( () => (evalWithUrl( 'instantiate(' + JSON.stringify(module_bytes) + ')', 'callInstantiate'), 0)) .then(waitForTwoWasmScripts) .then( () => InspectorTest.log( 'Setting breakpoint on line 7 (on the setlocal before the call), url ' + urls[wasm_B_scriptId])) .then( () => Protocol.Debugger.setBreakpoint( {'location': {'scriptId': wasm_B_scriptId, 'lineNumber': 7}})) .then(printFailure) .then(msg => InspectorTest.logMessage(msg.result.actualLocation)) .then(() => evalWithUrl('instance.exports.main(4)', 'runWasm')) .then(() => InspectorTest.log('exports.main returned!')) .then(() => InspectorTest.log('Finished!')) .then(InspectorTest.completeTest); function printFailure(message) { if (!message.result) { InspectorTest.logMessage(message); } return message; } function waitForTwoWasmScripts() { var num = 0; InspectorTest.log('Waiting for two wasm scripts to be parsed.'); var promise = new Promise(fulfill => gotBothSources = fulfill); function waitForMore() { if (num == 2) return promise; Protocol.Debugger.onceScriptParsed() .then(handleNewScript) .then(waitForMore); } function handleNewScript(msg) { var url = msg.params.url; if (!url.startsWith('wasm://')) { InspectorTest.log('Ignoring script with url ' + url); return; } num += 1; var scriptId = msg.params.scriptId; urls[scriptId] = url; InspectorTest.log('Got wasm script: ' + url); if (url.substr(-2) == '-1') wasm_B_scriptId = scriptId; InspectorTest.log('Requesting source for ' + url + '...'); Protocol.Debugger.getScriptSource({scriptId: scriptId}) .then(printFailure) .then(msg => sources[scriptId] = msg.result.scriptSource) .then(InspectorTest.log) .then(() => Object.keys(sources).length == 2 ? gotBothSources() : 0); } waitForMore(); return promise; } function printPauseLocation(scriptId, lineNr, columnNr) { var lines = sources[scriptId].split('\n'); var line = ''; if (lineNr < lines.length) { line = lines[lineNr]; if (columnNr < line.length) { line = line.substr(0, columnNr) + '>' + line.substr(columnNr); } } InspectorTest.log( 'Paused at ' + urls[scriptId] + ':' + lineNr + ':' + columnNr + ': ' + line); } async function getValueString(value) { if (value.type == 'object') { var msg = await Protocol.Runtime.callFunctionOn({ objectId: value.objectId, functionDeclaration: 'function () { return JSON.stringify(this); }' }); printFailure(msg); return msg.result.result.value + ' (' + value.description + ')'; } return value.value + ' (' + value.type + ')'; } async function dumpProperties(message) { printFailure(message); for (var value of message.result.result) { var value_str = await getValueString(value.value); InspectorTest.log(' ' + value.name + ': ' + value_str); } } async function dumpScopeChainsOnPause(message) { for (var frame of message.params.callFrames) { var functionName = frame.functionName || '(anonymous)'; var lineNumber = frame.location ? frame.location.lineNumber : frame.lineNumber; var columnNumber = frame.location ? frame.location.columnNumber : frame.columnNumber; InspectorTest.log(`at ${functionName} (${lineNumber}:${columnNumber}):`); for (var scope of frame.scopeChain) { InspectorTest.logObject(' - scope (' + scope.type + '):'); if (scope.type == 'global') { InspectorTest.logObject(' -- skipped'); } else { var properties = await Protocol.Runtime.getProperties( {'objectId': scope.object.objectId}); await dumpProperties(properties); } } } } function handlePaused(msg) { var loc = msg.params.callFrames[0].location; printPauseLocation(loc.scriptId, loc.lineNumber, loc.columnNumber); dumpScopeChainsOnPause(msg) .then(Protocol.Debugger[step_actions.shift() || 'resume']); }