| /* |
| * Copyright 2015 Google Inc. All Rights Reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "cobalt/script/javascriptcore/jsc_debugger.h" |
| |
| #include <cstdlib> |
| #include <string> |
| |
| #include "base/logging.h" |
| #include "base/string_number_conversions.h" |
| #include "cobalt/base/polymorphic_downcast.h" |
| #include "cobalt/script/javascriptcore/jsc_call_frame.h" |
| #include "cobalt/script/javascriptcore/jsc_global_environment.h" |
| #include "cobalt/script/javascriptcore/jsc_global_object.h" |
| #include "cobalt/script/javascriptcore/jsc_object_handle_holder.h" |
| #include "cobalt/script/javascriptcore/jsc_source_provider.h" |
| |
| #include "third_party/WebKit/Source/JavaScriptCore/debugger/DebuggerCallFrame.h" |
| #include "third_party/WebKit/Source/JavaScriptCore/heap/MarkedBlock.h" |
| #include "third_party/WebKit/Source/JavaScriptCore/interpreter/CallFrame.h" |
| #include "third_party/WebKit/Source/JavaScriptCore/parser/SourceProvider.h" |
| #include "third_party/WebKit/Source/JavaScriptCore/runtime/Executable.h" |
| #include "third_party/WebKit/Source/JavaScriptCore/runtime/JSCell.h" |
| #include "third_party/WebKit/Source/JavaScriptCore/runtime/JSFunction.h" |
| #include "third_party/WebKit/Source/JavaScriptCore/runtime/JSGlobalData.h" |
| #include "third_party/WebKit/Source/JavaScriptCore/runtime/JSGlobalObject.h" |
| #include "third_party/WebKit/Source/JavaScriptCore/runtime/JSScope.h" |
| #include "third_party/WebKit/Source/JavaScriptCore/runtime/JSValue.h" |
| |
| namespace cobalt { |
| namespace script { |
| |
| // Static factory method declared in public interface. |
| scoped_ptr<ScriptDebugger> ScriptDebugger::CreateDebugger( |
| GlobalEnvironment* global_environment, Delegate* delegate) { |
| return scoped_ptr<ScriptDebugger>( |
| new javascriptcore::JSCDebugger(global_environment, delegate)); |
| } |
| |
| namespace javascriptcore { |
| |
| namespace { |
| // Type used to store a set of source providers. |
| typedef WTF::HashSet<JSC::SourceProvider*> SourceProviderSet; |
| |
| // Functor to iterate over the JS cells and gather source providers. |
| class GathererFunctor : public JSC::MarkedBlock::VoidFunctor { |
| public: |
| GathererFunctor(JSC::JSGlobalObject* global_object, |
| SourceProviderSet* source_providers) |
| : global_object_(global_object), source_providers_(source_providers) {} |
| |
| void operator()(JSC::JSCell* cell) { |
| JSC::JSFunction* function = JSC::jsDynamicCast<JSC::JSFunction*>(cell); |
| if (function && !function->isHostFunction() && |
| function->scope()->globalObject() == global_object_ && |
| function->executable()->isFunctionExecutable()) { |
| source_providers_->add( |
| JSC::jsCast<JSC::FunctionExecutable*>(function->executable()) |
| ->source() |
| .provider()); |
| } |
| } |
| |
| private: |
| SourceProviderSet* source_providers_; |
| JSC::JSGlobalObject* global_object_; |
| }; |
| |
| // Uses the GatherorFunctor defined above to gather all the currently parsed |
| // source providers defined in |global_object| and populate |source_providers|. |
| // This is called once by the |attach| method; the script debugger is |
| // automatically notified of subsequently parsed scripts via the |source_parsed| |
| // method. |
| void GatherSourceProviders(JSC::JSGlobalObject* global_object, |
| SourceProviderSet* source_providers) { |
| DCHECK(global_object); |
| DCHECK(source_providers); |
| source_providers->clear(); |
| GathererFunctor gatherer_functor(global_object, source_providers); |
| JSC::JSGlobalData& global_data = global_object->globalData(); |
| global_data.heap.objectSpace().forEachLiveCell(gatherer_functor); |
| } |
| |
| intptr_t StringToIntptr(const std::string& input) { |
| COMPILE_ASSERT(sizeof(int64) >= sizeof(intptr_t), |
| int64_not_big_enough_to_store_intptr_t); |
| int64 as_int64 = 0; |
| bool did_convert = base::StringToInt64(input, &as_int64); |
| DCHECK(did_convert); |
| return static_cast<intptr_t>(as_int64); |
| } |
| } // namespace |
| |
| JSCDebugger::JSCDebugger(GlobalEnvironment* global_environment, |
| Delegate* delegate) |
| : global_environment_(global_environment), |
| delegate_(delegate), |
| pause_on_exceptions_(kNone), |
| pause_on_next_statement_(false), |
| pause_on_call_frame_(NULL), |
| current_call_frame_(NULL), |
| current_source_id_(0), |
| current_line_number_(0), |
| current_column_number_(0), |
| is_paused_(false) {} |
| |
| JSCDebugger::~JSCDebugger() { |
| if (GetGlobalObject()->debugger() == this) { |
| detach(GetGlobalObject()); |
| } |
| } |
| |
| void JSCDebugger::Attach() { |
| if (GetGlobalObject()->debugger() == NULL) { |
| attach(GetGlobalObject()); |
| } else { |
| DLOG(WARNING) << "Debugger is already attached."; |
| } |
| } |
| |
| void JSCDebugger::Detach() { |
| if (GetGlobalObject()->debugger() == this) { |
| detach(GetGlobalObject()); |
| } else { |
| DLOG(WARNING) << "Debugger is not attached."; |
| } |
| } |
| |
| void JSCDebugger::Pause() { |
| pause_on_next_statement_ = true; |
| pause_on_call_frame_ = NULL; |
| } |
| |
| void JSCDebugger::Resume() { |
| pause_on_next_statement_ = false; |
| pause_on_call_frame_ = NULL; |
| } |
| |
| void JSCDebugger::SetBreakpoint(const std::string& script_id, int line_number, |
| int column_number) { |
| // Convert the string script_id used by devtools into the intptr_t source_id |
| // used internally. |
| intptr_t source_id = StringToIntptr(script_id); |
| |
| breakpoints_.push_back(Breakpoint(source_id, line_number, column_number)); |
| } |
| |
| script::ScriptDebugger::PauseOnExceptionsState |
| JSCDebugger::SetPauseOnExceptions(PauseOnExceptionsState state) { |
| const PauseOnExceptionsState previous_state = pause_on_exceptions_; |
| pause_on_exceptions_ = state; |
| return previous_state; |
| } |
| |
| void JSCDebugger::StepInto() { |
| pause_on_next_statement_ = true; |
| pause_on_call_frame_ = NULL; |
| } |
| |
| void JSCDebugger::StepOut() { |
| pause_on_next_statement_ = false; |
| const JSC::CallFrame* call_frame = current_call_frame_.callFrame(); |
| pause_on_call_frame_ = call_frame ? call_frame->callerFrame() : NULL; |
| } |
| |
| void JSCDebugger::StepOver() { |
| pause_on_next_statement_ = false; |
| pause_on_call_frame_ = current_call_frame_.callFrame(); |
| DCHECK(pause_on_call_frame_); |
| } |
| |
| JSCGlobalObject* JSCDebugger::GetGlobalObject() const { |
| return base::polymorphic_downcast<JSCGlobalEnvironment*>(global_environment_) |
| ->global_object(); |
| } |
| |
| void JSCDebugger::attach(JSC::JSGlobalObject* global_object) { |
| DCHECK(global_object); |
| JSC::Debugger::attach(global_object); |
| |
| // Gather the source providers and call |sourceParsed| on each one. |
| // Any scripts parsed after this point will automatically invoke a call |
| // to |sourceParsed|. |
| SourceProviderSet source_providers; |
| GatherSourceProviders(global_object, &source_providers); |
| |
| for (SourceProviderSet::iterator iter = source_providers.begin(); |
| iter != source_providers.end(); ++iter) { |
| sourceParsed(global_object->globalExec(), *iter, -1, String()); |
| } |
| } |
| |
| void JSCDebugger::detach(JSC::JSGlobalObject* global_object) { |
| DCHECK(global_object); |
| JSC::Debugger::detach(global_object); |
| delegate_->OnScriptDebuggerDetach("canceled_by_user"); |
| } |
| |
| void JSCDebugger::sourceParsed(JSC::ExecState* exec_state, |
| JSC::SourceProvider* source_provider, |
| int error_line, |
| const WTF::String& error_message) { |
| UNREFERENCED_PARAMETER(exec_state); |
| DCHECK(source_provider); |
| |
| if (error_line < 0) { |
| // Script was parsed successfully. |
| delegate_->OnScriptParsed( |
| scoped_ptr<SourceProvider>(new JSCSourceProvider(source_provider))); |
| } else { |
| // Script failed to parse. |
| delegate_->OnScriptFailedToParse( |
| scoped_ptr<SourceProvider>(new JSCSourceProvider( |
| source_provider, error_line, error_message.utf8().data()))); |
| } |
| } |
| |
| void JSCDebugger::exception(const JSC::DebuggerCallFrame& call_frame, |
| intptr_t source_id, int line_number, |
| int column_number, bool has_handler) { |
| if (pause_on_exceptions_ == kAll || |
| (pause_on_exceptions_ == kUncaught && !has_handler)) { |
| pause_on_next_statement_ = true; |
| pause_on_call_frame_ = NULL; |
| } |
| |
| UpdateAndPauseIfNeeded(call_frame, source_id, line_number, column_number); |
| } |
| |
| void JSCDebugger::atStatement(const JSC::DebuggerCallFrame& call_frame, |
| intptr_t source_id, int line_number, |
| int column_number) { |
| UpdateAndPauseIfNeeded(call_frame, source_id, line_number, column_number); |
| } |
| |
| void JSCDebugger::callEvent(const JSC::DebuggerCallFrame& call_frame, |
| intptr_t source_id, int line_number, |
| int column_number) { |
| UpdateAndPauseIfNeeded(call_frame, source_id, line_number, column_number); |
| } |
| |
| void JSCDebugger::returnEvent(const JSC::DebuggerCallFrame& call_frame, |
| intptr_t source_id, int line_number, |
| int column_number) { |
| UpdateAndPauseIfNeeded(call_frame, source_id, line_number, column_number); |
| } |
| |
| void JSCDebugger::willExecuteProgram(const JSC::DebuggerCallFrame& call_frame, |
| intptr_t source_id, int line_number, |
| int column_number) { |
| UpdateAndPauseIfNeeded(call_frame, source_id, line_number, column_number); |
| } |
| |
| void JSCDebugger::didExecuteProgram(const JSC::DebuggerCallFrame& call_frame, |
| intptr_t source_id, int line_number, |
| int column_number) { |
| UpdateAndPauseIfNeeded(call_frame, source_id, line_number, column_number); |
| } |
| |
| void JSCDebugger::didReachBreakpoint(const JSC::DebuggerCallFrame& call_frame, |
| intptr_t source_id, int line_number, |
| int column_number) { |
| pause_on_next_statement_ = true; |
| pause_on_call_frame_ = NULL; |
| UpdateAndPauseIfNeeded(call_frame, source_id, line_number, column_number); |
| } |
| |
| void JSCDebugger::UpdateAndPauseIfNeeded( |
| const JSC::DebuggerCallFrame& call_frame, intptr_t source_id, |
| int line_number, int column_number) { |
| // Don't do anything if we're currently paused. We want to remember the call |
| // frame and source location at the point we paused, not override them with |
| // any debugging scripts that get evaluated while paused. |
| if (is_paused_) { |
| return; |
| } |
| |
| UpdateSourceLocation(source_id, line_number, column_number); |
| UpdateCallFrame(call_frame); |
| PauseIfNeeded(call_frame); |
| } |
| |
| void JSCDebugger::UpdateSourceLocation(intptr_t source_id, int line_number, |
| int column_number) { |
| current_source_id_ = source_id; |
| current_line_number_ = line_number; |
| current_column_number_ = column_number; |
| } |
| |
| void JSCDebugger::UpdateCallFrame(const JSC::DebuggerCallFrame& call_frame) { |
| current_call_frame_ = call_frame; |
| } |
| |
| void JSCDebugger::PauseIfNeeded(const JSC::DebuggerCallFrame& call_frame) { |
| // Determine whether we should pause. |
| bool will_pause = pause_on_next_statement_; |
| will_pause |= |
| pause_on_call_frame_ && pause_on_call_frame_ == call_frame.callFrame(); |
| will_pause |= IsBreakpointAtCurrentLocation(); |
| |
| if (!will_pause) { |
| return; |
| } |
| |
| // Set the |is_paused_| state for the remainder of this function. |
| ScopedPausedState paused(this); |
| |
| // Delegate handles the actual blocking of the thread to implement Pause. |
| delegate_->OnScriptDebuggerPause(scoped_ptr<CallFrame>( |
| new JSCCallFrame(call_frame, current_source_id_, current_line_number_, |
| current_column_number_))); |
| } |
| |
| bool JSCDebugger::IsBreakpointAtCurrentLocation() const { |
| for (BreakpointVector::const_iterator it = breakpoints_.begin(); |
| it != breakpoints_.end(); ++it) { |
| if (it->source_id == current_source_id_ && |
| it->line_number == current_line_number_ && |
| (it->column_number == 0 || |
| it->column_number == current_column_number_)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace javascriptcore |
| } // namespace script |
| } // namespace cobalt |