Skip to content

Commit 0272e35

Browse files
committed
Fix DAP bugs: crash when removing breakpoints, duplicated breakpoint data, source checksums not updating
1 parent e585e6a commit 0272e35

File tree

4 files changed

+99
-54
lines changed

4 files changed

+99
-54
lines changed

editor/debugger/debug_adapter/debug_adapter_parser.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,7 @@ Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const
322322

323323
Array arr;
324324
DebugAdapterProtocol *dap = DebugAdapterProtocol::get_singleton();
325-
for (const KeyValue<DAP::StackFrame, List<int>> &E : dap->stackframe_list) {
326-
DAP::StackFrame sf = E.key;
325+
for (DAP::StackFrame sf : dap->stackframe_list) {
327326
if (!lines_at_one) {
328327
sf.line--;
329328
}
@@ -369,6 +368,8 @@ Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) co
369368
lines.push_back(breakpoint.line + !lines_at_one);
370369
}
371370

371+
// Always update the source checksum for the requested path, as it might have been modified externally.
372+
DebugAdapterProtocol::get_singleton()->update_source(source.path);
372373
Array updated_breakpoints = DebugAdapterProtocol::get_singleton()->update_breakpoints(source.path, lines);
373374
body["breakpoints"] = updated_breakpoints;
374375

@@ -399,15 +400,13 @@ Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) const {
399400
int frame_id = args["frameId"];
400401
Array scope_list;
401402

402-
DAP::StackFrame frame;
403-
frame.id = frame_id;
404-
HashMap<DAP::StackFrame, List<int>, DAP::StackFrame>::Iterator E = DebugAdapterProtocol::get_singleton()->stackframe_list.find(frame);
403+
HashMap<DebugAdapterProtocol::DAPStackFrameID, Vector<int>>::Iterator E = DebugAdapterProtocol::get_singleton()->scope_list.find(frame_id);
405404
if (E) {
406-
ERR_FAIL_COND_V(E->value.size() != 3, prepare_error_response(p_params, DAP::ErrorType::UNKNOWN));
407-
List<int>::ConstIterator itr = E->value.begin();
408-
for (int i = 0; i < 3; ++itr, ++i) {
405+
const Vector<int> &scope_ids = E->value;
406+
ERR_FAIL_COND_V(scope_ids.size() != 3, prepare_error_response(p_params, DAP::ErrorType::UNKNOWN));
407+
for (int i = 0; i < 3; ++i) {
409408
DAP::Scope scope;
410-
scope.variablesReference = *itr;
409+
scope.variablesReference = scope_ids[i];
411410
switch (i) {
412411
case 0:
413412
scope.name = "Locals";

editor/debugger/debug_adapter/debug_adapter_protocol.cpp

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ void DebugAdapterProtocol::reset_current_info() {
176176
void DebugAdapterProtocol::reset_ids() {
177177
breakpoint_id = 0;
178178
breakpoint_list.clear();
179+
breakpoint_source_list.clear();
179180

180181
reset_stack_info();
181182
}
@@ -185,6 +186,7 @@ void DebugAdapterProtocol::reset_stack_info() {
185186
variable_id = 1;
186187

187188
stackframe_list.clear();
189+
scope_list.clear();
188190
variable_list.clear();
189191
object_list.clear();
190192
object_pending_set.clear();
@@ -824,6 +826,30 @@ bool DebugAdapterProtocol::request_remote_evaluate(const String &p_eval, int p_s
824826
return true;
825827
}
826828

829+
const DAP::Source &DebugAdapterProtocol::fetch_source(const String &p_path) {
830+
const String &global_path = ProjectSettings::get_singleton()->globalize_path(p_path);
831+
832+
HashMap<String, DAP::Source>::Iterator E = breakpoint_source_list.find(global_path);
833+
if (E != breakpoint_source_list.end()) {
834+
return E->value;
835+
}
836+
DAP::Source &added_source = breakpoint_source_list.insert(global_path, DAP::Source())->value;
837+
added_source.name = global_path.get_file();
838+
added_source.path = global_path;
839+
added_source.compute_checksums();
840+
841+
return added_source;
842+
}
843+
844+
void DebugAdapterProtocol::update_source(const String &p_path) {
845+
const String &global_path = ProjectSettings::get_singleton()->globalize_path(p_path);
846+
847+
HashMap<String, DAP::Source>::Iterator E = breakpoint_source_list.find(global_path);
848+
if (E != breakpoint_source_list.end()) {
849+
E->value.compute_checksums();
850+
}
851+
}
852+
827853
bool DebugAdapterProtocol::process_message(const String &p_text) {
828854
JSON json;
829855
ERR_FAIL_COND_V_MSG(json.parse(p_text) != OK, true, "Malformed message!");
@@ -962,22 +988,40 @@ Array DebugAdapterProtocol::update_breakpoints(const String &p_path, const Array
962988

963989
// Add breakpoints
964990
for (int i = 0; i < p_lines.size(); i++) {
965-
EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, p_lines[i], true);
966-
DAP::Breakpoint breakpoint;
991+
DAP::Breakpoint breakpoint(fetch_source(p_path));
967992
breakpoint.line = p_lines[i];
968-
breakpoint.source.path = p_path;
969993

970-
ERR_FAIL_COND_V(!breakpoint_list.find(breakpoint), Array());
971-
updated_breakpoints.push_back(breakpoint_list.find(breakpoint)->get().to_json());
994+
// Avoid duplicated entries.
995+
List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint);
996+
if (E) {
997+
updated_breakpoints.push_back(E->get().to_json());
998+
continue;
999+
}
1000+
1001+
EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, p_lines[i], true);
1002+
1003+
// Breakpoints are inserted at the end of the breakpoint list.
1004+
List<DAP::Breakpoint>::Element *added_breakpoint = breakpoint_list.back();
1005+
ERR_FAIL_NULL_V(added_breakpoint, Array());
1006+
ERR_FAIL_COND_V(!(added_breakpoint->get() == breakpoint), Array());
1007+
updated_breakpoints.push_back(added_breakpoint->get().to_json());
9721008
}
9731009

9741010
// Remove breakpoints
1011+
// Must be deferred because we are iterating the breakpoint list.
1012+
Vector<int> to_remove;
1013+
9751014
for (const DAP::Breakpoint &b : breakpoint_list) {
976-
if (b.source.path == p_path && !p_lines.has(b.line)) {
977-
EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, b.line, false);
1015+
if (b.source->path == p_path && !p_lines.has(b.line)) {
1016+
to_remove.push_back(b.line);
9781017
}
9791018
}
9801019

1020+
// Safe to remove queued data now.
1021+
for (const int &line : to_remove) {
1022+
EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, line, false);
1023+
}
1024+
9811025
return updated_breakpoints;
9821026
}
9831027

@@ -1020,10 +1064,8 @@ void DebugAdapterProtocol::on_debug_breaked(const bool &p_reallydid, const bool
10201064
}
10211065

10221066
void DebugAdapterProtocol::on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled) {
1023-
DAP::Breakpoint breakpoint;
1067+
DAP::Breakpoint breakpoint(fetch_source(p_path));
10241068
breakpoint.verified = true;
1025-
breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(p_path);
1026-
breakpoint.source.compute_checksums();
10271069
breakpoint.line = p_line;
10281070

10291071
if (p_enabled) {
@@ -1046,8 +1088,7 @@ void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) {
10461088
if (_processing_breakpoint && !p_stack_dump.is_empty()) {
10471089
// Find existing breakpoint
10481090
Dictionary d = p_stack_dump[0];
1049-
DAP::Breakpoint breakpoint;
1050-
breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(d["file"]);
1091+
DAP::Breakpoint breakpoint(fetch_source(d["file"]));
10511092
breakpoint.line = d["line"];
10521093

10531094
List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint);
@@ -1060,25 +1101,26 @@ void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) {
10601101

10611102
stackframe_id = 0;
10621103
stackframe_list.clear();
1104+
scope_list.clear();
10631105

10641106
// Fill in stacktrace information
10651107
for (int i = 0; i < p_stack_dump.size(); i++) {
10661108
Dictionary stack_info = p_stack_dump[i];
1067-
DAP::StackFrame stackframe;
1109+
1110+
DAP::StackFrame stackframe(fetch_source(stack_info["file"]));
10681111
stackframe.id = stackframe_id++;
10691112
stackframe.name = stack_info["function"];
10701113
stackframe.line = stack_info["line"];
10711114
stackframe.column = 0;
1072-
stackframe.source.path = ProjectSettings::get_singleton()->globalize_path(stack_info["file"]);
1073-
stackframe.source.compute_checksums();
10741115

10751116
// Information for "Locals", "Members" and "Globals" variables respectively
1076-
List<int> scope_ids;
1117+
Vector<int> scope_ids;
10771118
for (int j = 0; j < 3; j++) {
10781119
scope_ids.push_back(variable_id++);
10791120
}
10801121

1081-
stackframe_list.insert(stackframe, scope_ids);
1122+
stackframe_list.push_back(stackframe);
1123+
scope_list.insert(stackframe.id, scope_ids);
10821124
}
10831125

10841126
_current_frame = 0;
@@ -1087,11 +1129,9 @@ void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) {
10871129

10881130
void DebugAdapterProtocol::on_debug_stack_frame_vars(const int &p_size) {
10891131
_remaining_vars = p_size;
1090-
DAP::StackFrame frame;
1091-
frame.id = _current_frame;
1092-
ERR_FAIL_COND(!stackframe_list.has(frame));
1093-
List<int> scope_ids = stackframe_list.find(frame)->value;
1094-
for (const int var_id : scope_ids) {
1132+
ERR_FAIL_COND(!scope_list.has(_current_frame));
1133+
Vector<int> scope_ids = scope_list.find(_current_frame)->value;
1134+
for (const int &var_id : scope_ids) {
10951135
if (variable_list.has(var_id)) {
10961136
variable_list.find(var_id)->value.clear();
10971137
} else {
@@ -1104,11 +1144,9 @@ void DebugAdapterProtocol::on_debug_stack_frame_var(const Array &p_data) {
11041144
DebuggerMarshalls::ScriptStackVariable stack_var;
11051145
stack_var.deserialize(p_data);
11061146

1107-
ERR_FAIL_COND(stackframe_list.is_empty());
1108-
DAP::StackFrame frame;
1109-
frame.id = _current_frame;
1147+
ERR_FAIL_COND(!scope_list.has(_current_frame));
1148+
Vector<int> scope_ids = scope_list.find(_current_frame)->value;
11101149

1111-
List<int> scope_ids = stackframe_list.find(frame)->value;
11121150
ERR_FAIL_COND(scope_ids.size() != 3);
11131151
ERR_FAIL_INDEX(stack_var.type, 4);
11141152
int var_id = scope_ids.get(stack_var.type);

editor/debugger/debug_adapter/debug_adapter_protocol.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class DebugAdapterProtocol : public Object {
7676
friend class DebugAdapterParser;
7777

7878
using DAPVarID = int;
79+
using DAPStackFrameID = int;
7980

8081
private:
8182
static DebugAdapterProtocol *singleton;
@@ -109,6 +110,9 @@ class DebugAdapterProtocol : public Object {
109110
bool request_remote_object(const ObjectID &p_object_id);
110111
bool request_remote_evaluate(const String &p_eval, int p_stack_frame);
111112

113+
const DAP::Source &fetch_source(const String &p_path);
114+
void update_source(const String &p_path);
115+
112116
bool _initialized = false;
113117
bool _processing_breakpoint = false;
114118
bool _stepping = false;
@@ -125,7 +129,9 @@ class DebugAdapterProtocol : public Object {
125129
int stackframe_id = 0;
126130
DAPVarID variable_id = 0;
127131
List<DAP::Breakpoint> breakpoint_list;
128-
HashMap<DAP::StackFrame, List<int>, DAP::StackFrame> stackframe_list;
132+
HashMap<String, DAP::Source> breakpoint_source_list;
133+
List<DAP::StackFrame> stackframe_list;
134+
HashMap<DAPStackFrameID, Vector<int>> scope_list;
129135
HashMap<DAPVarID, Array> variable_list;
130136

131137
HashMap<ObjectID, DAPVarID> object_list;

editor/debugger/debug_adapter/debug_adapter_types.h

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@
3030

3131
#pragma once
3232

33-
#include "core/io/json.h"
34-
#include "core/variant/dictionary.h"
33+
#include "core/io/file_access.h"
3534

3635
namespace DAP {
3736

@@ -68,6 +67,8 @@ struct Source {
6867
void compute_checksums() {
6968
ERR_FAIL_COND(path.is_empty());
7069

70+
_checksums.clear();
71+
7172
// MD5
7273
Checksum md5;
7374
md5.algorithm = "MD5";
@@ -101,18 +102,24 @@ struct Source {
101102
struct Breakpoint {
102103
int id = 0;
103104
bool verified = false;
104-
Source source;
105+
const Source *source = nullptr;
105106
int line = 0;
106107

108+
Breakpoint() = default; // Empty constructor is invalid, but is necessary because Godot's collections don't support rvalues.
109+
Breakpoint(const Source &p_source) :
110+
source(&p_source) {}
111+
107112
bool operator==(const Breakpoint &p_other) const {
108-
return source.path == p_other.source.path && line == p_other.line;
113+
return source == p_other.source && line == p_other.line;
109114
}
110115

111116
_FORCE_INLINE_ Dictionary to_json() const {
112117
Dictionary dict;
113118
dict["id"] = id;
114119
dict["verified"] = verified;
115-
dict["source"] = source.to_json();
120+
if (source) {
121+
dict["source"] = source->to_json();
122+
}
116123
dict["line"] = line;
117124

118125
return dict;
@@ -212,30 +219,25 @@ struct SourceBreakpoint {
212219
struct StackFrame {
213220
int id = 0;
214221
String name;
215-
Source source;
222+
const Source *source = nullptr;
216223
int line = 0;
217224
int column = 0;
218225

226+
StackFrame() = default; // Empty constructor is invalid, but is necessary because Godot's collections don't support rvalues.
227+
StackFrame(const Source &p_source) :
228+
source(&p_source) {}
229+
219230
static uint32_t hash(const StackFrame &p_frame) {
220231
return hash_murmur3_one_32(p_frame.id);
221232
}
222-
bool operator==(const StackFrame &p_other) const {
223-
return id == p_other.id;
224-
}
225-
226-
_FORCE_INLINE_ void from_json(const Dictionary &p_params) {
227-
id = p_params["id"];
228-
name = p_params["name"];
229-
source.from_json(p_params["source"]);
230-
line = p_params["line"];
231-
column = p_params["column"];
232-
}
233233

234234
_FORCE_INLINE_ Dictionary to_json() const {
235235
Dictionary dict;
236236
dict["id"] = id;
237237
dict["name"] = name;
238-
dict["source"] = source.to_json();
238+
if (source) {
239+
dict["source"] = source->to_json();
240+
}
239241
dict["line"] = line;
240242
dict["column"] = column;
241243

0 commit comments

Comments
 (0)