/**************************************************************************/ /* debug_adapter_protocol.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* 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" #include "editor/editor_settings.h" #include "editor/gui/editor_run_bar.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_V_MSG(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 tcp_peer = server->take_connection(); tcp_peer->set_no_delay(true); Ref 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 &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; const String type_scalar = Variant::get_type_name(p_var.get_type() == Variant::VECTOR2 ? Variant::FLOAT : Variant::INT); DAP::Variable x, y; x.name = "x"; y.name = "y"; x.type = type_scalar; y.type = type_scalar; 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; const String type_scalar = Variant::get_type_name(p_var.get_type() == Variant::RECT2 ? Variant::FLOAT : Variant::INT); DAP::Variable x, y, w, h; x.name = "x"; y.name = "y"; w.name = "w"; h.name = "h"; x.type = type_scalar; y.type = type_scalar; w.type = type_scalar; h.type = type_scalar; 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; const String type_scalar = Variant::get_type_name(p_var.get_type() == Variant::VECTOR3 ? Variant::FLOAT : Variant::INT); DAP::Variable x, y, z; x.name = "x"; y.name = "y"; z.name = "z"; x.type = type_scalar; y.type = type_scalar; z.type = type_scalar; 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; const String type_vec2 = Variant::get_type_name(Variant::VECTOR2); DAP::Variable x, y, origin; x.name = "x"; y.name = "y"; origin.name = "origin"; x.type = type_vec2; y.type = type_vec2; origin.type = type_vec2; x.value = transform.columns[0]; y.value = transform.columns[1]; origin.value = transform.columns[2]; x.variablesReference = parse_variant(transform.columns[0]); y.variablesReference = parse_variant(transform.columns[1]); origin.variablesReference = parse_variant(transform.columns[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; const String type_float = Variant::get_type_name(Variant::FLOAT); DAP::Variable x, y, z, w; x.name = "x"; y.name = "y"; z.name = "z"; w.name = "w"; x.type = type_float; y.type = type_float; z.type = type_float; w.type = type_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; const String type_vec3 = Variant::get_type_name(Variant::VECTOR3); DAP::Variable position, size; position.name = "position"; size.name = "size"; position.type = type_vec3; size.type = type_vec3; 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; const String type_vec3 = Variant::get_type_name(Variant::VECTOR3); DAP::Variable x, y, z; x.name = "x"; y.name = "y"; z.name = "z"; x.type = type_vec3; y.type = type_vec3; z.type = type_vec3; x.value = basis.rows[0]; y.value = basis.rows[1]; z.value = basis.rows[2]; x.variablesReference = parse_variant(basis.rows[0]); y.variablesReference = parse_variant(basis.rows[1]); z.variablesReference = parse_variant(basis.rows[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; const String type_float = Variant::get_type_name(Variant::FLOAT); DAP::Variable r, g, b, a; r.name = "r"; g.name = "g"; b.name = "b"; a.name = "a"; r.type = type_float; g.type = type_float; b.type = type_float; a.type = type_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++; PackedVector3Array 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, "Malformed 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 { // Launch request needs to be deferred until we receive a configurationDone request. if (command != "req_launch") { 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>::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>::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>::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>::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>::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>::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>::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>::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, RemoteDebugger::MessageType p_type) { Dictionary event = parser->ev_output(p_message, p_type); for (List>::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>::Element *E = clients.front(); E; E = E->next()) { Ref 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>::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::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 (EditorRunBar::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, int p_type) { notify_output(p_message, RemoteDebugger::MessageType(p_type)); } 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::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::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 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 scope_ids = stackframe_list.find(frame)->value; for (List::Element *E = scope_ids.front(); E; E = E->next()) { int var_id = E->get(); if (variable_list.has(var_id)) { variable_list.find(var_id)->value.clear(); } else { variable_list.insert(var_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 scope_ids = stackframe_list.find(frame)->value; ERR_FAIL_COND(scope_ids.size() != 3); ERR_FAIL_INDEX(stack_var.type, 3); int var_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(var_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> to_delete; for (List>::Element *E = clients.front(); E; E = E->next()) { Ref peer = E->get(); peer->connection->poll(); 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>::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>::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(); EditorRunBar::get_singleton()->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); }