Commit 45928320 authored by Kim-Anh Tran's avatar Kim-Anh Tran Committed by Commit Bot

[debug] Add support for skipping locations on stepping over

This change adds support for skipping locations that are in a skipList
on step over. This feature is useful for when we are debugging
C++ applications that have DWARF information we only want to stop on
every breakable location in C++, not non every breakable location
on wasm level.

Bug: chromium:1105765
Change-Id: Ie835b011a00cf31e0c5b2df1ac96ebd89f53d23a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2339458Reviewed-by: 's avatarEric Leese <leese@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarSimon Zünd <szuend@chromium.org>
Commit-Queue: Kim-Anh Tran <kimanh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69329}
parent e8e8b0ee
......@@ -217,6 +217,10 @@ class DebugDelegate {
const debug::Location& end) {
return false;
}
virtual bool ShouldBeSkipped(v8::Local<v8::debug::Script> script, int line,
int column) {
return false;
}
};
V8_EXPORT_PRIVATE void SetDebugDelegate(Isolate* isolate,
......
......@@ -437,12 +437,14 @@ void Debug::Break(JavaScriptFrame* frame, Handle<JSFunction> break_target) {
MaybeHandle<FixedArray> break_points_hit =
CheckBreakPoints(debug_info, &location);
if (!break_points_hit.is_null() || break_on_next_function_call()) {
StepAction lastStepAction = last_step_action();
// Clear all current stepping setup.
ClearStepping();
// Notify the debug event listeners.
OnDebugBreak(!break_points_hit.is_null()
? break_points_hit.ToHandleChecked()
: isolate_->factory()->empty_fixed_array());
: isolate_->factory()->empty_fixed_array(),
lastStepAction);
return;
}
......@@ -503,12 +505,13 @@ void Debug::Break(JavaScriptFrame* frame, Handle<JSFunction> break_target) {
}
}
StepAction lastStepAction = last_step_action();
// Clear all current stepping setup.
ClearStepping();
if (step_break) {
// Notify the debug event listeners.
OnDebugBreak(isolate_->factory()->empty_fixed_array());
OnDebugBreak(isolate_->factory()->empty_fixed_array(), lastStepAction);
} else {
// Re-prepare to continue.
PrepareStep(step_action);
......@@ -1871,7 +1874,8 @@ void Debug::OnException(Handle<Object> exception, Handle<Object> promise,
v8::Utils::ToLocal(promise), uncaught, exception_type);
}
void Debug::OnDebugBreak(Handle<FixedArray> break_points_hit) {
void Debug::OnDebugBreak(Handle<FixedArray> break_points_hit,
StepAction lastStepAction) {
DCHECK(!break_points_hit.is_null());
// The caller provided for DebugScope.
AssertDebugContext();
......@@ -1887,6 +1891,13 @@ void Debug::OnDebugBreak(Handle<FixedArray> break_points_hit) {
HandleScope scope(isolate_);
DisableBreak no_recursive_break(this);
// Only check if we should skip this location if we
// paused because of a step over.
if (lastStepAction == StepAction::StepNext && ShouldBeSkipped()) {
PrepareStep(lastStepAction);
return;
}
std::vector<int> inspector_break_points_hit;
int inspector_break_points_count = 0;
// This array contains breakpoints installed using JS debug API.
......@@ -1940,6 +1951,27 @@ bool Debug::IsBlackboxed(Handle<SharedFunctionInfo> shared) {
return debug_info->debug_is_blackboxed();
}
bool Debug::ShouldBeSkipped() {
SuppressDebug while_processing(this);
PostponeInterruptsScope no_interrupts(isolate_);
DisableBreak no_recursive_break(this);
StackTraceFrameIterator iterator(isolate_);
StandardFrame* frame = iterator.frame();
FrameSummary summary = FrameSummary::GetTop(frame);
Handle<Object> script_obj = summary.script();
if (!script_obj->IsScript()) return false;
Handle<Script> script = Handle<Script>::cast(script_obj);
summary.EnsureSourcePositionsAvailable();
int source_position = summary.SourcePosition();
int line = Script::GetLineNumber(script, source_position);
int column = Script::GetColumnNumber(script, source_position);
return debug_delegate_->ShouldBeSkipped(ToApiHandle<debug::Script>(script),
line, column);
}
bool Debug::AllFramesOnStackAreBlackboxed() {
HandleScope scope(isolate_);
for (StackTraceFrameIterator it(isolate_); !it.done(); it.Advance()) {
......@@ -2081,13 +2113,15 @@ void Debug::HandleDebugBreak(IgnoreBreakMode ignore_break_mode) {
}
}
StepAction lastStepAction = last_step_action();
// Clear stepping to avoid duplicate breaks.
ClearStepping();
HandleScope scope(isolate_);
DebugScope debug_scope(this);
OnDebugBreak(isolate_->factory()->empty_fixed_array());
OnDebugBreak(isolate_->factory()->empty_fixed_array(), lastStepAction);
}
#ifdef DEBUG
......
......@@ -215,7 +215,7 @@ class DebugFeatureTracker {
class V8_EXPORT_PRIVATE Debug {
public:
// Debug event triggers.
void OnDebugBreak(Handle<FixedArray> break_points_hit);
void OnDebugBreak(Handle<FixedArray> break_points_hit, StepAction stepAction);
base::Optional<Object> OnThrow(Handle<Object> exception)
V8_WARN_UNUSED_RESULT;
......@@ -274,6 +274,7 @@ class V8_EXPORT_PRIVATE Debug {
std::vector<BreakLocation>* locations);
bool IsBlackboxed(Handle<SharedFunctionInfo> shared);
bool ShouldBeSkipped();
bool CanBreakAtEntry(Handle<SharedFunctionInfo> shared);
......
......@@ -318,6 +318,26 @@ protocol::DictionaryValue* getOrCreateObject(protocol::DictionaryValue* object,
object->setObject(key, std::move(newDictionary));
return value;
}
Response isValidPosition(protocol::Debugger::ScriptPosition* position) {
if (position->getLineNumber() < 0)
return Response::ServerError("Position missing 'line' or 'line' < 0.");
if (position->getColumnNumber() < 0)
return Response::ServerError("Position missing 'column' or 'column' < 0.");
return Response::Success();
}
Response isValidRangeOfPositions(std::vector<std::pair<int, int>>& positions) {
for (size_t i = 1; i < positions.size(); ++i) {
if (positions[i - 1].first < positions[i].first) continue;
if (positions[i - 1].first == positions[i].first &&
positions[i - 1].second < positions[i].second)
continue;
return Response::ServerError(
"Input positions array is not sorted or contains duplicate values.");
}
return Response::Success();
}
} // namespace
V8DebuggerAgentImpl::V8DebuggerAgentImpl(
......@@ -388,6 +408,7 @@ Response V8DebuggerAgentImpl::disable() {
m_blackboxedPositions.clear();
m_blackboxPattern.reset();
resetBlackboxedStateCache();
m_skipList.clear();
m_scripts.clear();
m_cachedScriptIds.clear();
m_cachedScriptSize = 0;
......@@ -844,6 +865,33 @@ bool V8DebuggerAgentImpl::isFunctionBlackboxed(const String16& scriptId,
std::distance(ranges.begin(), itStartRange) % 2;
}
bool V8DebuggerAgentImpl::shouldBeSkipped(const String16& scriptId, int line,
int column) {
if (m_skipList.empty()) return false;
auto it = m_skipList.find(scriptId);
if (it == m_skipList.end()) return false;
const std::vector<std::pair<int, int>>& ranges = it->second;
DCHECK(!ranges.empty());
const std::pair<int, int> location = std::make_pair(line, column);
auto itLowerBound = std::lower_bound(ranges.begin(), ranges.end(), location,
positionComparator);
bool shouldSkip = false;
if (itLowerBound != ranges.end()) {
// Skip lists are defined as pairs of locations that specify the
// start and the end of ranges to skip: [ranges[0], ranges[1], ..], where
// locations in [ranges[0], ranges[1]) should be skipped, i.e.
// [(lineStart, columnStart), (lineEnd, columnEnd)).
const bool isSameAsLowerBound = location == *itLowerBound;
const bool isUnevenIndex = (itLowerBound - ranges.begin()) % 2;
shouldSkip = isSameAsLowerBound ^ isUnevenIndex;
}
return shouldSkip;
}
bool V8DebuggerAgentImpl::acceptsPause(bool isOOMBreak) const {
return enabled() && (isOOMBreak || !m_skipAllPauses);
}
......@@ -1081,6 +1129,14 @@ Response V8DebuggerAgentImpl::resume(Maybe<bool> terminateOnResume) {
Response V8DebuggerAgentImpl::stepOver(
Maybe<protocol::Array<protocol::Debugger::LocationRange>> inSkipList) {
if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
if (inSkipList.isJust()) {
const Response res = processSkipList(inSkipList.fromJust());
if (res.IsError()) return res;
} else {
m_skipList.clear();
}
m_session->releaseObjectGroup(kBacktraceObjectGroup);
m_debugger->stepOverStatement(m_session->contextGroupId());
return Response::Success();
......@@ -1354,23 +1410,14 @@ Response V8DebuggerAgentImpl::setBlackboxedRanges(
positions.reserve(inPositions->size());
for (const std::unique_ptr<protocol::Debugger::ScriptPosition>& position :
*inPositions) {
if (position->getLineNumber() < 0)
return Response::ServerError("Position missing 'line' or 'line' < 0.");
if (position->getColumnNumber() < 0)
return Response::ServerError(
"Position missing 'column' or 'column' < 0.");
Response res = isValidPosition(position.get());
if (res.IsError()) return res;
positions.push_back(
std::make_pair(position->getLineNumber(), position->getColumnNumber()));
}
for (size_t i = 1; i < positions.size(); ++i) {
if (positions[i - 1].first < positions[i].first) continue;
if (positions[i - 1].first == positions[i].first &&
positions[i - 1].second < positions[i].second)
continue;
return Response::ServerError(
"Input positions array is not sorted or contains duplicate values.");
}
Response res = isValidRangeOfPositions(positions);
if (res.IsError()) return res;
m_blackboxedPositions[scriptId] = positions;
it->second->resetBlackboxedStateCache();
......@@ -1866,6 +1913,7 @@ void V8DebuggerAgentImpl::reset() {
if (!enabled()) return;
m_blackboxedPositions.clear();
resetBlackboxedStateCache();
m_skipList.clear();
m_scripts.clear();
m_cachedScriptIds.clear();
m_cachedScriptSize = 0;
......@@ -1887,4 +1935,38 @@ void V8DebuggerAgentImpl::ScriptCollected(const V8DebuggerScript* script) {
}
}
Response V8DebuggerAgentImpl::processSkipList(
protocol::Array<protocol::Debugger::LocationRange>* skipList) {
std::unordered_map<String16, std::vector<std::pair<int, int>>> skipListInit;
for (std::unique_ptr<protocol::Debugger::LocationRange>& range : *skipList) {
protocol::Debugger::ScriptPosition* start = range->getStart();
protocol::Debugger::ScriptPosition* end = range->getEnd();
String16 scriptId = range->getScriptId();
auto it = m_scripts.find(scriptId);
if (it == m_scripts.end())
return Response::ServerError("No script with passed id.");
Response res = isValidPosition(start);
if (res.IsError()) return res;
res = isValidPosition(end);
if (res.IsError()) return res;
skipListInit[scriptId].emplace_back(start->getLineNumber(),
start->getColumnNumber());
skipListInit[scriptId].emplace_back(end->getLineNumber(),
end->getColumnNumber());
}
// Verify that the skipList is sorted, and that all ranges
// are properly defined (start comes before end).
for (auto skipListPair : skipListInit) {
Response res = isValidRangeOfPositions(skipListPair.second);
if (res.IsError()) return res;
}
m_skipList = std::move(skipListInit);
return Response::Success();
}
} // namespace v8_inspector
......@@ -162,6 +162,7 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
bool isFunctionBlackboxed(const String16& scriptId,
const v8::debug::Location& start,
const v8::debug::Location& end);
bool shouldBeSkipped(const String16& scriptId, int line, int column);
bool acceptsPause(bool isOOMBreak) const;
......@@ -199,6 +200,9 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
void setScriptInstrumentationBreakpointIfNeeded(V8DebuggerScript* script);
Response processSkipList(
protocol::Array<protocol::Debugger::LocationRange>* skipList);
using ScriptsMap =
std::unordered_map<String16, std::unique_ptr<V8DebuggerScript>>;
using BreakpointIdToDebuggerBreakpointIdsMap =
......@@ -239,6 +243,7 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
std::unique_ptr<V8Regex> m_blackboxPattern;
std::unordered_map<String16, std::vector<std::pair<int, int>>>
m_blackboxedPositions;
std::unordered_map<String16, std::vector<std::pair<int, int>>> m_skipList;
DISALLOW_COPY_AND_ASSIGN(V8DebuggerAgentImpl);
};
......
......@@ -572,6 +572,27 @@ bool V8Debugger::IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,
return hasAgents && allBlackboxed;
}
bool V8Debugger::ShouldBeSkipped(v8::Local<v8::debug::Script> script, int line,
int column) {
int contextId;
if (!script->ContextId().To(&contextId)) return false;
bool hasAgents = false;
bool allShouldBeSkipped = true;
String16 scriptId = String16::fromInteger(script->Id());
m_inspector->forEachSession(
m_inspector->contextGroupId(contextId),
[&hasAgents, &allShouldBeSkipped, &scriptId, line,
column](V8InspectorSessionImpl* session) {
V8DebuggerAgentImpl* agent = session->debuggerAgent();
if (!agent->enabled()) return;
hasAgents = true;
const bool skip = agent->shouldBeSkipped(scriptId, line, column);
allShouldBeSkipped &= skip;
});
return hasAgents && allShouldBeSkipped;
}
void V8Debugger::AsyncEventOccurred(v8::debug::DebugAsyncActionType type,
int id, bool isBlackboxed) {
// Async task events from Promises are given misaligned pointers to prevent
......
......@@ -210,6 +210,9 @@ class V8Debugger : public v8::debug::DebugDelegate,
const v8::debug::Location& start,
const v8::debug::Location& end) override;
bool ShouldBeSkipped(v8::Local<v8::debug::Script> script, int line,
int column) override;
int currentContextGroupId();
bool asyncStepOutOfFunction(int targetContextGroupId, bool onlyAtReturn);
......
......@@ -476,8 +476,10 @@ RUNTIME_FUNCTION(Runtime_WasmDebugBreak) {
auto* debug_info = frame->native_module()->GetDebugInfo();
if (debug_info->IsStepping(frame)) {
debug_info->ClearStepping(isolate);
StepAction stepAction = isolate->debug()->last_step_action();
isolate->debug()->ClearStepping();
isolate->debug()->OnDebugBreak(isolate->factory()->empty_fixed_array());
isolate->debug()->OnDebugBreak(isolate->factory()->empty_fixed_array(),
stepAction);
return ReadOnlyRoots(isolate).undefined_value();
}
......@@ -487,10 +489,11 @@ RUNTIME_FUNCTION(Runtime_WasmDebugBreak) {
if (WasmScript::CheckBreakPoints(isolate, script, position)
.ToHandle(&breakpoints)) {
debug_info->ClearStepping(isolate);
StepAction stepAction = isolate->debug()->last_step_action();
isolate->debug()->ClearStepping();
if (isolate->debug()->break_points_active()) {
// We hit one or several breakpoints. Notify the debug listeners.
isolate->debug()->OnDebugBreak(breakpoints);
isolate->debug()->OnDebugBreak(breakpoints, stepAction);
}
}
......
Tests that stepOver and stepInto correctly handle skipLists.
Test: No skip list
Testing step over with skipList: []
test: 1:2
test: 2:10
test: 3:12
test: 4:2
test: 5:4
test: 7:10
test: 8:2
test: 9:13
Test: Skip lines
Testing step over with skipList: [{"scriptId":"3","start":{"lineNumber":2,"columnNumber":0},"end":{"lineNumber":4,"columnNumber":0}},{"scriptId":"3","start":{"lineNumber":8,"columnNumber":0},"end":{"lineNumber":9,"columnNumber":0}}]
test: 1:2
test: 4:2
test: 5:4
test: 7:10
test: 9:13
Test: Start location is inclusive
Testing step over with skipList: [{"scriptId":"3","start":{"lineNumber":8,"columnNumber":2},"end":{"lineNumber":9,"columnNumber":0}}]
test: 1:2
test: 2:10
test: 3:12
test: 4:2
test: 5:4
test: 7:10
test: 9:13
Test: End location is exclusive
Testing step over with skipList: [{"scriptId":"3","start":{"lineNumber":2,"columnNumber":0},"end":{"lineNumber":8,"columnNumber":2}}]
test: 1:2
test: 8:2
test: 9:13
Test: start position has invalid column number
Testing step over with skipList: [{"scriptId":"3","start":{"lineNumber":2,"columnNumber":-1},"end":{"lineNumber":9,"columnNumber":0}}]
test: 1:2
Position missing 'column' or 'column' < 0.
Test: start position has invalid line number
Testing step over with skipList: [{"scriptId":"3","start":{"lineNumber":-1,"columnNumber":0},"end":{"lineNumber":2,"columnNumber":0}}]
test: 1:2
Position missing 'line' or 'line' < 0.
Test: end position smaller than start position
Testing step over with skipList: [{"scriptId":"3","start":{"lineNumber":4,"columnNumber":0},"end":{"lineNumber":2,"columnNumber":0}}]
test: 1:2
Input positions array is not sorted or contains duplicate values.
Test: skip list is not maximally merged
Testing step over with skipList: [{"scriptId":"3","start":{"lineNumber":2,"columnNumber":0},"end":{"lineNumber":4,"columnNumber":0}},{"scriptId":"3","start":{"lineNumber":4,"columnNumber":0},"end":{"lineNumber":9,"columnNumber":0}}]
test: 1:2
Input positions array is not sorted or contains duplicate values.
Test: skip list is not sorted
Testing step over with skipList: [{"scriptId":"3","start":{"lineNumber":8,"columnNumber":0},"end":{"lineNumber":9,"columnNumber":0}},{"scriptId":"3","start":{"lineNumber":2,"columnNumber":0},"end":{"lineNumber":4,"columnNumber":0}}]
test: 1:2
Input positions array is not sorted or contains duplicate values.
// Copyright 2020 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 that stepOver and stepInto correctly handle skipLists.');
function test(input) {
debugger;
var a = 4;
var sum = 0;
if (input > 0) {
sum = a + input;
}
var b = 5;
sum = add(sum, b);
return sum;
}
function add(a, b) {
return a + b;
}
contextGroup.addScript(`${test} //# sourceURL=test.js`);
contextGroup.addScript(`${add}`);
const first_non_debug_line_offset = 2;
const last_line_line_offset = 9;
const function_call_line_offset = 8;
const function_call_column_offset = 2;
const if_case_line_offset = 4;
Protocol.Debugger.enable();
runTest()
.catch(reason => InspectorTest.log(`Failed: ${reason}`))
.then(InspectorTest.completeTest);
async function runTest() {
const response = await Protocol.Debugger.onceScriptParsed();
const scriptId = response.params.scriptId;
await checkValidSkipLists(scriptId);
await checkInvalidSkipLists(scriptId);
}
async function checkInvalidSkipLists(scriptId) {
InspectorTest.log('Test: start position has invalid column number');
let skipList = [createLocationRange(
scriptId, first_non_debug_line_offset, -1, last_line_line_offset, 0)];
await testStepOver(skipList);
InspectorTest.log('Test: start position has invalid line number');
skipList =
[createLocationRange(scriptId, -1, 0, first_non_debug_line_offset, 0)];
await testStepOver(skipList);
InspectorTest.log('Test: end position smaller than start position');
skipList = [createLocationRange(
scriptId, if_case_line_offset, 0, first_non_debug_line_offset, 0)];
await testStepOver(skipList);
InspectorTest.log('Test: skip list is not maximally merged');
skipList = [
createLocationRange(
scriptId, first_non_debug_line_offset, 0, if_case_line_offset, 0),
createLocationRange(
scriptId, if_case_line_offset, 0, last_line_line_offset, 0)
];
await testStepOver(skipList);
InspectorTest.log('Test: skip list is not sorted');
skipList = [
createLocationRange(
scriptId, function_call_line_offset, 0, last_line_line_offset, 0),
createLocationRange(
scriptId, first_non_debug_line_offset, 0, if_case_line_offset, 0)
];
await testStepOver(skipList);
}
async function checkValidSkipLists(scriptId) {
InspectorTest.log('Test: No skip list');
await testStepOver([]);
InspectorTest.log('Test: Skip lines');
let skipList = [
createLocationRange(
scriptId, first_non_debug_line_offset, 0, if_case_line_offset, 0),
createLocationRange(
scriptId, function_call_line_offset, 0, last_line_line_offset, 0)
];
await testStepOver(skipList);
InspectorTest.log('Test: Start location is inclusive');
skipList = [createLocationRange(
scriptId, function_call_line_offset, function_call_column_offset,
last_line_line_offset, 0)];
await testStepOver(skipList);
InspectorTest.log('Test: End location is exclusive');
skipList = [createLocationRange(
scriptId, first_non_debug_line_offset, 0, function_call_line_offset,
function_call_column_offset)];
await testStepOver(skipList);
}
async function testStepOver(skipList) {
InspectorTest.log(
`Testing step over with skipList: ${JSON.stringify(skipList)}`);
Protocol.Runtime.evaluate({expression: 'test(5);'});
while (true) {
const pausedMsg = await Protocol.Debugger.oncePaused();
const topCallFrame = pausedMsg.params.callFrames[0];
printCallFrame(topCallFrame);
if (topCallFrame.location.lineNumber == last_line_line_offset) break;
const stepOverMsg = await Protocol.Debugger.stepOver({skipList});
if (stepOverMsg.error) {
InspectorTest.log(stepOverMsg.error.message);
Protocol.Debugger.resume();
return;
}
}
await Protocol.Debugger.resume();
}
function createLocationRange(
scriptId, startLine, startColumn, endLine, endColumn) {
return {
scriptId: scriptId,
start: {lineNumber: startLine, columnNumber: startColumn},
end: {lineNumber: endLine, columnNumber: endColumn}
}
}
function printCallFrame(frame) {
InspectorTest.log(
frame.functionName + ': ' + frame.location.lineNumber + ':' +
frame.location.columnNumber);
}
Tests stepping through wasm scripts by byte offsets
Setting up global instance variable
Got wasm script: wasm://wasm/befe41aa
{
columnNumber : 46
lineNumber : 0
scriptId : <scriptId>
}
Test with valid skip lists
Script wasm://wasm/befe41aa byte offset 46: Wasm opcode 0x20
Test: No skip list
Testing step over with skipList: []
Script wasm://wasm/befe41aa byte offset 48: Wasm opcode 0x04
Script wasm://wasm/befe41aa byte offset 50: Wasm opcode 0x20
Script wasm://wasm/befe41aa byte offset 52: Wasm opcode 0x41
Script wasm://wasm/befe41aa byte offset 54: Wasm opcode 0x6b
Script wasm://wasm/befe41aa byte offset 55: Wasm opcode 0x21
Script wasm://wasm/befe41aa byte offset 57: Wasm opcode 0x41
Script wasm://wasm/befe41aa byte offset 60: Wasm opcode 0x10
Script wasm://wasm/befe41aa byte offset 62: Wasm opcode 0x0c
Script wasm://wasm/befe41aa byte offset 46: Wasm opcode 0x20
Test: Skip lines
Testing step over with skipList: [{"scriptId":"4","start":{"lineNumber":0,"columnNumber":48},"end":{"lineNumber":0,"columnNumber":50}},{"scriptId":"4","start":{"lineNumber":0,"columnNumber":60},"end":{"lineNumber":0,"columnNumber":62}}]
Script wasm://wasm/befe41aa byte offset 50: Wasm opcode 0x20
Script wasm://wasm/befe41aa byte offset 52: Wasm opcode 0x41
Script wasm://wasm/befe41aa byte offset 54: Wasm opcode 0x6b
Script wasm://wasm/befe41aa byte offset 55: Wasm opcode 0x21
Script wasm://wasm/befe41aa byte offset 57: Wasm opcode 0x41
Script wasm://wasm/befe41aa byte offset 62: Wasm opcode 0x0c
Script wasm://wasm/befe41aa byte offset 46: Wasm opcode 0x20
Test: Start location is inclusive
Testing step over with skipList: [{"scriptId":"4","start":{"lineNumber":0,"columnNumber":48},"end":{"lineNumber":0,"columnNumber":61}}]
Script wasm://wasm/befe41aa byte offset 62: Wasm opcode 0x0c
Script wasm://wasm/befe41aa byte offset 46: Wasm opcode 0x20
Test: End location is exclusive
Testing step over with skipList: [{"scriptId":"4","start":{"lineNumber":0,"columnNumber":49},"end":{"lineNumber":0,"columnNumber":62}}]
Script wasm://wasm/befe41aa byte offset 48: Wasm opcode 0x04
Script wasm://wasm/befe41aa byte offset 62: Wasm opcode 0x0c
Script wasm://wasm/befe41aa byte offset 46: Wasm opcode 0x20
Test with invalid skip lists
Script wasm://wasm/befe41aa byte offset 46: Wasm opcode 0x20
Test: start position has invalid column number
Testing step over with skipList: [{"scriptId":"4","start":{"lineNumber":0,"columnNumber":-1},"end":{"lineNumber":0,"columnNumber":62}}]
Position missing 'column' or 'column' < 0.
Test: start position has invalid line number
Testing step over with skipList: [{"scriptId":"4","start":{"lineNumber":-1,"columnNumber":0},"end":{"lineNumber":0,"columnNumber":62}}]
Position missing 'line' or 'line' < 0.
Test: end position smaller than start position
Testing step over with skipList: [{"scriptId":"4","start":{"lineNumber":0,"columnNumber":62},"end":{"lineNumber":0,"columnNumber":48}}]
Input positions array is not sorted or contains duplicate values.
Test: skip list is not maximally merged
Testing step over with skipList: [{"scriptId":"4","start":{"lineNumber":0,"columnNumber":48},"end":{"lineNumber":0,"columnNumber":50}},{"scriptId":"4","start":{"lineNumber":0,"columnNumber":50},"end":{"lineNumber":0,"columnNumber":62}}]
Input positions array is not sorted or contains duplicate values.
Test: skip list is not sorted
Testing step over with skipList: [{"scriptId":"4","start":{"lineNumber":0,"columnNumber":50},"end":{"lineNumber":0,"columnNumber":62}},{"scriptId":"4","start":{"lineNumber":0,"columnNumber":48},"end":{"lineNumber":0,"columnNumber":62}}]
Input positions array is not sorted or contains duplicate values.
Finished!
// Copyright 2020 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.
utils.load('test/inspector/wasm-inspector-test.js');
let {session, contextGroup, Protocol} =
InspectorTest.start('Tests stepping through wasm scripts by byte offsets');
session.setupScriptMap();
var builder = new WasmModuleBuilder();
var func_a_idx =
builder.addFunction('wasm_A', kSig_v_i).addBody([kExprNop, kExprNop]).index;
// wasm_B calls wasm_A <param0> times.
var func_b = builder.addFunction('wasm_B', kSig_v_i)
.addBody([
// clang-format off
kExprLoop, kWasmStmt, // while
kExprLocalGet, 0, // -
kExprIf, kWasmStmt, // if <param0> != 0
kExprLocalGet, 0, // -
kExprI32Const, 1, // -
kExprI32Sub, // -
kExprLocalSet, 0, // decrease <param0>
...wasmI32Const(1024), // some longer i32 const (2 byte imm)
kExprCallFunction, func_a_idx, // -
kExprBr, 1, // continue
kExprEnd, // -
kExprEnd, // break
// clang-format on
])
.exportAs('main');
const module_bytes = builder.toArray();
const loop_start_offset = func_b.body_offset + 2;
const loop_body_start_offset = loop_start_offset + 2;
const loop_body_end_offset = loop_body_start_offset + 14;
const if_statement_offset = loop_body_start_offset + 2
const call_function_offset = loop_body_start_offset + 12;
runTest()
.catch(reason => InspectorTest.log(`Failed: ${reason}`))
.then(InspectorTest.completeTest);
async function runTest() {
await Protocol.Debugger.enable();
InspectorTest.log('Setting up global instance variable');
WasmInspectorTest.instantiate(module_bytes);
const [, {params: wasmScript}] = await Protocol.Debugger.onceScriptParsed(2);
const scriptId = wasmScript.scriptId;
InspectorTest.log('Got wasm script: ' + wasmScript.url);
let bpmsg = await Protocol.Debugger.setBreakpoint({
location:
{scriptId: scriptId, lineNumber: 0, columnNumber: loop_start_offset}
});
InspectorTest.logMessage(bpmsg.result.actualLocation);
await checkValidSkipLists(scriptId);
await checkInvalidSkipLists(scriptId);
InspectorTest.log('Finished!');
}
async function checkValidSkipLists(scriptId) {
InspectorTest.log('Test with valid skip lists');
Protocol.Runtime.evaluate({expression: 'instance.exports.main(5)'});
const {params: {callFrames}} = await Protocol.Debugger.oncePaused();
await session.logSourceLocation(callFrames[0].location);
InspectorTest.log('Test: No skip list');
let skipList = [];
await stepThroughOneLoopIteration(skipList);
InspectorTest.log('Test: Skip lines');
skipList = [
createLocationRange(scriptId, loop_body_start_offset, if_statement_offset),
createLocationRange(scriptId, call_function_offset, loop_body_end_offset)
];
await stepThroughOneLoopIteration(skipList);
InspectorTest.log('Test: Start location is inclusive');
skipList = [
createLocationRange(
scriptId, loop_body_start_offset, loop_body_end_offset - 1),
];
await stepThroughOneLoopIteration(skipList);
InspectorTest.log('Test: End location is exclusive');
skipList = [
createLocationRange(
scriptId, loop_body_start_offset + 1, loop_body_end_offset),
];
await stepThroughOneLoopIteration(skipList);
await Protocol.Debugger.resume();
}
async function checkInvalidSkipLists(scriptId) {
InspectorTest.log('Test with invalid skip lists');
Protocol.Runtime.evaluate({expression: 'instance.exports.main(5)'});
const {params: {callFrames}} = await Protocol.Debugger.oncePaused();
await session.logSourceLocation(callFrames[0].location);
InspectorTest.log('Test: start position has invalid column number');
let skipList = [
createLocationRange(scriptId, -1, loop_body_end_offset),
];
await stepThroughOneLoopIteration(skipList);
InspectorTest.log('Test: start position has invalid line number');
skipList = [{
scriptId: scriptId,
start: {lineNumber: -1, columnNumber: 0},
end: {lineNumber: 0, columnNumber: loop_body_end_offset}
}];
await stepThroughOneLoopIteration(skipList);
InspectorTest.log('Test: end position smaller than start position');
skipList = [createLocationRange(
scriptId, loop_body_end_offset, loop_body_start_offset)];
await stepThroughOneLoopIteration(skipList);
InspectorTest.log('Test: skip list is not maximally merged');
skipList = [
createLocationRange(scriptId, loop_body_start_offset, if_statement_offset),
createLocationRange(scriptId, if_statement_offset, loop_body_end_offset)
];
await stepThroughOneLoopIteration(skipList);
InspectorTest.log('Test: skip list is not sorted');
skipList = [
createLocationRange(scriptId, if_statement_offset, loop_body_end_offset),
createLocationRange(scriptId, loop_body_start_offset, loop_body_end_offset)
];
await stepThroughOneLoopIteration(skipList);
}
async function stepThroughOneLoopIteration(skipList) {
InspectorTest.log(
`Testing step over with skipList: ${JSON.stringify(skipList)}`);
let topFrameLocation = -1;
while (topFrameLocation.columnNumber != loop_start_offset) {
const stepOverMsg = await Protocol.Debugger.stepOver({skipList});
if (stepOverMsg.error) {
InspectorTest.log(stepOverMsg.error.message);
return;
}
const {params: {callFrames}} = await Protocol.Debugger.oncePaused();
topFrameLocation = callFrames[0].location;
await session.logSourceLocation(topFrameLocation);
}
}
function createLocationRange(scriptId, startColumn, endColumn) {
return {
scriptId: scriptId, start: {lineNumber: 0, columnNumber: startColumn},
end: {lineNumber: 0, columnNumber: endColumn}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment