diff options
Diffstat (limited to 'editor/debugger/debug_adapter')
-rw-r--r-- | editor/debugger/debug_adapter/SCsub | 5 | ||||
-rw-r--r-- | editor/debugger/debug_adapter/debug_adapter_parser.cpp | 610 | ||||
-rw-r--r-- | editor/debugger/debug_adapter/debug_adapter_parser.h | 96 | ||||
-rw-r--r-- | editor/debugger/debug_adapter/debug_adapter_protocol.cpp | 1010 | ||||
-rw-r--r-- | editor/debugger/debug_adapter/debug_adapter_protocol.h | 155 | ||||
-rw-r--r-- | editor/debugger/debug_adapter/debug_adapter_server.cpp | 85 | ||||
-rw-r--r-- | editor/debugger/debug_adapter/debug_adapter_server.h | 57 | ||||
-rw-r--r-- | editor/debugger/debug_adapter/debug_adapter_types.h | 278 |
8 files changed, 2296 insertions, 0 deletions
diff --git a/editor/debugger/debug_adapter/SCsub b/editor/debugger/debug_adapter/SCsub new file mode 100644 index 0000000000..359d04e5df --- /dev/null +++ b/editor/debugger/debug_adapter/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.editor_sources, "*.cpp") diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp new file mode 100644 index 0000000000..485d58f4a3 --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp @@ -0,0 +1,610 @@ +/*************************************************************************/ +/* debug_adapter_parser.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "debug_adapter_parser.h" + +#include "editor/debugger/editor_debugger_node.h" +#include "editor/debugger/script_editor_debugger.h" +#include "editor/editor_node.h" +#include "editor/editor_run_native.h" + +void DebugAdapterParser::_bind_methods() { + // Requests + ClassDB::bind_method(D_METHOD("req_initialize", "params"), &DebugAdapterParser::req_initialize); + ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::req_disconnect); + ClassDB::bind_method(D_METHOD("req_launch", "params"), &DebugAdapterParser::req_launch); + ClassDB::bind_method(D_METHOD("req_attach", "params"), &DebugAdapterParser::req_attach); + ClassDB::bind_method(D_METHOD("req_restart", "params"), &DebugAdapterParser::req_restart); + ClassDB::bind_method(D_METHOD("req_terminate", "params"), &DebugAdapterParser::req_terminate); + ClassDB::bind_method(D_METHOD("req_configurationDone", "params"), &DebugAdapterParser::prepare_success_response); + ClassDB::bind_method(D_METHOD("req_pause", "params"), &DebugAdapterParser::req_pause); + ClassDB::bind_method(D_METHOD("req_continue", "params"), &DebugAdapterParser::req_continue); + ClassDB::bind_method(D_METHOD("req_threads", "params"), &DebugAdapterParser::req_threads); + ClassDB::bind_method(D_METHOD("req_stackTrace", "params"), &DebugAdapterParser::req_stackTrace); + ClassDB::bind_method(D_METHOD("req_setBreakpoints", "params"), &DebugAdapterParser::req_setBreakpoints); + ClassDB::bind_method(D_METHOD("req_breakpointLocations", "params"), &DebugAdapterParser::req_breakpointLocations); + ClassDB::bind_method(D_METHOD("req_scopes", "params"), &DebugAdapterParser::req_scopes); + ClassDB::bind_method(D_METHOD("req_variables", "params"), &DebugAdapterParser::req_variables); + ClassDB::bind_method(D_METHOD("req_next", "params"), &DebugAdapterParser::req_next); + ClassDB::bind_method(D_METHOD("req_stepIn", "params"), &DebugAdapterParser::req_stepIn); + ClassDB::bind_method(D_METHOD("req_evaluate", "params"), &DebugAdapterParser::req_evaluate); + ClassDB::bind_method(D_METHOD("req_godot/put_msg", "params"), &DebugAdapterParser::req_godot_put_msg); +} + +Dictionary DebugAdapterParser::prepare_base_event() const { + Dictionary event; + event["type"] = "event"; + + return event; +} + +Dictionary DebugAdapterParser::prepare_success_response(const Dictionary &p_params) const { + Dictionary response; + response["type"] = "response"; + response["request_seq"] = p_params["seq"]; + response["command"] = p_params["command"]; + response["success"] = true; + + return response; +} + +Dictionary DebugAdapterParser::prepare_error_response(const Dictionary &p_params, DAP::ErrorType err_type, const Dictionary &variables) const { + Dictionary response, body; + response["type"] = "response"; + response["request_seq"] = p_params["seq"]; + response["command"] = p_params["command"]; + response["success"] = false; + response["body"] = body; + + DAP::Message message; + String error, error_desc; + switch (err_type) { + case DAP::ErrorType::WRONG_PATH: + error = "wrong_path"; + error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\""; + break; + case DAP::ErrorType::NOT_RUNNING: + error = "not_running"; + error_desc = "Can't attach to a running session since there isn't one."; + break; + case DAP::ErrorType::TIMEOUT: + error = "timeout"; + error_desc = "Timeout reached while processing a request."; + break; + case DAP::ErrorType::UNKNOWN_PLATFORM: + error = "unknown_platform"; + error_desc = "The specified platform is unknown."; + break; + case DAP::ErrorType::MISSING_DEVICE: + error = "missing_device"; + error_desc = "There's no connected device with specified id."; + break; + case DAP::ErrorType::UNKNOWN: + default: + error = "unknown"; + error_desc = "An unknown error has ocurred when processing the request."; + break; + } + + message.id = err_type; + message.format = error_desc; + message.variables = variables; + response["message"] = error; + body["error"] = message.to_json(); + + return response; +} + +Dictionary DebugAdapterParser::req_initialize(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params); + Dictionary args = p_params["arguments"]; + + Ref<DAPeer> peer = DebugAdapterProtocol::get_singleton()->get_current_peer(); + + peer->linesStartAt1 = args.get("linesStartAt1", false); + peer->columnsStartAt1 = args.get("columnsStartAt1", false); + peer->supportsVariableType = args.get("supportsVariableType", false); + peer->supportsInvalidatedEvent = args.get("supportsInvalidatedEvent", false); + + DAP::Capabilities caps; + response["body"] = caps.to_json(); + + DebugAdapterProtocol::get_singleton()->notify_initialized(); + + if (DebugAdapterProtocol::get_singleton()->_sync_breakpoints) { + // Send all current breakpoints + List<String> breakpoints; + ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); + for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) { + String breakpoint = E->get(); + + String path = breakpoint.left(breakpoint.find(":", 6)); // Skip initial part of path, aka "res://" + int line = breakpoint.substr(path.size()).to_int(); + + DebugAdapterProtocol::get_singleton()->on_debug_breakpoint_toggled(path, line, true); + } + } else { + // Remove all current breakpoints + EditorDebuggerNode::get_singleton()->get_default_debugger()->_clear_breakpoints(); + } + + return response; +} + +Dictionary DebugAdapterParser::req_disconnect(const Dictionary &p_params) const { + if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->attached) { + EditorNode::get_singleton()->run_stop(); + } + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) const { + Dictionary args = p_params["arguments"]; + if (args.has("project") && !is_valid_path(args["project"])) { + Dictionary variables; + variables["clientPath"] = args["project"]; + variables["editorPath"] = ProjectSettings::get_singleton()->get_resource_path(); + return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables); + } + + if (args.has("godot/custom_data")) { + DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsCustomData = args["godot/custom_data"]; + } + + ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger(); + if ((bool)args["noDebug"] != dbg->is_skip_breakpoints()) { + dbg->debug_skip_breakpoints(); + } + + String platform_string = args.get("platform", "host"); + if (platform_string == "host") { + EditorNode::get_singleton()->run_play(); + } else { + int device = args.get("device", -1); + int idx = -1; + if (platform_string == "android") { + for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) { + if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Android") { + idx = i; + break; + } + } + } else if (platform_string == "web") { + for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) { + if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "HTML5") { + idx = i; + break; + } + } + } + + if (idx == -1) { + return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN_PLATFORM); + } + + EditorNode *editor = EditorNode::get_singleton(); + Error err = platform_string == "android" ? editor->run_play_native(device, idx) : editor->run_play_native(-1, idx); + if (err) { + if (err == ERR_INVALID_PARAMETER && platform_string == "android") { + return prepare_error_response(p_params, DAP::ErrorType::MISSING_DEVICE); + } else { + return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN); + } + } + } + + DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = false; + DebugAdapterProtocol::get_singleton()->notify_process(); + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_attach(const Dictionary &p_params) const { + ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger(); + if (!dbg->is_session_active()) { + return prepare_error_response(p_params, DAP::ErrorType::NOT_RUNNING); + } + + DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = true; + DebugAdapterProtocol::get_singleton()->notify_process(); + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_restart(const Dictionary &p_params) const { + // Extract embedded "arguments" so it can be given to req_launch/req_attach + Dictionary params = p_params, args; + args = params["arguments"]; + args = args["arguments"]; + params["arguments"] = args; + + Dictionary response = DebugAdapterProtocol::get_singleton()->get_current_peer()->attached ? req_attach(params) : req_launch(params); + if (!response["success"]) { + response["command"] = p_params["command"]; + return response; + } + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_terminate(const Dictionary &p_params) const { + EditorNode::get_singleton()->run_stop(); + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_pause(const Dictionary &p_params) const { + EditorNode::get_singleton()->get_pause_button()->set_pressed(true); + EditorDebuggerNode::get_singleton()->_paused(); + + DebugAdapterProtocol::get_singleton()->notify_stopped_paused(); + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_continue(const Dictionary &p_params) const { + EditorNode::get_singleton()->get_pause_button()->set_pressed(false); + EditorDebuggerNode::get_singleton()->_paused(); + + DebugAdapterProtocol::get_singleton()->notify_continued(); + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_threads(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + Array arr; + DAP::Thread thread; + + thread.id = 1; // Hardcoded because Godot only supports debugging one thread at the moment + thread.name = "Main"; + arr.push_back(thread.to_json()); + body["threads"] = arr; + + return response; +} + +Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const { + if (DebugAdapterProtocol::get_singleton()->_processing_stackdump) { + return Dictionary(); + } + + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + bool lines_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->linesStartAt1; + bool columns_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->columnsStartAt1; + + Array arr; + DebugAdapterProtocol *dap = DebugAdapterProtocol::get_singleton(); + for (const KeyValue<DAP::StackFrame, List<int>> &E : dap->stackframe_list) { + DAP::StackFrame sf = E.key; + if (!lines_at_one) { + sf.line--; + } + if (!columns_at_one) { + sf.column--; + } + + arr.push_back(sf.to_json()); + } + + body["stackFrames"] = arr; + return response; +} + +Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + Dictionary args = p_params["arguments"]; + DAP::Source source; + source.from_json(args["source"]); + + bool lines_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->linesStartAt1; + + if (!is_valid_path(source.path)) { + Dictionary variables; + variables["clientPath"] = source.path; + variables["editorPath"] = ProjectSettings::get_singleton()->get_resource_path(); + return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables); + } + + Array breakpoints = args["breakpoints"], lines; + for (int i = 0; i < breakpoints.size(); i++) { + DAP::SourceBreakpoint breakpoint; + breakpoint.from_json(breakpoints[i]); + + lines.push_back(breakpoint.line + !lines_at_one); + } + + Array updated_breakpoints = DebugAdapterProtocol::get_singleton()->update_breakpoints(source.path, lines); + body["breakpoints"] = updated_breakpoints; + + return response; +} + +Dictionary DebugAdapterParser::req_breakpointLocations(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + Dictionary args = p_params["arguments"]; + + Array locations; + DAP::BreakpointLocation location; + location.line = args["line"]; + if (args.has("endLine")) { + location.endLine = args["endLine"]; + } + locations.push_back(location.to_json()); + + body["breakpoints"] = locations; + return response; +} + +Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + Dictionary args = p_params["arguments"]; + int frame_id = args["frameId"]; + Array scope_list; + + DAP::StackFrame frame; + frame.id = frame_id; + Map<DAP::StackFrame, List<int>>::Element *E = DebugAdapterProtocol::get_singleton()->stackframe_list.find(frame); + if (E) { + ERR_FAIL_COND_V(E->value().size() != 3, prepare_error_response(p_params, DAP::ErrorType::UNKNOWN)); + for (int i = 0; i < 3; i++) { + DAP::Scope scope; + scope.variablesReference = E->value()[i]; + switch (i) { + case 0: + scope.name = "Locals"; + scope.presentationHint = "locals"; + break; + case 1: + scope.name = "Members"; + scope.presentationHint = "members"; + break; + case 2: + scope.name = "Globals"; + scope.presentationHint = "globals"; + } + + scope_list.push_back(scope.to_json()); + } + } + + EditorDebuggerNode::get_singleton()->get_default_debugger()->request_stack_dump(frame_id); + DebugAdapterProtocol::get_singleton()->_current_frame = frame_id; + + body["scopes"] = scope_list; + return response; +} + +Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const { + // If _remaining_vars > 0, the debugee is still sending a stack dump to the editor. + if (DebugAdapterProtocol::get_singleton()->_remaining_vars > 0) { + return Dictionary(); + } + + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + Dictionary args = p_params["arguments"]; + int variable_id = args["variablesReference"]; + + Map<int, Array>::Element *E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id); + + if (E) { + if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) { + for (int i = 0; i < E->value().size(); i++) { + Dictionary variable = E->value()[i]; + variable.erase("type"); + } + } + body["variables"] = E ? E->value() : Array(); + return response; + } else { + return Dictionary(); + } +} + +Dictionary DebugAdapterParser::req_next(const Dictionary &p_params) const { + EditorDebuggerNode::get_singleton()->get_default_debugger()->debug_next(); + DebugAdapterProtocol::get_singleton()->_stepping = true; + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const { + EditorDebuggerNode::get_singleton()->get_default_debugger()->debug_step(); + DebugAdapterProtocol::get_singleton()->_stepping = true; + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + Dictionary args = p_params["arguments"]; + + String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression"]); + body["result"] = value; + + return response; +} + +Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const { + Dictionary args = p_params["arguments"]; + + String msg = args["message"]; + Array data = args["data"]; + + EditorDebuggerNode::get_singleton()->get_default_debugger()->_put_msg(msg, data); + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::ev_initialized() const { + Dictionary event = prepare_base_event(); + event["event"] = "initialized"; + + return event; +} + +Dictionary DebugAdapterParser::ev_process(const String &p_command) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "process"; + event["body"] = body; + + body["name"] = OS::get_singleton()->get_executable_path(); + body["startMethod"] = p_command; + + return event; +} + +Dictionary DebugAdapterParser::ev_terminated() const { + Dictionary event = prepare_base_event(); + event["event"] = "terminated"; + + return event; +} + +Dictionary DebugAdapterParser::ev_exited(const int &p_exitcode) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "exited"; + event["body"] = body; + + body["exitCode"] = p_exitcode; + + return event; +} + +Dictionary DebugAdapterParser::ev_stopped() const { + Dictionary event = prepare_base_event(), body; + event["event"] = "stopped"; + event["body"] = body; + + body["threadId"] = 1; + + return event; +} + +Dictionary DebugAdapterParser::ev_stopped_paused() const { + Dictionary event = ev_stopped(); + Dictionary body = event["body"]; + + body["reason"] = "paused"; + body["description"] = "Paused"; + + return event; +} + +Dictionary DebugAdapterParser::ev_stopped_exception(const String &p_error) const { + Dictionary event = ev_stopped(); + Dictionary body = event["body"]; + + body["reason"] = "exception"; + body["description"] = "Exception"; + body["text"] = p_error; + + return event; +} + +Dictionary DebugAdapterParser::ev_stopped_breakpoint(const int &p_id) const { + Dictionary event = ev_stopped(); + Dictionary body = event["body"]; + + body["reason"] = "breakpoint"; + body["description"] = "Breakpoint"; + + Array breakpoints; + breakpoints.push_back(p_id); + body["hitBreakpointIds"] = breakpoints; + + return event; +} + +Dictionary DebugAdapterParser::ev_stopped_step() const { + Dictionary event = ev_stopped(); + Dictionary body = event["body"]; + + body["reason"] = "step"; + body["description"] = "Breakpoint"; + + return event; +} + +Dictionary DebugAdapterParser::ev_continued() const { + Dictionary event = prepare_base_event(), body; + event["event"] = "continued"; + event["body"] = body; + + body["threadId"] = 1; + + return event; +} + +Dictionary DebugAdapterParser::ev_output(const String &p_message) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "output"; + event["body"] = body; + + body["category"] = "stdout"; + body["output"] = p_message + "\r\n"; + + return event; +} + +Dictionary DebugAdapterParser::ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "breakpoint"; + event["body"] = body; + + body["reason"] = p_enabled ? "new" : "removed"; + body["breakpoint"] = p_breakpoint.to_json(); + + return event; +} + +Dictionary DebugAdapterParser::ev_custom_data(const String &p_msg, const Array &p_data) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "godot/custom_data"; + event["body"] = body; + + body["message"] = p_msg; + body["data"] = p_data; + + return event; +} diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.h b/editor/debugger/debug_adapter/debug_adapter_parser.h new file mode 100644 index 0000000000..4c93464e39 --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_parser.h @@ -0,0 +1,96 @@ +/*************************************************************************/ +/* debug_adapter_parser.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef DEBUG_ADAPTER_PARSER_H +#define DEBUG_ADAPTER_PARSER_H + +#include "core/config/project_settings.h" +#include "debug_adapter_protocol.h" +#include "debug_adapter_types.h" + +struct DAPeer; +class DebugAdapterProtocol; + +class DebugAdapterParser : public Object { + GDCLASS(DebugAdapterParser, Object); + +private: + friend DebugAdapterProtocol; + + _FORCE_INLINE_ bool is_valid_path(const String &p_path) const { + return p_path.begins_with(ProjectSettings::get_singleton()->get_resource_path()); + } + +protected: + static void _bind_methods(); + + Dictionary prepare_base_event() const; + Dictionary prepare_success_response(const Dictionary &p_params) const; + Dictionary prepare_error_response(const Dictionary &p_params, DAP::ErrorType err_type, const Dictionary &variables = Dictionary()) const; + + Dictionary ev_stopped() const; + +public: + // Requests + Dictionary req_initialize(const Dictionary &p_params) const; + Dictionary req_launch(const Dictionary &p_params) const; + Dictionary req_disconnect(const Dictionary &p_params) const; + Dictionary req_attach(const Dictionary &p_params) const; + Dictionary req_restart(const Dictionary &p_params) const; + Dictionary req_terminate(const Dictionary &p_params) const; + Dictionary req_pause(const Dictionary &p_params) const; + Dictionary req_continue(const Dictionary &p_params) const; + Dictionary req_threads(const Dictionary &p_params) const; + Dictionary req_stackTrace(const Dictionary &p_params) const; + Dictionary req_setBreakpoints(const Dictionary &p_params) const; + Dictionary req_breakpointLocations(const Dictionary &p_params) const; + Dictionary req_scopes(const Dictionary &p_params) const; + Dictionary req_variables(const Dictionary &p_params) const; + Dictionary req_next(const Dictionary &p_params) const; + Dictionary req_stepIn(const Dictionary &p_params) const; + Dictionary req_evaluate(const Dictionary &p_params) const; + Dictionary req_godot_put_msg(const Dictionary &p_params) const; + + // Events + Dictionary ev_initialized() const; + Dictionary ev_process(const String &p_command) const; + Dictionary ev_terminated() const; + Dictionary ev_exited(const int &p_exitcode) const; + Dictionary ev_stopped_paused() const; + Dictionary ev_stopped_exception(const String &p_error) const; + Dictionary ev_stopped_breakpoint(const int &p_id) const; + Dictionary ev_stopped_step() const; + Dictionary ev_continued() const; + Dictionary ev_output(const String &p_message) const; + Dictionary ev_custom_data(const String &p_msg, const Array &p_data) const; + Dictionary ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const; +}; + +#endif diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp new file mode 100644 index 0000000000..2e0e6cb7c8 --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp @@ -0,0 +1,1010 @@ +/*************************************************************************/ +/* debug_adapter_protocol.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "debug_adapter_protocol.h" + +#include "core/config/project_settings.h" +#include "core/debugger/debugger_marshalls.h" +#include "core/io/json.h" +#include "editor/debugger/script_editor_debugger.h" +#include "editor/doc_tools.h" +#include "editor/editor_log.h" +#include "editor/editor_node.h" + +DebugAdapterProtocol *DebugAdapterProtocol::singleton = nullptr; + +Error DAPeer::handle_data() { + int read = 0; + // Read headers + if (!has_header) { + if (!connection->get_available_bytes()) { + return OK; + } + while (true) { + if (req_pos >= DAP_MAX_BUFFER_SIZE) { + req_pos = 0; + ERR_FAIL_COND_V_MSG(true, ERR_OUT_OF_MEMORY, "Response header too big"); + } + Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); + if (err != OK) { + return FAILED; + } else if (read != 1) { // Busy, wait until next poll + return ERR_BUSY; + } + char *r = (char *)req_buf; + int l = req_pos; + + // End of headers + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + r[l - 3] = '\0'; // Null terminate to read string + String header; + header.parse_utf8(r); + content_length = header.substr(16).to_int(); + has_header = true; + req_pos = 0; + break; + } + req_pos++; + } + } + if (has_header) { + while (req_pos < content_length) { + if (content_length >= DAP_MAX_BUFFER_SIZE) { + req_pos = 0; + has_header = false; + ERR_FAIL_COND_V_MSG(req_pos >= DAP_MAX_BUFFER_SIZE, ERR_OUT_OF_MEMORY, "Response content too big"); + } + Error err = connection->get_partial_data(&req_buf[req_pos], content_length - req_pos, read); + if (err != OK) { + return FAILED; + } else if (read < content_length - req_pos) { + return ERR_BUSY; + } + req_pos += read; + } + + // Parse data + String msg; + msg.parse_utf8((const char *)req_buf, req_pos); + + // Apply a timestamp if it there's none yet + if (!timestamp) { + timestamp = OS::get_singleton()->get_ticks_msec(); + } + + // Response + if (DebugAdapterProtocol::get_singleton()->process_message(msg)) { + // Reset to read again + req_pos = 0; + has_header = false; + timestamp = 0; + } + } + return OK; +} + +Error DAPeer::send_data() { + while (res_queue.size()) { + Dictionary data = res_queue.front()->get(); + if (!data.has("seq")) { + data["seq"] = ++seq; + } + String formatted_data = format_output(data); + + int data_sent = 0; + while (data_sent < formatted_data.length()) { + int curr_sent = 0; + Error err = connection->put_partial_data((const uint8_t *)formatted_data.utf8().get_data(), formatted_data.size() - data_sent - 1, curr_sent); + if (err != OK) { + return err; + } + data_sent += curr_sent; + } + res_queue.pop_front(); + } + return OK; +} + +String DAPeer::format_output(const Dictionary &p_params) const { + String response = Variant(p_params).to_json_string(); + String header = "Content-Length: "; + CharString charstr = response.utf8(); + size_t len = charstr.length(); + header += itos(len); + header += "\r\n\r\n"; + + return header + response; +} + +Error DebugAdapterProtocol::on_client_connected() { + ERR_FAIL_COND_V_MSG(clients.size() >= DAP_MAX_CLIENTS, FAILED, "Max client limits reached"); + + Ref<StreamPeerTCP> tcp_peer = server->take_connection(); + tcp_peer->set_no_delay(true); + Ref<DAPeer> peer = memnew(DAPeer); + peer->connection = tcp_peer; + clients.push_back(peer); + + EditorDebuggerNode::get_singleton()->get_default_debugger()->set_move_to_foreground(false); + EditorNode::get_log()->add_message("[DAP] Connection Taken", EditorLog::MSG_TYPE_EDITOR); + return OK; +} + +void DebugAdapterProtocol::on_client_disconnected(const Ref<DAPeer> &p_peer) { + clients.erase(p_peer); + if (!clients.size()) { + reset_ids(); + EditorDebuggerNode::get_singleton()->get_default_debugger()->set_move_to_foreground(true); + } + EditorNode::get_log()->add_message("[DAP] Disconnected", EditorLog::MSG_TYPE_EDITOR); +} + +void DebugAdapterProtocol::reset_current_info() { + _current_request = ""; + _current_peer.unref(); +} + +void DebugAdapterProtocol::reset_ids() { + breakpoint_id = 0; + breakpoint_list.clear(); + + reset_stack_info(); +} + +void DebugAdapterProtocol::reset_stack_info() { + stackframe_id = 0; + variable_id = 1; + + stackframe_list.clear(); + variable_list.clear(); +} + +int DebugAdapterProtocol::parse_variant(const Variant &p_var) { + switch (p_var.get_type()) { + case Variant::VECTOR2: + case Variant::VECTOR2I: { + int id = variable_id++; + Vector2 vec = p_var; + DAP::Variable x, y; + x.name = "x"; + y.name = "y"; + x.type = y.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR2 ? Variant::FLOAT : Variant::INT); + x.value = rtos(vec.x); + y.value = rtos(vec.y); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::RECT2: + case Variant::RECT2I: { + int id = variable_id++; + Rect2 rect = p_var; + DAP::Variable x, y, w, h; + x.name = "x"; + y.name = "y"; + w.name = "w"; + h.name = "h"; + x.type = y.type = w.type = h.type = Variant::get_type_name(p_var.get_type() == Variant::RECT2 ? Variant::FLOAT : Variant::INT); + x.value = rtos(rect.position.x); + y.value = rtos(rect.position.y); + w.value = rtos(rect.size.x); + h.value = rtos(rect.size.y); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(w.to_json()); + arr.push_back(h.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::VECTOR3: + case Variant::VECTOR3I: { + int id = variable_id++; + Vector3 vec = p_var; + DAP::Variable x, y, z; + x.name = "x"; + y.name = "y"; + z.name = "z"; + x.type = y.type = z.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR3 ? Variant::FLOAT : Variant::INT); + x.value = rtos(vec.x); + y.value = rtos(vec.y); + z.value = rtos(vec.z); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(z.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::TRANSFORM2D: { + int id = variable_id++; + Transform2D transform = p_var; + DAP::Variable x, y, origin; + x.name = "x"; + y.name = "y"; + origin.name = "origin"; + x.type = y.type = origin.type = Variant::get_type_name(Variant::VECTOR2); + x.value = transform.elements[0]; + y.value = transform.elements[1]; + origin.value = transform.elements[2]; + x.variablesReference = parse_variant(transform.elements[0]); + y.variablesReference = parse_variant(transform.elements[1]); + origin.variablesReference = parse_variant(transform.elements[2]); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(origin.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::PLANE: { + int id = variable_id++; + Plane plane = p_var; + DAP::Variable d, normal; + d.name = "d"; + normal.name = "normal"; + d.type = Variant::get_type_name(Variant::FLOAT); + normal.type = Variant::get_type_name(Variant::VECTOR3); + d.value = rtos(plane.d); + normal.value = plane.normal; + normal.variablesReference = parse_variant(plane.normal); + + Array arr; + arr.push_back(d.to_json()); + arr.push_back(normal.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::QUATERNION: { + int id = variable_id++; + Quaternion quat = p_var; + DAP::Variable x, y, z, w; + x.name = "x"; + y.name = "y"; + z.name = "z"; + w.name = "w"; + x.type = y.type = z.type = w.type = Variant::get_type_name(Variant::FLOAT); + x.value = rtos(quat.x); + y.value = rtos(quat.y); + z.value = rtos(quat.z); + w.value = rtos(quat.w); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(z.to_json()); + arr.push_back(w.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::AABB: { + int id = variable_id++; + AABB aabb = p_var; + DAP::Variable position, size; + position.name = "position"; + size.name = "size"; + position.type = size.type = Variant::get_type_name(Variant::VECTOR3); + position.value = aabb.position; + size.value = aabb.size; + position.variablesReference = parse_variant(aabb.position); + size.variablesReference = parse_variant(aabb.size); + + Array arr; + arr.push_back(position.to_json()); + arr.push_back(size.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::BASIS: { + int id = variable_id++; + Basis basis = p_var; + DAP::Variable x, y, z; + x.name = "x"; + y.name = "y"; + z.name = "z"; + x.type = y.type = z.type = Variant::get_type_name(Variant::VECTOR2); + x.value = basis.elements[0]; + y.value = basis.elements[1]; + z.value = basis.elements[2]; + x.variablesReference = parse_variant(basis.elements[0]); + y.variablesReference = parse_variant(basis.elements[1]); + z.variablesReference = parse_variant(basis.elements[2]); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(z.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::TRANSFORM3D: { + int id = variable_id++; + Transform3D transform = p_var; + DAP::Variable basis, origin; + basis.name = "basis"; + origin.name = "origin"; + basis.type = Variant::get_type_name(Variant::BASIS); + origin.type = Variant::get_type_name(Variant::VECTOR3); + basis.value = transform.basis; + origin.value = transform.origin; + basis.variablesReference = parse_variant(transform.basis); + origin.variablesReference = parse_variant(transform.origin); + + Array arr; + arr.push_back(basis.to_json()); + arr.push_back(origin.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::COLOR: { + int id = variable_id++; + Color color = p_var; + DAP::Variable r, g, b, a; + r.name = "r"; + g.name = "g"; + b.name = "b"; + a.name = "a"; + r.type = g.type = b.type = a.type = Variant::get_type_name(Variant::FLOAT); + r.value = rtos(color.r); + g.value = rtos(color.g); + b.value = rtos(color.b); + a.value = rtos(color.a); + + Array arr; + arr.push_back(r.to_json()); + arr.push_back(g.to_json()); + arr.push_back(b.to_json()); + arr.push_back(a.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::ARRAY: { + int id = variable_id++; + Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(array[i].get_type()); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::DICTIONARY: { + int id = variable_id++; + Dictionary dictionary = p_var; + Array arr; + + for (int i = 0; i < dictionary.size(); i++) { + DAP::Variable var; + var.name = dictionary.get_key_at_index(i); + Variant value = dictionary.get_value_at_index(i); + var.type = Variant::get_type_name(value.get_type()); + var.value = value; + var.variablesReference = parse_variant(value); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_BYTE_ARRAY: { + int id = variable_id++; + PackedByteArray array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "byte"; + var.value = itos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_INT32_ARRAY: { + int id = variable_id++; + PackedInt32Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "int"; + var.value = itos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_INT64_ARRAY: { + int id = variable_id++; + PackedInt64Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "long"; + var.value = itos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_FLOAT32_ARRAY: { + int id = variable_id++; + PackedFloat32Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "float"; + var.value = rtos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_FLOAT64_ARRAY: { + int id = variable_id++; + PackedFloat64Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "double"; + var.value = rtos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_STRING_ARRAY: { + int id = variable_id++; + PackedStringArray array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::STRING); + var.value = array[i]; + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_VECTOR2_ARRAY: { + int id = variable_id++; + PackedVector2Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::VECTOR2); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_VECTOR3_ARRAY: { + int id = variable_id++; + PackedVector2Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::VECTOR3); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_COLOR_ARRAY: { + int id = variable_id++; + PackedColorArray array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::COLOR); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + default: + // Simple atomic stuff, or too complex to be manipulated + return 0; + } +} + +bool DebugAdapterProtocol::process_message(const String &p_text) { + JSON json; + ERR_FAIL_COND_V_MSG(json.parse(p_text) != OK, true, "Mal-formed message!"); + Dictionary params = json.get_data(); + bool completed = true; + + if (OS::get_singleton()->get_ticks_msec() - _current_peer->timestamp > _request_timeout) { + Dictionary response = parser->prepare_error_response(params, DAP::ErrorType::TIMEOUT); + _current_peer->res_queue.push_front(response); + return true; + } + + // Append "req_" to any command received; prevents name clash with existing functions, and possibly exploiting + String command = "req_" + (String)params["command"]; + if (parser->has_method(command)) { + _current_request = params["command"]; + + Array args; + args.push_back(params); + Dictionary response = parser->callv(command, args); + if (!response.is_empty()) { + _current_peer->res_queue.push_front(response); + } else { + completed = false; + } + } + + reset_current_info(); + return completed; +} + +void DebugAdapterProtocol::notify_initialized() { + Dictionary event = parser->ev_initialized(); + _current_peer->res_queue.push_back(event); +} + +void DebugAdapterProtocol::notify_process() { + String launch_mode = _current_peer->attached ? "attach" : "launch"; + + Dictionary event = parser->ev_process(launch_mode); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_terminated() { + Dictionary event = parser->ev_terminated(); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) { + continue; + } + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_exited(const int &p_exitcode) { + Dictionary event = parser->ev_exited(p_exitcode); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) { + continue; + } + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_stopped_paused() { + Dictionary event = parser->ev_stopped_paused(); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_stopped_exception(const String &p_error) { + Dictionary event = parser->ev_stopped_exception(p_error); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_stopped_breakpoint(const int &p_id) { + Dictionary event = parser->ev_stopped_breakpoint(p_id); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_stopped_step() { + Dictionary event = parser->ev_stopped_step(); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_continued() { + Dictionary event = parser->ev_continued(); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + if (_current_request == "continue" && E->get() == _current_peer) { + continue; + } + E->get()->res_queue.push_back(event); + } + + reset_stack_info(); +} + +void DebugAdapterProtocol::notify_output(const String &p_message) { + Dictionary event = parser->ev_output(p_message); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_custom_data(const String &p_msg, const Array &p_data) { + Dictionary event = parser->ev_custom_data(p_msg, p_data); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + Ref<DAPeer> peer = E->get(); + if (peer->supportsCustomData) { + peer->res_queue.push_back(event); + } + } +} + +void DebugAdapterProtocol::notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) { + Dictionary event = parser->ev_breakpoint(p_breakpoint, p_enabled); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + if (_current_request == "setBreakpoints" && E->get() == _current_peer) { + continue; + } + E->get()->res_queue.push_back(event); + } +} + +Array DebugAdapterProtocol::update_breakpoints(const String &p_path, const Array &p_lines) { + Array updated_breakpoints; + + // Add breakpoints + for (int i = 0; i < p_lines.size(); i++) { + EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, p_lines[i], true); + DAP::Breakpoint breakpoint; + breakpoint.line = p_lines[i]; + breakpoint.source.path = p_path; + + ERR_FAIL_COND_V(!breakpoint_list.find(breakpoint), Array()); + updated_breakpoints.push_back(breakpoint_list.find(breakpoint)->get().to_json()); + } + + // Remove breakpoints + for (List<DAP::Breakpoint>::Element *E = breakpoint_list.front(); E; E = E->next()) { + DAP::Breakpoint b = E->get(); + if (b.source.path == p_path && !p_lines.has(b.line)) { + EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, b.line, false); + } + } + + return updated_breakpoints; +} + +void DebugAdapterProtocol::on_debug_paused() { + if (EditorNode::get_singleton()->get_pause_button()->is_pressed()) { + notify_stopped_paused(); + } else { + notify_continued(); + } +} + +void DebugAdapterProtocol::on_debug_stopped() { + notify_exited(); + notify_terminated(); +} + +void DebugAdapterProtocol::on_debug_output(const String &p_message) { + notify_output(p_message); +} + +void DebugAdapterProtocol::on_debug_breaked(const bool &p_reallydid, const bool &p_can_debug, const String &p_reason, const bool &p_has_stackdump) { + if (!p_reallydid) { + notify_continued(); + return; + } + + if (p_reason == "Breakpoint") { + if (_stepping) { + notify_stopped_step(); + _stepping = false; + } else { + _processing_breakpoint = true; // Wait for stack_dump to find where the breakpoint happened + } + } else { + notify_stopped_exception(p_reason); + } + + _processing_stackdump = p_has_stackdump; +} + +void DebugAdapterProtocol::on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled) { + DAP::Breakpoint breakpoint; + breakpoint.verified = true; + breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(p_path); + breakpoint.source.compute_checksums(); + breakpoint.line = p_line; + + if (p_enabled) { + // Add the breakpoint + breakpoint.id = breakpoint_id++; + breakpoint_list.push_back(breakpoint); + } else { + // Remove the breakpoint + List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint); + if (E) { + breakpoint.id = E->get().id; + breakpoint_list.erase(E); + } + } + + notify_breakpoint(breakpoint, p_enabled); +} + +void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) { + if (_processing_breakpoint && !p_stack_dump.is_empty()) { + // Find existing breakpoint + Dictionary d = p_stack_dump[0]; + DAP::Breakpoint breakpoint; + breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(d["file"]); + breakpoint.line = d["line"]; + + List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint); + if (E) { + notify_stopped_breakpoint(E->get().id); + } + + _processing_breakpoint = false; + } + + stackframe_id = 0; + stackframe_list.clear(); + + // Fill in stacktrace information + for (int i = 0; i < p_stack_dump.size(); i++) { + Dictionary stack_info = p_stack_dump[i]; + DAP::StackFrame stackframe; + stackframe.id = stackframe_id++; + stackframe.name = stack_info["function"]; + stackframe.line = stack_info["line"]; + stackframe.column = 0; + stackframe.source.path = ProjectSettings::get_singleton()->globalize_path(stack_info["file"]); + stackframe.source.compute_checksums(); + + // Information for "Locals", "Members" and "Globals" variables respectively + List<int> scope_ids; + for (int j = 0; j < 3; j++) { + scope_ids.push_back(variable_id++); + } + + stackframe_list.insert(stackframe, scope_ids); + } + + _current_frame = 0; + _processing_stackdump = false; +} + +void DebugAdapterProtocol::on_debug_stack_frame_vars(const int &p_size) { + _remaining_vars = p_size; + DAP::StackFrame frame; + frame.id = _current_frame; + ERR_FAIL_COND(!stackframe_list.has(frame)); + List<int> scope_ids = stackframe_list.find(frame)->value(); + for (List<int>::Element *E = scope_ids.front(); E; E = E->next()) { + int variable_id = E->get(); + if (variable_list.has(variable_id)) { + variable_list.find(variable_id)->value().clear(); + } else { + variable_list.insert(variable_id, Array()); + } + } +} + +void DebugAdapterProtocol::on_debug_stack_frame_var(const Array &p_data) { + DebuggerMarshalls::ScriptStackVariable stack_var; + stack_var.deserialize(p_data); + + ERR_FAIL_COND(stackframe_list.is_empty()); + DAP::StackFrame frame; + frame.id = _current_frame; + + List<int> scope_ids = stackframe_list.find(frame)->value(); + ERR_FAIL_COND(scope_ids.size() != 3); + ERR_FAIL_INDEX(stack_var.type, 3); + int variable_id = scope_ids[stack_var.type]; + + DAP::Variable variable; + + variable.name = stack_var.name; + variable.value = stack_var.value; + variable.type = Variant::get_type_name(stack_var.value.get_type()); + variable.variablesReference = parse_variant(stack_var.value); + + variable_list.find(variable_id)->value().push_back(variable.to_json()); + _remaining_vars--; +} + +void DebugAdapterProtocol::on_debug_data(const String &p_msg, const Array &p_data) { + // Ignore data that is already handled by DAP + if (p_msg == "debug_enter" || p_msg == "debug_exit" || p_msg == "stack_dump" || p_msg == "stack_frame_vars" || p_msg == "stack_frame_var" || p_msg == "output" || p_msg == "request_quit") { + return; + } + + notify_custom_data(p_msg, p_data); +} + +void DebugAdapterProtocol::poll() { + if (server->is_connection_available()) { + on_client_connected(); + } + List<Ref<DAPeer>> to_delete; + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + Ref<DAPeer> peer = E->get(); + StreamPeerTCP::Status status = peer->connection->get_status(); + if (status == StreamPeerTCP::STATUS_NONE || status == StreamPeerTCP::STATUS_ERROR) { + to_delete.push_back(peer); + } else { + _current_peer = peer; + Error err = peer->handle_data(); + if (err != OK && err != ERR_BUSY) { + to_delete.push_back(peer); + } + err = peer->send_data(); + if (err != OK && err != ERR_BUSY) { + to_delete.push_back(peer); + } + } + } + + for (List<Ref<DAPeer>>::Element *E = to_delete.front(); E; E = E->next()) { + on_client_disconnected(E->get()); + } + to_delete.clear(); +} + +Error DebugAdapterProtocol::start(int p_port, const IPAddress &p_bind_ip) { + _request_timeout = (uint64_t)_EDITOR_GET("network/debug_adapter/request_timeout"); + _sync_breakpoints = (bool)_EDITOR_GET("network/debug_adapter/sync_breakpoints"); + _initialized = true; + return server->listen(p_port, p_bind_ip); +} + +void DebugAdapterProtocol::stop() { + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->connection->disconnect_from_host(); + } + + clients.clear(); + server->stop(); + _initialized = false; +} + +DebugAdapterProtocol::DebugAdapterProtocol() { + server.instantiate(); + singleton = this; + parser = memnew(DebugAdapterParser); + + reset_ids(); + + EditorNode *node = EditorNode::get_singleton(); + node->get_pause_button()->connect("pressed", callable_mp(this, &DebugAdapterProtocol::on_debug_paused)); + + EditorDebuggerNode *debugger_node = EditorDebuggerNode::get_singleton(); + debugger_node->connect("breakpoint_toggled", callable_mp(this, &DebugAdapterProtocol::on_debug_breakpoint_toggled)); + + debugger_node->get_default_debugger()->connect("stopped", callable_mp(this, &DebugAdapterProtocol::on_debug_stopped)); + debugger_node->get_default_debugger()->connect("output", callable_mp(this, &DebugAdapterProtocol::on_debug_output)); + debugger_node->get_default_debugger()->connect("breaked", callable_mp(this, &DebugAdapterProtocol::on_debug_breaked)); + debugger_node->get_default_debugger()->connect("stack_dump", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_dump)); + debugger_node->get_default_debugger()->connect("stack_frame_vars", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_vars)); + debugger_node->get_default_debugger()->connect("stack_frame_var", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_var)); + debugger_node->get_default_debugger()->connect("debug_data", callable_mp(this, &DebugAdapterProtocol::on_debug_data)); +} + +DebugAdapterProtocol::~DebugAdapterProtocol() { + memdelete(parser); +} diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.h b/editor/debugger/debug_adapter/debug_adapter_protocol.h new file mode 100644 index 0000000000..d4291992bf --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_protocol.h @@ -0,0 +1,155 @@ +/*************************************************************************/ +/* debug_adapter_protocol.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef DEBUG_ADAPTER_PROTOCOL_H +#define DEBUG_ADAPTER_PROTOCOL_H + +#include "core/io/stream_peer.h" +#include "core/io/stream_peer_tcp.h" +#include "core/io/tcp_server.h" + +#include "debug_adapter_parser.h" +#include "debug_adapter_types.h" + +#define DAP_MAX_BUFFER_SIZE 4194304 // 4MB +#define DAP_MAX_CLIENTS 8 + +class DebugAdapterParser; + +struct DAPeer : RefCounted { + Ref<StreamPeerTCP> connection; + + uint8_t req_buf[DAP_MAX_BUFFER_SIZE]; + int req_pos = 0; + bool has_header = false; + int content_length = 0; + List<Dictionary> res_queue; + int seq = 0; + uint64_t timestamp = 0; + + // Client specific info + bool linesStartAt1 = false; + bool columnsStartAt1 = false; + bool supportsVariableType = false; + bool supportsInvalidatedEvent = false; + bool supportsCustomData = false; + + // Internal client info + bool attached = false; + + Error handle_data(); + Error send_data(); + String format_output(const Dictionary &p_params) const; +}; + +class DebugAdapterProtocol : public Object { + GDCLASS(DebugAdapterProtocol, Object) + + friend class DebugAdapterParser; + +private: + static DebugAdapterProtocol *singleton; + DebugAdapterParser *parser; + + List<Ref<DAPeer>> clients; + Ref<TCPServer> server; + + Error on_client_connected(); + void on_client_disconnected(const Ref<DAPeer> &p_peer); + void on_debug_paused(); + void on_debug_stopped(); + void on_debug_output(const String &p_message); + void on_debug_breaked(const bool &p_reallydid, const bool &p_can_debug, const String &p_reason, const bool &p_has_stackdump); + void on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled); + void on_debug_stack_dump(const Array &p_stack_dump); + void on_debug_stack_frame_vars(const int &p_size); + void on_debug_stack_frame_var(const Array &p_data); + void on_debug_data(const String &p_msg, const Array &p_data); + + void reset_current_info(); + void reset_ids(); + void reset_stack_info(); + + int parse_variant(const Variant &p_var); + + bool _initialized = false; + bool _processing_breakpoint = false; + bool _stepping = false; + bool _processing_stackdump = false; + int _remaining_vars = 0; + int _current_frame = 0; + uint64_t _request_timeout = 1000; + bool _sync_breakpoints = false; + + String _current_request; + Ref<DAPeer> _current_peer; + + int breakpoint_id; + int stackframe_id; + int variable_id; + List<DAP::Breakpoint> breakpoint_list; + Map<DAP::StackFrame, List<int>> stackframe_list; + Map<int, Array> variable_list; + +public: + friend class DebugAdapterServer; + + _FORCE_INLINE_ static DebugAdapterProtocol *get_singleton() { return singleton; } + _FORCE_INLINE_ bool is_active() const { return _initialized && clients.size() > 0; } + + bool process_message(const String &p_text); + + String get_current_request() const { return _current_request; } + Ref<DAPeer> get_current_peer() const { return _current_peer; } + + void notify_initialized(); + void notify_process(); + void notify_terminated(); + void notify_exited(const int &p_exitcode = 0); + void notify_stopped_paused(); + void notify_stopped_exception(const String &p_error); + void notify_stopped_breakpoint(const int &p_id); + void notify_stopped_step(); + void notify_continued(); + void notify_output(const String &p_message); + void notify_custom_data(const String &p_msg, const Array &p_data); + void notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled); + + Array update_breakpoints(const String &p_path, const Array &p_lines); + + void poll(); + Error start(int p_port, const IPAddress &p_bind_ip); + void stop(); + + DebugAdapterProtocol(); + ~DebugAdapterProtocol(); +}; + +#endif diff --git a/editor/debugger/debug_adapter/debug_adapter_server.cpp b/editor/debugger/debug_adapter/debug_adapter_server.cpp new file mode 100644 index 0000000000..4775e2c8b0 --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_server.cpp @@ -0,0 +1,85 @@ +/*************************************************************************/ +/* debug_adapter_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "debug_adapter_server.h" + +#include "core/os/os.h" +#include "editor/editor_log.h" +#include "editor/editor_node.h" + +DebugAdapterServer::DebugAdapterServer() { + _EDITOR_DEF("network/debug_adapter/remote_port", remote_port); + _EDITOR_DEF("network/debug_adapter/request_timeout", protocol._request_timeout); + _EDITOR_DEF("network/debug_adapter/sync_breakpoints", protocol._sync_breakpoints); +} + +void DebugAdapterServer::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + start(); + break; + case NOTIFICATION_EXIT_TREE: + stop(); + break; + case NOTIFICATION_INTERNAL_PROCESS: { + // The main loop can be run again during request processing, which modifies internal state of the protocol. + // Thus, "polling" is needed to prevent it from parsing other requests while the current one isn't finished. + if (started && !polling) { + polling = true; + protocol.poll(); + polling = false; + } + } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + protocol._request_timeout = EditorSettings::get_singleton()->get("network/debug_adapter/request_timeout"); + protocol._sync_breakpoints = EditorSettings::get_singleton()->get("network/debug_adapter/sync_breakpoints"); + int remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port"); + if (remote_port != this->remote_port) { + this->stop(); + this->start(); + } + } break; + } +} + +void DebugAdapterServer::start() { + remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port"); + if (protocol.start(remote_port, IPAddress("127.0.0.1")) == OK) { + EditorNode::get_log()->add_message("--- Debug adapter server started ---", EditorLog::MSG_TYPE_EDITOR); + set_process_internal(true); + started = true; + } +} + +void DebugAdapterServer::stop() { + protocol.stop(); + started = false; + EditorNode::get_log()->add_message("--- Debug adapter server stopped ---", EditorLog::MSG_TYPE_EDITOR); +} diff --git a/editor/debugger/debug_adapter/debug_adapter_server.h b/editor/debugger/debug_adapter/debug_adapter_server.h new file mode 100644 index 0000000000..c449403cc2 --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_server.h @@ -0,0 +1,57 @@ +/*************************************************************************/ +/* debug_adapter_server.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef DEBUG_ADAPTER_SERVER_H +#define DEBUG_ADAPTER_SERVER_H + +#include "debug_adapter_protocol.h" +#include "editor/editor_plugin.h" + +class DebugAdapterServer : public EditorPlugin { + GDCLASS(DebugAdapterServer, EditorPlugin); + + DebugAdapterProtocol protocol; + + int remote_port = 6006; + bool thread_running = false; + bool started = false; + bool polling = false; + static void thread_func(void *p_userdata); + +private: + void _notification(int p_what); + +public: + DebugAdapterServer(); + void start(); + void stop(); +}; + +#endif diff --git a/editor/debugger/debug_adapter/debug_adapter_types.h b/editor/debugger/debug_adapter/debug_adapter_types.h new file mode 100644 index 0000000000..5156c91d14 --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_types.h @@ -0,0 +1,278 @@ +/*************************************************************************/ +/* debug_adapter_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef DEBUG_ADAPTER_TYPES_H +#define DEBUG_ADAPTER_TYPES_H + +#include "core/io/json.h" +#include "core/variant/dictionary.h" + +namespace DAP { + +enum ErrorType { + UNKNOWN, + WRONG_PATH, + NOT_RUNNING, + TIMEOUT, + UNKNOWN_PLATFORM, + MISSING_DEVICE +}; + +struct Checksum { + String algorithm; + String checksum; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["algorithm"] = algorithm; + dict["checksum"] = checksum; + + return dict; + } +}; + +struct Source { +private: + Array _checksums; + +public: + String name; + String path; + + void compute_checksums() { + ERR_FAIL_COND(path.is_empty()); + + // MD5 + Checksum md5; + md5.algorithm = "MD5"; + md5.checksum = FileAccess::get_md5(path); + + // SHA-256 + Checksum sha256; + sha256.algorithm = "SHA256"; + sha256.checksum = FileAccess::get_sha256(path); + + _checksums.push_back(md5.to_json()); + _checksums.push_back(sha256.to_json()); + } + + _FORCE_INLINE_ void from_json(const Dictionary &p_params) { + name = p_params["name"]; + path = p_params["path"]; + _checksums = p_params["checksums"]; + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["name"] = name; + dict["path"] = path; + dict["checksums"] = _checksums; + + return dict; + } +}; + +struct Breakpoint { + int id; + bool verified; + Source source; + int line; + + bool operator==(const Breakpoint &p_other) const { + return source.path == p_other.source.path && line == p_other.line; + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["id"] = id; + dict["verified"] = verified; + dict["source"] = source.to_json(); + dict["line"] = line; + + return dict; + } +}; + +struct BreakpointLocation { + int line; + int endLine = -1; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["line"] = line; + if (endLine >= 0) { + dict["endLine"] = endLine; + } + + return dict; + } +}; + +struct Capabilities { + bool supportsConfigurationDoneRequest = true; + bool supportsEvaluateForHovers = true; + bool supportsSetVariable = true; + String supportedChecksumAlgorithms[2] = { "MD5", "SHA256" }; + bool supportsRestartRequest = true; + bool supportsValueFormattingOptions = true; + bool supportTerminateDebuggee = true; + bool supportSuspendDebuggee = true; + bool supportsTerminateRequest = true; + bool supportsBreakpointLocationsRequest = true; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["supportsConfigurationDoneRequest"] = supportsConfigurationDoneRequest; + dict["supportsEvaluateForHovers"] = supportsEvaluateForHovers; + dict["supportsSetVariable"] = supportsSetVariable; + dict["supportsRestartRequest"] = supportsRestartRequest; + dict["supportsValueFormattingOptions"] = supportsValueFormattingOptions; + dict["supportTerminateDebuggee"] = supportTerminateDebuggee; + dict["supportSuspendDebuggee"] = supportSuspendDebuggee; + dict["supportsTerminateRequest"] = supportsTerminateRequest; + dict["supportsBreakpointLocationsRequest"] = supportsBreakpointLocationsRequest; + + Array arr; + arr.push_back(supportedChecksumAlgorithms[0]); + arr.push_back(supportedChecksumAlgorithms[1]); + dict["supportedChecksumAlgorithms"] = arr; + + return dict; + } +}; + +struct Message { + int id; + String format; + bool sendTelemetry = false; // Just in case :) + bool showUser; + Dictionary variables; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["id"] = id; + dict["format"] = format; + dict["sendTelemetry"] = sendTelemetry; + dict["showUser"] = showUser; + dict["variables"] = variables; + + return dict; + } +}; + +struct Scope { + String name; + String presentationHint; + int variablesReference; + bool expensive; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["name"] = name; + dict["presentationHint"] = presentationHint; + dict["variablesReference"] = variablesReference; + dict["expensive"] = expensive; + + return dict; + } +}; + +struct SourceBreakpoint { + int line; + + _FORCE_INLINE_ void from_json(const Dictionary &p_params) { + line = p_params["line"]; + } +}; + +struct StackFrame { + int id; + String name; + Source source; + int line; + int column; + + bool operator<(const StackFrame &p_other) const { + return id < p_other.id; + } + + _FORCE_INLINE_ void from_json(const Dictionary &p_params) { + id = p_params["id"]; + name = p_params["name"]; + source.from_json(p_params["source"]); + line = p_params["line"]; + column = p_params["column"]; + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["id"] = id; + dict["name"] = name; + dict["source"] = source.to_json(); + dict["line"] = line; + dict["column"] = column; + + return dict; + } +}; + +struct Thread { + int id; + String name; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["id"] = id; + dict["name"] = name; + + return dict; + } +}; + +struct Variable { + String name; + String value; + String type; + int variablesReference = 0; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["name"] = name; + dict["value"] = value; + dict["type"] = type; + dict["variablesReference"] = variablesReference; + + return dict; + } +}; + +} // namespace DAP + +#endif |