Commit ac50c79a authored by kozyatinskiy's avatar kozyatinskiy Committed by Commit bot

[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}
parent d90e6e12
......@@ -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 {
......
......@@ -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
......
......@@ -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.
......
......@@ -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_;
......
......@@ -9,4 +9,5 @@ include_rules = [
"+src/tracing",
"-include/v8-debug.h",
"+src/debug/debug-interface.h",
"+src/debug/interface-types.h",
]
This diff is collapsed.
......@@ -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;
......
......@@ -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;
......
......@@ -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);
......
......@@ -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;
......
......@@ -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;
......
......@@ -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();
......
......@@ -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
......
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:
// 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);
}
]);
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)
// 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};
}
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...
// 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'});
}
......@@ -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",
......
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