[inspector] implemented blackboxing inside v8

V8 has internal mechanism to ignore steps and breaks inside internal scripts, in this CL it's reused for blackboxing implementation.
Advantages:
- much faster blackboxing implementation (before we at least wrap and collect current call stack for each step),
- get rid of StepFrame action and potential pause in blackboxed code after N StepFrame steps,
- simplification of debugger agent logic.
Disadvtanges:
- currently when user was paused in blackboxed code (e.g. on breakpoint) and then makes step action, debugger ignores blackboxed state of the script and allows to use step actions as usual - this behavior is regressed, we still able to support it on frontend side.

Current state and proposed changes for blackboxing: https://docs.google.com/document/d/1hnzaXPAN8_QC5ENxIgxgMNDbXLraM_OXT73rAyijTF8/edit?usp=sharing

BUG=v8:5842
R=yangguo@chromium.org,dgozman@chromium.org,alph@chromium.org

Review-Url: https://codereview.chromium.org/2633803002
Cr-Commit-Position: refs/heads/master@{#42614}
This commit is contained in:
kozyatinskiy 2017-01-23 17:50:25 -08:00 committed by Commit bot
parent d90e6e12e6
commit ac50c79a3e
20 changed files with 907 additions and 235 deletions

View File

@ -9035,11 +9035,17 @@ void debug::PrepareStep(Isolate* v8_isolate, StepAction action) {
isolate->debug()->PrepareStep(static_cast<i::StepAction>(action));
}
void debug::ClearStepping(Isolate* v8_isolate) {
bool debug::HasNonBlackboxedFrameOnStack(Isolate* v8_isolate) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
ENTER_V8(isolate);
// Clear all current stepping setup.
isolate->debug()->ClearStepping();
i::HandleScope scope(isolate);
for (i::StackTraceFrameIterator it(isolate); !it.done(); it.Advance()) {
if (!it.is_javascript()) continue;
i::Handle<i::SharedFunctionInfo> shared(
it.javascript_frame()->function()->shared());
if (!isolate->debug()->IsBlackboxed(shared)) return true;
}
return false;
}
v8::Isolate* debug::Script::GetIsolate() const {
@ -9314,11 +9320,22 @@ MaybeLocal<UnboundScript> debug::CompileInspectorScript(Isolate* v8_isolate,
RETURN_ESCAPED(ToApiHandle<UnboundScript>(result));
}
void debug::SetDebugEventListener(Isolate* v8_isolate,
debug::DebugEventListener* listener) {
void debug::SetDebugDelegate(Isolate* v8_isolate,
debug::DebugDelegate* delegate) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
ENTER_V8(isolate);
isolate->debug()->SetDebugEventListener(listener);
isolate->debug()->SetDebugDelegate(delegate);
}
void debug::ResetBlackboxedStateCache(Isolate* v8_isolate,
v8::Local<debug::Script> script) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
ENTER_V8(isolate);
i::DisallowHeapAllocation no_gc;
i::SharedFunctionInfo::ScriptIterator iter(Utils::OpenHandle(*script));
while (i::SharedFunctionInfo* info = iter.Next()) {
info->set_computed_debug_is_blackboxed(false);
}
}
Local<String> CpuProfileNode::GetFunctionName() const {

View File

@ -86,13 +86,13 @@ void ChangeBreakOnException(Isolate* isolate, ExceptionBreakState state);
enum StepAction {
StepOut = 0, // Step out of the current function.
StepNext = 1, // Step to the next statement in the current function.
StepIn = 2, // Step into new functions invoked or the next statement
StepIn = 2 // Step into new functions invoked or the next statement
// in the current function.
StepFrame = 3 // Step into a new frame or return to previous frame.
};
void PrepareStep(Isolate* isolate, StepAction action);
void ClearStepping(Isolate* isolate);
bool HasNonBlackboxedFrameOnStack(Isolate* isolate);
/**
* Out-of-memory callback function.
@ -147,9 +147,9 @@ void GetLoadedScripts(Isolate* isolate, PersistentValueVector<Script>& scripts);
MaybeLocal<UnboundScript> CompileInspectorScript(Isolate* isolate,
Local<String> source);
class DebugEventListener {
class DebugDelegate {
public:
virtual ~DebugEventListener() {}
virtual ~DebugDelegate() {}
virtual void PromiseEventOccurred(debug::PromiseDebugActionType type,
int id) {}
virtual void ScriptCompiled(v8::Local<Script> script,
@ -161,9 +161,17 @@ class DebugEventListener {
v8::Local<v8::Object> exec_state,
v8::Local<v8::Value> exception,
bool is_promise_rejection, bool is_uncaught) {}
virtual bool IsFunctionBlackboxed(v8::Local<debug::Script> script,
const debug::Location& start,
const debug::Location& end) {
return false;
}
};
void SetDebugEventListener(Isolate* isolate, DebugEventListener* listener);
void SetDebugDelegate(Isolate* isolate, DebugDelegate* listener);
void ResetBlackboxedStateCache(Isolate* isolate,
v8::Local<debug::Script> script);
} // namespace debug
} // namespace v8

View File

@ -840,7 +840,8 @@ void Debug::FloodWithOneShot(Handle<JSFunction> function,
// Debug utility functions are not subject to debugging.
if (function->native_context() == *debug_context()) return;
if (!function->shared()->IsSubjectToDebugging()) {
if (!function->shared()->IsSubjectToDebugging() ||
IsBlackboxed(function->shared())) {
// Builtin functions are not subject to stepping, but need to be
// deoptimized, because optimized code does not check for debug
// step in at call sites.
@ -959,7 +960,8 @@ void Debug::PrepareStepOnThrow() {
// Find the closest Javascript frame we can flood with one-shots.
while (!it.done() &&
!it.frame()->function()->shared()->IsSubjectToDebugging()) {
(!it.frame()->function()->shared()->IsSubjectToDebugging() ||
IsBlackboxed(it.frame()->function()->shared()))) {
it.Advance();
}
@ -1019,6 +1021,8 @@ void Debug::PrepareStep(StepAction step_action) {
if (location.IsReturn()) step_action = StepOut;
// A step-next at a tail call is a step-out.
if (location.IsTailCall() && step_action == StepNext) step_action = StepOut;
// A step-next in blackboxed function is a step-out.
if (step_action == StepNext && IsBlackboxed(shared)) step_action = StepOut;
thread_local_.last_statement_position_ =
summary.abstract_code()->SourceStatementPosition(summary.code_offset());
@ -1034,8 +1038,10 @@ void Debug::PrepareStep(StepAction step_action) {
// Advance to caller frame.
frames_it.Advance();
// Skip native and extension functions on the stack.
while (!frames_it.done() &&
!frames_it.frame()->function()->shared()->IsSubjectToDebugging()) {
while (
!frames_it.done() &&
(!frames_it.frame()->function()->shared()->IsSubjectToDebugging() ||
IsBlackboxed(frames_it.frame()->function()->shared()))) {
// Builtin functions are not subject to stepping, but need to be
// deoptimized to include checks for step-in at call sites.
Deoptimizer::DeoptimizeFunction(frames_it.frame()->function());
@ -1751,15 +1757,18 @@ void Debug::OnException(Handle<Object> exception, Handle<Object> promise) {
}
{
// Check whether the break location is muted.
JavaScriptFrameIterator it(isolate_);
if (!it.done() && IsMutedAtCurrentLocation(it.frame())) return;
// Check whether the top frame is blackboxed or the break location is muted.
if (!it.done() && (IsBlackboxed(it.frame()->function()->shared()) ||
IsMutedAtCurrentLocation(it.frame()))) {
return;
}
}
DebugScope debug_scope(this);
if (debug_scope.failed()) return;
if (debug_event_listener_) {
if (debug_delegate_) {
HandleScope scope(isolate_);
// Create the execution state.
@ -1767,7 +1776,7 @@ void Debug::OnException(Handle<Object> exception, Handle<Object> promise) {
// Bail out and don't call debugger if exception.
if (!MakeExecutionState().ToHandle(&exec_state)) return;
debug_event_listener_->ExceptionThrown(
debug_delegate_->ExceptionThrown(
GetDebugEventContext(isolate_),
v8::Utils::ToLocal(Handle<JSObject>::cast(exec_state)),
v8::Utils::ToLocal(exception), promise->IsJSObject(), uncaught);
@ -1797,7 +1806,7 @@ void Debug::OnDebugBreak(Handle<Object> break_points_hit) {
PrintBreakLocation();
#endif // DEBUG
if (debug_event_listener_) {
if (debug_delegate_) {
HandleScope scope(isolate_);
// Create the execution state.
@ -1807,7 +1816,7 @@ void Debug::OnDebugBreak(Handle<Object> break_points_hit) {
bool previous = in_debug_event_listener_;
in_debug_event_listener_ = true;
debug_event_listener_->BreakProgramRequested(
debug_delegate_->BreakProgramRequested(
GetDebugEventContext(isolate_),
v8::Utils::ToLocal(Handle<JSObject>::cast(exec_state)),
v8::Utils::ToLocal(break_points_hit));
@ -1891,11 +1900,46 @@ int Debug::NextAsyncTaskId(Handle<JSObject> promise) {
return async_id->value();
}
namespace {
debug::Location GetDebugLocation(Handle<Script> script, int source_position) {
Script::PositionInfo info;
Script::GetPositionInfo(script, source_position, &info, Script::WITH_OFFSET);
return debug::Location(info.line, info.column);
}
} // namespace
bool Debug::IsBlackboxed(SharedFunctionInfo* shared) {
HandleScope scope(isolate_);
Handle<SharedFunctionInfo> shared_function_info(shared);
return IsBlackboxed(shared_function_info);
}
bool Debug::IsBlackboxed(Handle<SharedFunctionInfo> shared) {
if (!debug_delegate_) return false;
if (!shared->computed_debug_is_blackboxed()) {
bool is_blackboxed = false;
if (shared->script()->IsScript()) {
HandleScope handle_scope(isolate_);
Handle<Script> script(Script::cast(shared->script()));
if (script->type() == i::Script::TYPE_NORMAL) {
debug::Location start =
GetDebugLocation(script, shared->start_position());
debug::Location end = GetDebugLocation(script, shared->end_position());
is_blackboxed = debug_delegate_->IsFunctionBlackboxed(
ToApiHandle<debug::Script>(script), start, end);
}
}
shared->set_debug_is_blackboxed(is_blackboxed);
shared->set_computed_debug_is_blackboxed(true);
}
return shared->debug_is_blackboxed();
}
void Debug::OnAsyncTaskEvent(debug::PromiseDebugActionType type, int id) {
if (in_debug_scope() || ignore_events()) return;
if (debug_event_listener_) {
debug_event_listener_->PromiseEventOccurred(type, id);
if (debug_delegate_) {
debug_delegate_->PromiseEventOccurred(type, id);
if (!non_inspector_listener_exists()) return;
}
@ -1967,9 +2011,9 @@ void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) {
DebugScope debug_scope(this);
if (debug_scope.failed()) return;
if (debug_event_listener_) {
debug_event_listener_->ScriptCompiled(ToApiHandle<debug::Script>(script),
event != v8::AfterCompile);
if (debug_delegate_) {
debug_delegate_->ScriptCompiled(ToApiHandle<debug::Script>(script),
event != v8::AfterCompile);
if (!non_inspector_listener_exists()) return;
}
@ -2013,15 +2057,13 @@ void Debug::SetEventListener(Handle<Object> callback,
UpdateState();
}
void Debug::SetDebugEventListener(debug::DebugEventListener* listener) {
debug_event_listener_ = listener;
void Debug::SetDebugDelegate(debug::DebugDelegate* delegate) {
debug_delegate_ = delegate;
UpdateState();
}
void Debug::UpdateState() {
bool is_active =
!event_listener_.is_null() || debug_event_listener_ != nullptr;
bool is_active = !event_listener_.is_null() || debug_delegate_ != nullptr;
if (is_active || in_debug_scope()) {
// Note that the debug context could have already been loaded to
// bootstrap test cases.
@ -2078,6 +2120,11 @@ void Debug::HandleDebugBreak() {
if (fun && fun->IsJSFunction()) {
// Don't stop in builtin functions.
if (!JSFunction::cast(fun)->shared()->IsSubjectToDebugging()) return;
if (isolate_->stack_guard()->CheckDebugBreak() &&
IsBlackboxed(JSFunction::cast(fun)->shared())) {
Deoptimizer::DeoptimizeFunction(JSFunction::cast(fun));
return;
}
JSGlobalObject* global =
JSFunction::cast(fun)->context()->global_object();
// Don't stop in debugger functions.

View File

@ -356,7 +356,9 @@ class Debug {
int NextAsyncTaskId(Handle<JSObject> promise);
void SetDebugEventListener(debug::DebugEventListener* listener);
bool IsBlackboxed(Handle<SharedFunctionInfo> shared);
void SetDebugDelegate(debug::DebugDelegate* delegate);
// Returns whether the operation succeeded. Compilation can only be triggered
// if a valid closure is passed as the second argument, otherwise the shared
@ -494,6 +496,8 @@ class Debug {
return !event_listener_.is_null() && !event_listener_->IsForeign();
}
bool IsBlackboxed(SharedFunctionInfo* shared);
void OnException(Handle<Object> exception, Handle<Object> promise);
// Constructors for debug event objects.
@ -554,7 +558,7 @@ class Debug {
Handle<Object> event_listener_;
Handle<Object> event_listener_data_;
debug::DebugEventListener* debug_event_listener_ = nullptr;
debug::DebugDelegate* debug_delegate_ = nullptr;
// Debugger is active, i.e. there is a debug event listener attached.
bool is_active_;

View File

@ -9,4 +9,5 @@ include_rules = [
"+src/tracing",
"-include/v8-debug.h",
"+src/debug/debug-interface.h",
"+src/debug/interface-types.h",
]

View File

@ -54,7 +54,6 @@ static const char skipAllPauses[] = "skipAllPauses";
} // namespace DebuggerAgentState
static const int kMaxSkipStepFrameCount = 128;
static const char kBacktraceObjectGroup[] = "backtrace";
static const char kDebuggerNotEnabled[] = "Debugger agent is not enabled";
static const char kDebuggerNotPaused[] =
@ -134,13 +133,8 @@ V8DebuggerAgentImpl::V8DebuggerAgentImpl(
m_isolate(m_inspector->isolate()),
m_breakReason(protocol::Debugger::Paused::ReasonEnum::Other),
m_scheduledDebuggerStep(NoStep),
m_skipNextDebuggerStepOut(false),
m_javaScriptPauseScheduled(false),
m_steppingFromFramework(false),
m_pausingOnNativeEvent(false),
m_skippedStepFrameCount(0),
m_recursionLevelForStepOut(0),
m_recursionLevelForStepFrame(0),
m_skipAllPauses(false) {
clearBreakDetails();
}
@ -190,21 +184,17 @@ Response V8DebuggerAgentImpl::disable() {
m_pausedContext.Reset();
JavaScriptCallFrames emptyCallFrames;
m_pausedCallFrames.swap(emptyCallFrames);
m_scripts.clear();
m_blackboxedPositions.clear();
m_blackboxPattern.reset();
resetBlackboxedStateCache();
m_scripts.clear();
m_breakpointIdToDebuggerBreakpointIds.clear();
m_debugger->setAsyncCallStackDepth(this, 0);
m_continueToLocationBreakpointId = String16();
clearBreakDetails();
m_scheduledDebuggerStep = NoStep;
m_skipNextDebuggerStepOut = false;
m_javaScriptPauseScheduled = false;
m_steppingFromFramework = false;
m_pausingOnNativeEvent = false;
m_skippedStepFrameCount = 0;
m_recursionLevelForStepFrame = 0;
m_skipAllPauses = false;
m_blackboxPattern = nullptr;
m_state->remove(DebuggerAgentState::blackboxPattern);
m_enabled = false;
m_state->setBoolean(DebuggerAgentState::debuggerEnabled, false);
@ -434,28 +424,10 @@ Response V8DebuggerAgentImpl::continueToLocation(
return resume();
}
bool V8DebuggerAgentImpl::isCurrentCallStackEmptyOrBlackboxed() {
DCHECK(enabled());
JavaScriptCallFrames callFrames = m_debugger->currentCallFrames();
for (size_t index = 0; index < callFrames.size(); ++index) {
if (!isCallFrameWithUnknownScriptOrBlackboxed(callFrames[index].get()))
return false;
}
return true;
}
bool V8DebuggerAgentImpl::isTopPausedCallFrameBlackboxed() {
DCHECK(enabled());
JavaScriptCallFrame* frame =
m_pausedCallFrames.size() ? m_pausedCallFrames[0].get() : nullptr;
return isCallFrameWithUnknownScriptOrBlackboxed(frame);
}
bool V8DebuggerAgentImpl::isCallFrameWithUnknownScriptOrBlackboxed(
JavaScriptCallFrame* frame) {
if (!frame) return true;
ScriptsMap::iterator it =
m_scripts.find(String16::fromInteger(frame->sourceID()));
bool V8DebuggerAgentImpl::isFunctionBlackboxed(const String16& scriptId,
const v8::debug::Location& start,
const v8::debug::Location& end) {
ScriptsMap::iterator it = m_scripts.find(scriptId);
if (it == m_scripts.end()) {
// Unknown scripts are blackboxed.
return true;
@ -466,48 +438,24 @@ bool V8DebuggerAgentImpl::isCallFrameWithUnknownScriptOrBlackboxed(
m_blackboxPattern->match(scriptSourceURL) != -1)
return true;
}
auto itBlackboxedPositions =
m_blackboxedPositions.find(String16::fromInteger(frame->sourceID()));
auto itBlackboxedPositions = m_blackboxedPositions.find(scriptId);
if (itBlackboxedPositions == m_blackboxedPositions.end()) return false;
const std::vector<std::pair<int, int>>& ranges =
itBlackboxedPositions->second;
auto itRange = std::lower_bound(
auto itStartRange = std::lower_bound(
ranges.begin(), ranges.end(),
std::make_pair(frame->line(), frame->column()), positionComparator);
std::make_pair(start.GetLineNumber(), start.GetColumnNumber()),
positionComparator);
auto itEndRange = std::lower_bound(
itStartRange, ranges.end(),
std::make_pair(end.GetLineNumber(), end.GetColumnNumber()),
positionComparator);
// Ranges array contains positions in script where blackbox state is changed.
// [(0,0) ... ranges[0]) isn't blackboxed, [ranges[0] ... ranges[1]) is
// blackboxed...
return std::distance(ranges.begin(), itRange) % 2;
}
V8DebuggerAgentImpl::SkipPauseRequest
V8DebuggerAgentImpl::shouldSkipExceptionPause(
JavaScriptCallFrame* topCallFrame) {
if (m_steppingFromFramework) return RequestNoSkip;
if (isCallFrameWithUnknownScriptOrBlackboxed(topCallFrame))
return RequestContinue;
return RequestNoSkip;
}
V8DebuggerAgentImpl::SkipPauseRequest V8DebuggerAgentImpl::shouldSkipStepPause(
JavaScriptCallFrame* topCallFrame) {
if (m_steppingFromFramework) return RequestNoSkip;
if (m_skipNextDebuggerStepOut) {
m_skipNextDebuggerStepOut = false;
if (m_scheduledDebuggerStep == StepOut) return RequestStepOut;
}
if (!isCallFrameWithUnknownScriptOrBlackboxed(topCallFrame))
return RequestNoSkip;
if (m_skippedStepFrameCount >= kMaxSkipStepFrameCount) return RequestStepOut;
if (!m_skippedStepFrameCount) m_recursionLevelForStepFrame = 1;
++m_skippedStepFrameCount;
return RequestStepFrame;
return itStartRange == itEndRange &&
std::distance(ranges.begin(), itStartRange) % 2;
}
std::unique_ptr<protocol::Debugger::Location>
@ -641,8 +589,6 @@ void V8DebuggerAgentImpl::schedulePauseOnNextStatement(
return;
m_breakReason = breakReason;
m_breakAuxData = std::move(data);
m_pausingOnNativeEvent = true;
m_skipNextDebuggerStepOut = false;
m_debugger->setPauseOnNextStatement(true);
}
@ -652,16 +598,12 @@ void V8DebuggerAgentImpl::schedulePauseOnNextStatementIfSteppingInto() {
m_debugger->isPaused())
return;
clearBreakDetails();
m_pausingOnNativeEvent = false;
m_skippedStepFrameCount = 0;
m_recursionLevelForStepFrame = 0;
m_debugger->setPauseOnNextStatement(true);
}
void V8DebuggerAgentImpl::cancelPauseOnNextStatement() {
if (m_javaScriptPauseScheduled || m_debugger->isPaused()) return;
clearBreakDetails();
m_pausingOnNativeEvent = false;
m_debugger->setPauseOnNextStatement(false);
}
@ -672,8 +614,6 @@ Response V8DebuggerAgentImpl::pause() {
clearBreakDetails();
m_javaScriptPauseScheduled = true;
m_scheduledDebuggerStep = NoStep;
m_skippedStepFrameCount = 0;
m_steppingFromFramework = false;
m_debugger->setPauseOnNextStatement(true);
return Response::OK();
}
@ -681,7 +621,6 @@ Response V8DebuggerAgentImpl::pause() {
Response V8DebuggerAgentImpl::resume() {
if (m_pausedContext.IsEmpty()) return Response::Error(kDebuggerNotPaused);
m_scheduledDebuggerStep = NoStep;
m_steppingFromFramework = false;
m_session->releaseObjectGroup(kBacktraceObjectGroup);
m_debugger->continueProgram();
return Response::OK();
@ -694,7 +633,6 @@ Response V8DebuggerAgentImpl::stepOver() {
!m_pausedCallFrames.empty() ? m_pausedCallFrames[0].get() : nullptr;
if (frame && frame->isAtReturn()) return stepInto();
m_scheduledDebuggerStep = StepOver;
m_steppingFromFramework = isTopPausedCallFrameBlackboxed();
m_session->releaseObjectGroup(kBacktraceObjectGroup);
m_debugger->stepOverStatement();
return Response::OK();
@ -703,7 +641,6 @@ Response V8DebuggerAgentImpl::stepOver() {
Response V8DebuggerAgentImpl::stepInto() {
if (m_pausedContext.IsEmpty()) return Response::Error(kDebuggerNotPaused);
m_scheduledDebuggerStep = StepInto;
m_steppingFromFramework = isTopPausedCallFrameBlackboxed();
m_session->releaseObjectGroup(kBacktraceObjectGroup);
m_debugger->stepIntoStatement();
return Response::OK();
@ -712,9 +649,7 @@ Response V8DebuggerAgentImpl::stepInto() {
Response V8DebuggerAgentImpl::stepOut() {
if (m_pausedContext.IsEmpty()) return Response::Error(kDebuggerNotPaused);
m_scheduledDebuggerStep = StepOut;
m_skipNextDebuggerStepOut = false;
m_recursionLevelForStepOut = 1;
m_steppingFromFramework = isTopPausedCallFrameBlackboxed();
m_session->releaseObjectGroup(kBacktraceObjectGroup);
m_debugger->stepOutOfFunction();
return Response::OK();
@ -811,6 +746,7 @@ Response V8DebuggerAgentImpl::setBlackboxPatterns(
std::unique_ptr<protocol::Array<String16>> patterns) {
if (!patterns->length()) {
m_blackboxPattern = nullptr;
resetBlackboxedStateCache();
m_state->remove(DebuggerAgentState::blackboxPattern);
return Response::OK();
}
@ -826,6 +762,7 @@ Response V8DebuggerAgentImpl::setBlackboxPatterns(
String16 pattern = patternBuilder.toString();
Response response = setBlackboxPattern(pattern);
if (!response.isSuccess()) return response;
resetBlackboxedStateCache();
m_state->setString(DebuggerAgentState::blackboxPattern, pattern);
return Response::OK();
}
@ -839,15 +776,23 @@ Response V8DebuggerAgentImpl::setBlackboxPattern(const String16& pattern) {
return Response::OK();
}
void V8DebuggerAgentImpl::resetBlackboxedStateCache() {
for (const auto& it : m_scripts) {
it.second->resetBlackboxedStateCache();
}
}
Response V8DebuggerAgentImpl::setBlackboxedRanges(
const String16& scriptId,
std::unique_ptr<protocol::Array<protocol::Debugger::ScriptPosition>>
inPositions) {
if (m_scripts.find(scriptId) == m_scripts.end())
auto it = m_scripts.find(scriptId);
if (it == m_scripts.end())
return Response::Error("No script with passed id.");
if (!inPositions->length()) {
m_blackboxedPositions.erase(scriptId);
it->second->resetBlackboxedStateCache();
return Response::OK();
}
@ -873,6 +818,7 @@ Response V8DebuggerAgentImpl::setBlackboxedRanges(
}
m_blackboxedPositions[scriptId] = positions;
it->second->resetBlackboxedStateCache();
return Response::OK();
}
@ -901,27 +847,6 @@ void V8DebuggerAgentImpl::changeJavaScriptRecursionLevel(int step) {
// switch stepping to step into a next JS task, as if we exited to a
// blackboxed framework.
m_scheduledDebuggerStep = StepInto;
m_skipNextDebuggerStepOut = false;
}
}
if (m_recursionLevelForStepFrame) {
m_recursionLevelForStepFrame += step;
if (!m_recursionLevelForStepFrame) {
// We have walked through a blackboxed framework and got back to where we
// started.
// If there was no stepping scheduled, we should cancel the stepping
// explicitly,
// since there may be a scheduled StepFrame left.
// Otherwise, if we were stepping in/over, the StepFrame will stop at the
// right location,
// whereas if we were stepping out, we should continue doing so after
// debugger pauses
// from the old StepFrame.
m_skippedStepFrameCount = 0;
if (m_scheduledDebuggerStep == NoStep)
m_debugger->clearStepping();
else if (m_scheduledDebuggerStep == StepOut)
m_skipNextDebuggerStepOut = true;
}
}
}
@ -1072,6 +997,11 @@ void V8DebuggerAgentImpl::didParseSource(
ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId);
DCHECK(scriptIterator != m_scripts.end());
V8DebuggerScript* scriptRef = scriptIterator->second.get();
// V8 could create functions for parsed scripts before reporting and asks
// inspector about blackboxed state, we should reset state each time when we
// make any change that change isFunctionBlackboxed output - adding parsed
// script is changing.
scriptRef->resetBlackboxedStateCache();
Maybe<String16> sourceMapURLParam = scriptRef->sourceMappingURL();
Maybe<protocol::DictionaryValue> executionContextAuxDataParam(
@ -1121,35 +1051,19 @@ void V8DebuggerAgentImpl::didParseSource(
}
}
V8DebuggerAgentImpl::SkipPauseRequest V8DebuggerAgentImpl::didPause(
v8::Local<v8::Context> context, v8::Local<v8::Value> exception,
const std::vector<String16>& hitBreakpoints, bool isPromiseRejection,
bool isUncaught, bool isOOMBreak) {
JavaScriptCallFrames callFrames = m_debugger->currentCallFrames(1);
JavaScriptCallFrame* topCallFrame =
!callFrames.empty() ? callFrames.begin()->get() : nullptr;
V8DebuggerAgentImpl::SkipPauseRequest result;
if (isOOMBreak)
result = RequestNoSkip;
else if (m_skipAllPauses)
result = RequestContinue;
else if (!hitBreakpoints.empty())
result = RequestNoSkip; // Don't skip explicit breakpoints even if set in
// frameworks.
else if (!exception.IsEmpty())
result = shouldSkipExceptionPause(topCallFrame);
else if (m_scheduledDebuggerStep != NoStep || m_javaScriptPauseScheduled ||
m_pausingOnNativeEvent)
result = shouldSkipStepPause(topCallFrame);
else
result = RequestNoSkip;
m_skipNextDebuggerStepOut = false;
if (result != RequestNoSkip) return result;
// Skip pauses inside V8 internal scripts and on syntax errors.
if (!topCallFrame) return RequestContinue;
bool V8DebuggerAgentImpl::didPause(v8::Local<v8::Context> context,
v8::Local<v8::Value> exception,
const std::vector<String16>& hitBreakpoints,
bool isPromiseRejection, bool isUncaught,
bool isOOMBreak) {
if (!isOOMBreak) {
if (m_skipAllPauses) return false;
JavaScriptCallFrames callFrames = m_debugger->currentCallFrames(1);
JavaScriptCallFrame* topCallFrame =
!callFrames.empty() ? callFrames.begin()->get() : nullptr;
// Skip pauses inside V8 internal scripts and on syntax errors.
if (!topCallFrame) return false;
}
DCHECK(m_pausedContext.IsEmpty());
JavaScriptCallFrames frames = m_debugger->currentCallFrames();
m_pausedCallFrames.swap(frames);
@ -1205,16 +1119,12 @@ V8DebuggerAgentImpl::SkipPauseRequest V8DebuggerAgentImpl::didPause(
currentAsyncStackTrace());
m_scheduledDebuggerStep = NoStep;
m_javaScriptPauseScheduled = false;
m_steppingFromFramework = false;
m_pausingOnNativeEvent = false;
m_skippedStepFrameCount = 0;
m_recursionLevelForStepFrame = 0;
if (!m_continueToLocationBreakpointId.isEmpty()) {
m_debugger->removeBreakpoint(m_continueToLocationBreakpointId);
m_continueToLocationBreakpointId = "";
}
return result;
return true;
}
void V8DebuggerAgentImpl::didContinue() {
@ -1229,14 +1139,11 @@ void V8DebuggerAgentImpl::breakProgram(
const String16& breakReason,
std::unique_ptr<protocol::DictionaryValue> data) {
if (!enabled() || m_skipAllPauses || !m_pausedContext.IsEmpty() ||
isCurrentCallStackEmptyOrBlackboxed() ||
!m_debugger->breakpointsActivated())
!m_debugger->canBreakProgram())
return;
m_breakReason = breakReason;
m_breakAuxData = std::move(data);
m_scheduledDebuggerStep = NoStep;
m_steppingFromFramework = false;
m_pausingOnNativeEvent = false;
m_debugger->breakProgram();
}
@ -1274,8 +1181,9 @@ void V8DebuggerAgentImpl::removeBreakpointAt(const String16& scriptId,
void V8DebuggerAgentImpl::reset() {
if (!enabled()) return;
m_scheduledDebuggerStep = NoStep;
m_scripts.clear();
m_blackboxedPositions.clear();
resetBlackboxedStateCache();
m_scripts.clear();
m_breakpointIdToDebuggerBreakpointIds.clear();
}

View File

@ -8,6 +8,7 @@
#include <vector>
#include "src/base/macros.h"
#include "src/debug/interface-types.h"
#include "src/inspector/java-script-call-frame.h"
#include "src/inspector/protocol/Debugger.h"
#include "src/inspector/protocol/Forward.h"
@ -29,14 +30,6 @@ using protocol::Response;
class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
public:
enum SkipPauseRequest {
RequestNoSkip,
RequestContinue,
RequestStepInto,
RequestStepOut,
RequestStepFrame
};
enum BreakpointSource {
UserBreakpointSource,
DebugCommandBreakpointSource,
@ -134,24 +127,23 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
void reset();
// Interface for V8InspectorImpl
SkipPauseRequest didPause(v8::Local<v8::Context>,
v8::Local<v8::Value> exception,
const std::vector<String16>& hitBreakpoints,
bool isPromiseRejection, bool isUncaught,
bool isOOMBreak);
bool didPause(v8::Local<v8::Context>, v8::Local<v8::Value> exception,
const std::vector<String16>& hitBreakpoints,
bool isPromiseRejection, bool isUncaught, bool isOOMBreak);
void didContinue();
void didParseSource(std::unique_ptr<V8DebuggerScript>, bool success);
void willExecuteScript(int scriptId);
void didExecuteScript();
bool isFunctionBlackboxed(const String16& scriptId,
const v8::debug::Location& start,
const v8::debug::Location& end);
v8::Isolate* isolate() { return m_isolate; }
private:
void enableImpl();
SkipPauseRequest shouldSkipExceptionPause(JavaScriptCallFrame* topCallFrame);
SkipPauseRequest shouldSkipStepPause(JavaScriptCallFrame* topCallFrame);
void schedulePauseOnNextStatementIfSteppingInto();
Response currentCallFrames(
@ -167,14 +159,11 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
void removeBreakpointImpl(const String16& breakpointId);
void clearBreakDetails();
bool isCurrentCallStackEmptyOrBlackboxed();
bool isTopPausedCallFrameBlackboxed();
bool isCallFrameWithUnknownScriptOrBlackboxed(JavaScriptCallFrame*);
void internalSetAsyncCallStackDepth(int);
void increaseCachedSkipStackGeneration();
Response setBlackboxPattern(const String16& pattern);
void resetBlackboxedStateCache();
using ScriptsMap =
protocol::HashMap<String16, std::unique_ptr<V8DebuggerScript>>;
@ -202,14 +191,9 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
String16 m_breakReason;
std::unique_ptr<protocol::DictionaryValue> m_breakAuxData;
DebuggerStep m_scheduledDebuggerStep;
bool m_skipNextDebuggerStepOut;
bool m_javaScriptPauseScheduled;
bool m_steppingFromFramework;
bool m_pausingOnNativeEvent;
int m_skippedStepFrameCount;
int m_recursionLevelForStepOut;
int m_recursionLevelForStepFrame;
bool m_skipAllPauses;
std::unique_ptr<V8Regex> m_blackboxPattern;

View File

@ -148,6 +148,11 @@ class ActualScript : public V8DebuggerScript {
return script->GetPossibleBreakpoints(start, end, locations);
}
void resetBlackboxedStateCache() override {
v8::HandleScope scope(m_isolate);
v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
}
private:
String16 GetNameOrSourceUrl(v8::Local<v8::debug::Script> script) {
v8::Local<v8::String> name;
@ -197,6 +202,8 @@ class WasmVirtualScript : public V8DebuggerScript {
return false;
}
void resetBlackboxedStateCache() override {}
private:
static const String16& emptyString() {
static const String16 singleEmptyString;

View File

@ -73,6 +73,7 @@ class V8DebuggerScript {
virtual bool getPossibleBreakpoints(
const v8::debug::Location& start, const v8::debug::Location& end,
std::vector<v8::debug::Location>* locations) = 0;
virtual void resetBlackboxedStateCache() = 0;
protected:
V8DebuggerScript(v8::Isolate*, String16 id, String16 url);

View File

@ -30,6 +30,18 @@ inline v8::Local<v8::Boolean> v8Boolean(bool value, v8::Isolate* isolate) {
return value ? v8::True(isolate) : v8::False(isolate);
}
V8DebuggerAgentImpl* agentForScript(V8InspectorImpl* inspector,
v8::Local<v8::debug::Script> script) {
v8::Local<v8::Value> contextData;
if (!script->ContextData().ToLocal(&contextData) || !contextData->IsInt32()) {
return nullptr;
}
int contextId = static_cast<int>(contextData.As<v8::Int32>()->Value());
int contextGroupId = inspector->contextGroupId(contextId);
if (!contextGroupId) return nullptr;
return inspector->enabledDebuggerAgentForGroup(contextGroupId);
}
} // namespace
static bool inLiveEditScope = false;
@ -68,7 +80,7 @@ void V8Debugger::enable() {
if (m_enableCount++) return;
DCHECK(!enabled());
v8::HandleScope scope(m_isolate);
v8::debug::SetDebugEventListener(m_isolate, this);
v8::debug::SetDebugDelegate(m_isolate, this);
v8::debug::SetOutOfMemoryCallback(m_isolate, &V8Debugger::v8OOMCallback,
this);
m_debuggerContext.Reset(m_isolate, v8::debug::GetDebugContext(m_isolate));
@ -85,7 +97,7 @@ void V8Debugger::disable() {
m_debuggerContext.Reset();
allAsyncTasksCanceled();
m_wasmTranslation.Clear();
v8::debug::SetDebugEventListener(m_isolate, nullptr);
v8::debug::SetDebugDelegate(m_isolate, nullptr);
v8::debug::SetOutOfMemoryCallback(m_isolate, nullptr, nullptr);
m_isolate->RestoreOriginalHeapLimit();
}
@ -243,7 +255,7 @@ void V8Debugger::setPauseOnNextStatement(bool pause) {
bool V8Debugger::canBreakProgram() {
if (!m_breakpointsActivated) return false;
return m_isolate->InContext();
return v8::debug::HasNonBlackboxedFrameOnStack(m_isolate);
}
void V8Debugger::breakProgram() {
@ -296,11 +308,6 @@ void V8Debugger::stepOutOfFunction() {
continueProgram();
}
void V8Debugger::clearStepping() {
DCHECK(enabled());
v8::debug::ClearStepping(m_isolate);
}
Response V8Debugger::setScriptSource(
const String16& sourceID, v8::Local<v8::String> newSource, bool dryRun,
Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails,
@ -480,10 +487,10 @@ void V8Debugger::handleProgramBreak(v8::Local<v8::Context> pausedContext,
m_pausedContext = pausedContext;
m_executionState = executionState;
V8DebuggerAgentImpl::SkipPauseRequest result =
bool shouldPause =
agent->didPause(pausedContext, exception, breakpointIds,
isPromiseRejection, isUncaught, m_scheduledOOMBreak);
if (result == V8DebuggerAgentImpl::RequestNoSkip) {
if (shouldPause) {
m_runningNestedMessageLoop = true;
int groupId = m_inspector->contextGroupId(pausedContext);
DCHECK(groupId);
@ -502,14 +509,6 @@ void V8Debugger::handleProgramBreak(v8::Local<v8::Context> pausedContext,
m_scheduledOOMBreak = false;
m_pausedContext.Clear();
m_executionState.Clear();
if (result == V8DebuggerAgentImpl::RequestStepFrame) {
v8::debug::PrepareStep(m_isolate, v8::debug::StepFrame);
} else if (result == V8DebuggerAgentImpl::RequestStepInto) {
v8::debug::PrepareStep(m_isolate, v8::debug::StepIn);
} else if (result == V8DebuggerAgentImpl::RequestStepOut) {
v8::debug::PrepareStep(m_isolate, v8::debug::StepOut);
}
}
void V8Debugger::v8OOMCallback(void* data) {
@ -521,15 +520,7 @@ void V8Debugger::v8OOMCallback(void* data) {
void V8Debugger::ScriptCompiled(v8::Local<v8::debug::Script> script,
bool has_compile_error) {
v8::Local<v8::Value> contextData;
if (!script->ContextData().ToLocal(&contextData) || !contextData->IsInt32()) {
return;
}
int contextId = static_cast<int>(contextData.As<v8::Int32>()->Value());
int contextGroupId = m_inspector->contextGroupId(contextId);
if (!contextGroupId) return;
V8DebuggerAgentImpl* agent =
m_inspector->enabledDebuggerAgentForGroup(contextGroupId);
V8DebuggerAgentImpl* agent = agentForScript(m_inspector, script);
if (!agent) return;
if (script->IsWasm()) {
m_wasmTranslation.AddScript(script.As<v8::debug::WasmScript>(), agent);
@ -562,6 +553,15 @@ void V8Debugger::ExceptionThrown(v8::Local<v8::Context> pausedContext,
v8::Local<v8::Array>(), isPromiseRejection, isUncaught);
}
bool V8Debugger::IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,
const v8::debug::Location& start,
const v8::debug::Location& end) {
V8DebuggerAgentImpl* agent = agentForScript(m_inspector, script);
if (!agent) return false;
return agent->isFunctionBlackboxed(String16::fromInteger(script->Id()), start,
end);
}
void V8Debugger::PromiseEventOccurred(v8::debug::PromiseDebugActionType type,
int id) {
if (!m_maxAsyncCallStackDepth) return;

View File

@ -26,7 +26,7 @@ class V8StackTraceImpl;
using protocol::Response;
class V8Debugger : public v8::debug::DebugEventListener {
class V8Debugger : public v8::debug::DebugDelegate {
public:
V8Debugger(v8::Isolate*, V8InspectorImpl*);
~V8Debugger();
@ -48,7 +48,6 @@ class V8Debugger : public v8::debug::DebugEventListener {
void stepIntoStatement();
void stepOverStatement();
void stepOutOfFunction();
void clearStepping();
Response setScriptSource(
const String16& sourceID, v8::Local<v8::String> newSource, bool dryRun,
@ -145,6 +144,9 @@ class V8Debugger : public v8::debug::DebugEventListener {
v8::Local<v8::Object> exec_state,
v8::Local<v8::Value> exception,
bool is_promise_rejection, bool is_uncaught) override;
bool IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,
const v8::debug::Location& start,
const v8::debug::Location& end) override;
v8::Isolate* m_isolate;
V8InspectorImpl* m_inspector;

View File

@ -6227,6 +6227,10 @@ BOOL_ACCESSORS(SharedFunctionInfo, debugger_hints, has_no_side_effect,
kHasNoSideEffect)
BOOL_ACCESSORS(SharedFunctionInfo, debugger_hints, computed_has_no_side_effect,
kComputedHasNoSideEffect)
BOOL_ACCESSORS(SharedFunctionInfo, debugger_hints, debug_is_blackboxed,
kDebugIsBlackboxed)
BOOL_ACCESSORS(SharedFunctionInfo, debugger_hints, computed_debug_is_blackboxed,
kComputedDebugIsBlackboxed)
bool Script::HasValidSource() {
Object* src = this->source();

View File

@ -7374,6 +7374,12 @@ class SharedFunctionInfo: public HeapObject {
// Indicates that |has_no_side_effect| has been computed and set.
DECL_BOOLEAN_ACCESSORS(computed_has_no_side_effect)
// Indicates that the function should be skipped during stepping.
DECL_BOOLEAN_ACCESSORS(debug_is_blackboxed)
// Indicates that |debug_is_blackboxed| has been computed and set.
DECL_BOOLEAN_ACCESSORS(computed_debug_is_blackboxed)
// The function's name if it is non-empty, otherwise the inferred name.
String* DebugName();
@ -7768,6 +7774,8 @@ class SharedFunctionInfo: public HeapObject {
kDeserialized,
kHasNoSideEffect,
kComputedHasNoSideEffect,
kDebugIsBlackboxed,
kComputedDebugIsBlackboxed,
};
// kFunctionKind has to be byte-aligned

View File

@ -0,0 +1,56 @@
Checks that breaks in framework code correctly processed.
Running test: testConsoleAssert
> all frames in framework:
> mixed, top frame in framework:
frameworkAssert (framework.js:9:10)
(anonymous) (user.js:0:0)
Running test: testCaughtException
> all frames in framework:
> mixed, top frame in framework:
Running test: testUncaughtException
> all frames in framework:
> mixed, top frame in framework:
Running test: testBreakpoint
> all frames in framework:
breakpoint (framework.js:24:2)
(anonymous) (framework.js:0:0)
> mixed, top frame in framework:
breakpoint (framework.js:24:2)
(anonymous) (user.js:0:0)
Running test: testDebuggerStatement
> all frames in framework:
debuggerStatement (framework.js:28:2)
(anonymous) (framework.js:0:0)
> mixed, top frame in framework:
debuggerStatement (framework.js:28:2)
(anonymous) (user.js:0:0)
Running test: testSyncDOMBreakpoint
> all frames in framework:
> mixed, top frame in framework:
syncDOMBreakpoint (framework.js:32:2)
(anonymous) (user.js:0:0)
Running test: testAsyncDOMBreakpoint
> all frames in framework:
(anonymous) (user.js:0:0)
Running test: testCaughtSyntaxError
> all frames in framework:
> mixed, top frame in framework:
Running test: testCaughtJSONParseError
> all frames in framework:
> mixed, top frame in framework:

View File

@ -0,0 +1,189 @@
// 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.
print('Checks that breaks in framework code correctly processed.');
InspectorTest.addScript(
`
function frameworkAssert() {
console.assert(false);
}
function throwCaughtError() {
try {
throw new Error();
} catch (e) {
}
}
function throwUncaughtError() {
throw new Error();
}
function breakpoint() {
return 239;
}
function debuggerStatement() {
debugger;
}
function syncDOMBreakpoint() {
breakProgram('', '');
}
function asyncDOMBreakpoint() {
return 42;
}
function throwCaughtSyntaxError() {
try {
eval('}');
} catch (e) {
}
}
function throwFromJSONParse() {
try {
JSON.parse('ping');
} catch (e) {
}
}
//# sourceURL=framework.js`,
7, 26);
InspectorTest.setupScriptMap();
Protocol.Debugger.onPaused(message => {
InspectorTest.logCallFrames(message.params.callFrames);
InspectorTest.log('');
Protocol.Debugger.resume();
});
Protocol.Debugger.enable();
Protocol.Debugger.setBlackboxPatterns({patterns: ['framework\.js']});
InspectorTest.runTestSuite([
function testConsoleAssert(next) {
Protocol.Debugger.setPauseOnExceptions({state: 'all'})
.then(() => InspectorTest.log('> all frames in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'frameworkAssert()//# sourceURL=framework.js'}))
.then(() => InspectorTest.log('> mixed, top frame in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'frameworkAssert()//# sourceURL=user.js'}))
.then(() => Protocol.Debugger.setPauseOnExceptions({state: 'none'}))
.then(next);
},
function testCaughtException(next) {
Protocol.Debugger.setPauseOnExceptions({state: 'all'})
.then(() => InspectorTest.log('> all frames in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'throwCaughtError()//# sourceURL=framework.js'}))
.then(() => InspectorTest.log('> mixed, top frame in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'throwCaughtError()//# sourceURL=user.js'}))
.then(() => Protocol.Debugger.setPauseOnExceptions({state: 'none'}))
.then(next);
},
function testUncaughtException(next) {
Protocol.Debugger.setPauseOnExceptions({state: 'all'})
.then(() => InspectorTest.log('> all frames in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'throwUncaughtError()//# sourceURL=framework.js'}))
.then(() => InspectorTest.log('> mixed, top frame in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'throwUncaughtError()//# sourceURL=user.js'}))
.then(() => Protocol.Debugger.setPauseOnExceptions({state: 'none'}))
.then(next);
},
function testBreakpoint(next) {
Protocol.Debugger.setBreakpointByUrl({lineNumber: 24, url: 'framework.js'})
.then(() => InspectorTest.log('> all frames in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'breakpoint()//# sourceURL=framework.js'}))
.then(() => InspectorTest.log('> mixed, top frame in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'breakpoint()//# sourceURL=user.js'}))
.then(next);
},
function testDebuggerStatement(next) {
InspectorTest.log('> all frames in framework:');
Protocol.Runtime
.evaluate({expression: 'debuggerStatement()//# sourceURL=framework.js'})
.then(() => InspectorTest.log('> mixed, top frame in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'debuggerStatement()//# sourceURL=user.js'}))
.then(next);
},
function testSyncDOMBreakpoint(next) {
InspectorTest.log('> all frames in framework:');
Protocol.Runtime
.evaluate({expression: 'syncDOMBreakpoint()//# sourceURL=framework.js'})
.then(() => InspectorTest.log('> mixed, top frame in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'syncDOMBreakpoint()//# sourceURL=user.js'}))
.then(next);
},
function testAsyncDOMBreakpoint(next) {
schedulePauseOnNextStatement('', '');
InspectorTest.log('> all frames in framework:');
Protocol.Runtime
.evaluate(
{expression: 'asyncDOMBreakpoint()//# sourceURL=framework.js'})
.then(() => cancelPauseOnNextStatement())
.then(
() => Protocol.Runtime.evaluate(
{expression: '42//# sourceURL=user.js'}))
.then(() => schedulePauseOnNextStatement('', ''))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'asyncDOMBreakpoint()//# sourceURL=user.js'}))
.then(next);
},
function testCaughtSyntaxError(next) {
Protocol.Debugger.setPauseOnExceptions({state: 'all'})
.then(() => InspectorTest.log('> all frames in framework:'))
.then(() => Protocol.Runtime.evaluate({
expression: 'throwCaughtSyntaxError()//# sourceURL=framework.js'
}))
.then(() => InspectorTest.log('> mixed, top frame in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'throwCaughtSyntaxError()//# sourceURL=user.js'}))
.then(() => Protocol.Debugger.setPauseOnExceptions({state: 'none'}))
.then(next);
},
function testCaughtJSONParseError(next) {
Protocol.Debugger.setPauseOnExceptions({state: 'all'})
.then(() => InspectorTest.log('> all frames in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'throwFromJSONParse()//# sourceURL=framework.js'}))
.then(() => InspectorTest.log('> mixed, top frame in framework:'))
.then(
() => Protocol.Runtime.evaluate(
{expression: 'throwFromJSONParse()//# sourceURL=user.js'}))
.then(() => Protocol.Debugger.setPauseOnExceptions({state: 'none'}))
.then(next);
}
]);

View File

@ -0,0 +1,140 @@
Checks framework debugging with blackboxed ranges.
Running test: testEntireScript
{
id : <messageId>
result : {
}
}
Running test: testFooNotBlackboxed
{
id : <messageId>
result : {
}
}
foo (test.js:8:12)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
foo (test.js:9:2)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
foo (test.js:10:0)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
Running test: testFooBlackboxed
{
id : <messageId>
result : {
}
}
testFunction (test.js:14:21)
(anonymous) (expr.js:0:0)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
boo (test.js:12:2)
foo (test.js:9:9)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
boo (test.js:13:0)
foo (test.js:9:9)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
testFunction (test.js:16:0)
(anonymous) (expr.js:0:0)
Running test: testBooPartiallyBlackboxed1
{
id : <messageId>
result : {
}
}
foo (test.js:8:12)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
foo (test.js:9:2)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
boo (test.js:12:2)
foo (test.js:9:9)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
boo (test.js:13:0)
foo (test.js:9:9)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
foo (test.js:10:0)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
Running test: testBooPartiallyBlackboxed2
{
id : <messageId>
result : {
}
}
foo (test.js:8:12)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
foo (test.js:9:2)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
boo (test.js:12:2)
foo (test.js:9:9)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
boo (test.js:13:0)
foo (test.js:9:9)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
foo (test.js:10:0)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
Running test: testBooPartiallyBlackboxed3
{
id : <messageId>
result : {
}
}
foo (test.js:8:12)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
foo (test.js:9:2)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
boo (test.js:12:2)
foo (test.js:9:9)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
boo (test.js:13:0)
foo (test.js:9:9)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)
foo (test.js:10:0)
testFunction (test.js:15:2)
(anonymous) (expr.js:0:0)

View File

@ -0,0 +1,78 @@
// 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.
print('Checks framework debugging with blackboxed ranges.');
InspectorTest.addScript(
`
function foo() {
return boo();
}
function boo() {
return 42;
}
function testFunction() {
foo();
}
//# sourceURL=test.js`,
7, 26);
InspectorTest.setupScriptMap();
Protocol.Debugger.onPaused(message => {
InspectorTest.logCallFrames(message.params.callFrames);
InspectorTest.log('');
Protocol.Debugger.stepInto();
});
var scriptId;
Protocol.Debugger.onScriptParsed(message => {
if (message.params.url === 'test.js') {
scriptId = message.params.scriptId;
}
});
Protocol.Debugger.enable()
.then(() => Protocol.Debugger.setBlackboxPatterns({patterns: ['expr\.js']}))
.then(() => InspectorTest.runTestSuite(testSuite));
var testSuite = [
function testEntireScript(next) {
testPositions([position(0, 0)]).then(next);
},
function testFooNotBlackboxed(next) {
testPositions([position(11, 0)]).then(next);
},
function testFooBlackboxed(next) {
testPositions([position(8, 0), position(10, 3)]).then(next);
},
function testBooPartiallyBlackboxed1(next) {
// first line is not blackboxed, second and third - blackboxed.
testPositions([position(12, 0)]).then(next);
},
function testBooPartiallyBlackboxed2(next) {
// first line is blackboxed, second - not, third - blackboxed.
testPositions([
position(11, 0), position(12, 0), position(13, 0)
]).then(next);
},
function testBooPartiallyBlackboxed3(next) {
// first line is blackboxed, second and third - not.
testPositions([
position(11, 0), position(12, 0), position(14, 0)
]).then(next);
}
];
function testPositions(positions) {
schedulePauseOnNextStatement('', '');
return Protocol.Debugger
.setBlackboxedRanges({scriptId: scriptId, positions: positions})
.then(InspectorTest.logMessage)
.then(
() => Protocol.Runtime.evaluate(
{expression: 'testFunction()//# sourceURL=expr.js'}));
}
function position(line, column) {
return {lineNumber: line, columnNumber: column};
}

View File

@ -0,0 +1,100 @@
Checks stepping with blackboxed frames on stack
Running test: testStepIntoFromUser
(anonymous) (expr.js:0:0)
Executing stepInto...
Executing stepInto...
userFoo (user.js:23:2)
frameworkCall (framework.js:10:23)
testStepFromUser (user.js:31:2)
(anonymous) (expr.js:0:0)
Executing stepInto...
Executing stepInto...
userBoo (user.js:27:2)
frameworkCall (framework.js:10:23)
testStepFromUser (user.js:31:2)
(anonymous) (expr.js:0:0)
Executing stepInto...
Executing stepInto...
testStepFromUser (user.js:32:0)
(anonymous) (expr.js:0:0)
Executing resume...
Running test: testStepOverFromUser
(anonymous) (expr.js:0:0)
Executing stepInto...
Executing stepInto...
userFoo (user.js:23:2)
frameworkCall (framework.js:10:23)
testStepFromUser (user.js:31:2)
(anonymous) (expr.js:0:0)
Executing stepOver...
Executing stepOver...
userBoo (user.js:27:2)
frameworkCall (framework.js:10:23)
testStepFromUser (user.js:31:2)
(anonymous) (expr.js:0:0)
Executing stepOver...
Executing stepOver...
testStepFromUser (user.js:32:0)
(anonymous) (expr.js:0:0)
Executing resume...
Running test: testStepOutFromUser
(anonymous) (expr.js:0:0)
Executing stepInto...
Executing stepInto...
userFoo (user.js:23:2)
frameworkCall (framework.js:10:23)
testStepFromUser (user.js:31:2)
(anonymous) (expr.js:0:0)
Executing stepOut...
testStepFromUser (user.js:32:0)
(anonymous) (expr.js:0:0)
Executing resume...
Running test: testStepIntoFromFramework
frameworkBreakAndCall (framework.js:14:2)
testStepFromFramework (user.js:35:2)
(anonymous) (expr.js:0:0)
Executing stepInto...
userFoo (user.js:23:2)
frameworkBreakAndCall (framework.js:15:23)
testStepFromFramework (user.js:35:2)
(anonymous) (expr.js:0:0)
Executing resume...
Running test: testStepOverFromFramework
frameworkBreakAndCall (framework.js:14:2)
testStepFromFramework (user.js:35:2)
(anonymous) (expr.js:0:0)
Executing stepOver...
testStepFromFramework (user.js:36:0)
(anonymous) (expr.js:0:0)
Executing resume...
Running test: testStepOutFromFramework
frameworkBreakAndCall (framework.js:14:2)
testStepFromFramework (user.js:35:2)
(anonymous) (expr.js:0:0)
Executing stepOut...
testStepFromFramework (user.js:36:0)
(anonymous) (expr.js:0:0)
Executing resume...

View File

@ -0,0 +1,113 @@
// 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.
print('Checks stepping with blackboxed frames on stack');
InspectorTest.addScript(
`
function frameworkCall(funcs) {
for (var f of funcs) f();
}
function frameworkBreakAndCall(funcs) {
debugger;
for (var f of funcs) f();
}
//# sourceURL=framework.js`,
8, 4);
InspectorTest.addScript(
`
function userFoo() {
return 1;
}
function userBoo() {
return 2;
}
function testStepFromUser() {
frameworkCall([userFoo, userBoo])
}
function testStepFromFramework() {
frameworkBreakAndCall([userFoo, userBoo]);
}
//# sourceURL=user.js`,
21, 4);
InspectorTest.setupScriptMap();
Protocol.Debugger.enable()
.then(
() => Protocol.Debugger.setBlackboxPatterns(
{patterns: ['framework\.js']}))
.then(() => InspectorTest.runTestSuite(testSuite));
var testSuite = [
function testStepIntoFromUser(next) {
schedulePauseOnNextStatement('', '');
test('testStepFromUser()', [
'print', // before testStepFromUser call
'stepInto', 'stepInto', 'print', // userFoo
'stepInto', 'stepInto', 'print', // userBoo
'stepInto', 'stepInto', 'print' // testStepFromUser
]).then(next);
},
function testStepOverFromUser(next) {
schedulePauseOnNextStatement('', '');
test('testStepFromUser()', [
'print', // before testStepFromUser call
'stepInto', 'stepInto', 'print', // userFoo
'stepOver', 'stepOver', 'print', // userBoo
'stepOver', 'stepOver', 'print' // testStepFromUser
]).then(next);
},
function testStepOutFromUser(next) {
schedulePauseOnNextStatement('', '');
test('testStepFromUser()', [
'print', // before testStepFromUser call
'stepInto', 'stepInto', 'print', // userFoo
'stepOut', 'print' // testStepFromUser
]).then(next);
},
function testStepIntoFromFramework(next) {
test('testStepFromFramework()', [
'print', // frameworkBreakAndCall
'stepInto', 'print', // userFoo
]).then(next);
},
function testStepOverFromFramework(next) {
test('testStepFromFramework()', [
'print', // frameworkBreakAndCall
'stepOver', 'print', // testStepFromFramework
]).then(next);
},
function testStepOutFromFramework(next) {
test('testStepFromFramework()', [
'print', // frameworkBreakAndCall
'stepOut', 'print', // testStepFromFramework
]).then(next);
}
];
function test(entryExpression, actions) {
Protocol.Debugger.onPaused(message => {
var action = actions.shift() || 'resume';
if (action === 'print') {
InspectorTest.logCallFrames(message.params.callFrames);
InspectorTest.log('');
action = actions.shift() || 'resume';
}
if (action) InspectorTest.log(`Executing ${action}...`);
Protocol.Debugger[action]();
});
return Protocol.Runtime.evaluate(
{expression: entryExpression + '//# sourceURL=expr.js'});
}

View File

@ -90,10 +90,15 @@ function setIncorrectRanges(scriptId, response)
function setMixedSourceRanges(scriptId)
{
Protocol.Debugger.onPaused(runAction);
Protocol.Debugger.setBlackboxedRanges({
scriptId: scriptId,
positions: [ { lineNumber: 8, columnNumber: 0 }, { lineNumber: 15, columnNumber: 0 } ] // blackbox ranges for mixed.js
}).then(runAction);
Protocol.Debugger
.setBlackboxedRanges({
scriptId: scriptId,
positions: [
{lineNumber: 6, columnNumber: 0},
{lineNumber: 14, columnNumber: 0}
] // blackbox ranges for mixed.js
})
.then(runAction);
}
var actions = [ "stepOut", "print", "stepOut", "print", "stepOut", "print",