Skip to content

Commit 7ba2ebd

Browse files
committed
Merge pull request #104523 from rsubtil/fix-dap_bugs
Fix crash when removing breakpoints from DAP, and multiple fixes
2 parents e5f7ec0 + 0272e35 commit 7ba2ebd

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
@@ -174,6 +174,7 @@ void DebugAdapterProtocol::reset_current_info() {
174174
void DebugAdapterProtocol::reset_ids() {
175175
breakpoint_id = 0;
176176
breakpoint_list.clear();
177+
breakpoint_source_list.clear();
177178

178179
reset_stack_info();
179180
}
@@ -183,6 +184,7 @@ void DebugAdapterProtocol::reset_stack_info() {
183184
variable_id = 1;
184185

185186
stackframe_list.clear();
187+
scope_list.clear();
186188
variable_list.clear();
187189
object_list.clear();
188190
object_pending_set.clear();
@@ -822,6 +824,30 @@ bool DebugAdapterProtocol::request_remote_evaluate(const String &p_eval, int p_s
822824
return true;
823825
}
824826

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

961987
// Add breakpoints
962988
for (int i = 0; i < p_lines.size(); i++) {
963-
EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, p_lines[i], true);
964-
DAP::Breakpoint breakpoint;
989+
DAP::Breakpoint breakpoint(fetch_source(p_path));
965990
breakpoint.line = p_lines[i];
966-
breakpoint.source.path = p_path;
967991

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

9721008
// Remove breakpoints
1009+
// Must be deferred because we are iterating the breakpoint list.
1010+
Vector<int> to_remove;
1011+
9731012
for (const DAP::Breakpoint &b : breakpoint_list) {
974-
if (b.source.path == p_path && !p_lines.has(b.line)) {
975-
EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, b.line, false);
1013+
if (b.source->path == p_path && !p_lines.has(b.line)) {
1014+
to_remove.push_back(b.line);
9761015
}
9771016
}
9781017

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

@@ -1018,10 +1062,8 @@ void DebugAdapterProtocol::on_debug_breaked(const bool &p_reallydid, const bool
10181062
}
10191063

10201064
void DebugAdapterProtocol::on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled) {
1021-
DAP::Breakpoint breakpoint;
1065+
DAP::Breakpoint breakpoint(fetch_source(p_path));
10221066
breakpoint.verified = true;
1023-
breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(p_path);
1024-
breakpoint.source.compute_checksums();
10251067
breakpoint.line = p_line;
10261068

10271069
if (p_enabled) {
@@ -1044,8 +1086,7 @@ void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) {
10441086
if (_processing_breakpoint && !p_stack_dump.is_empty()) {
10451087
// Find existing breakpoint
10461088
Dictionary d = p_stack_dump[0];
1047-
DAP::Breakpoint breakpoint;
1048-
breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(d["file"]);
1089+
DAP::Breakpoint breakpoint(fetch_source(d["file"]));
10491090
breakpoint.line = d["line"];
10501091

10511092
List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint);
@@ -1058,25 +1099,26 @@ void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) {
10581099

10591100
stackframe_id = 0;
10601101
stackframe_list.clear();
1102+
scope_list.clear();
10611103

10621104
// Fill in stacktrace information
10631105
for (int i = 0; i < p_stack_dump.size(); i++) {
10641106
Dictionary stack_info = p_stack_dump[i];
1065-
DAP::StackFrame stackframe;
1107+
1108+
DAP::StackFrame stackframe(fetch_source(stack_info["file"]));
10661109
stackframe.id = stackframe_id++;
10671110
stackframe.name = stack_info["function"];
10681111
stackframe.line = stack_info["line"];
10691112
stackframe.column = 0;
1070-
stackframe.source.path = ProjectSettings::get_singleton()->globalize_path(stack_info["file"]);
1071-
stackframe.source.compute_checksums();
10721113

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

1079-
stackframe_list.insert(stackframe, scope_ids);
1120+
stackframe_list.push_back(stackframe);
1121+
scope_list.insert(stackframe.id, scope_ids);
10801122
}
10811123

10821124
_current_frame = 0;
@@ -1085,11 +1127,9 @@ void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) {
10851127

10861128
void DebugAdapterProtocol::on_debug_stack_frame_vars(const int &p_size) {
10871129
_remaining_vars = p_size;
1088-
DAP::StackFrame frame;
1089-
frame.id = _current_frame;
1090-
ERR_FAIL_COND(!stackframe_list.has(frame));
1091-
List<int> scope_ids = stackframe_list.find(frame)->value;
1092-
for (const int var_id : scope_ids) {
1130+
ERR_FAIL_COND(!scope_list.has(_current_frame));
1131+
Vector<int> scope_ids = scope_list.find(_current_frame)->value;
1132+
for (const int &var_id : scope_ids) {
10931133
if (variable_list.has(var_id)) {
10941134
variable_list.find(var_id)->value.clear();
10951135
} else {
@@ -1102,11 +1142,9 @@ void DebugAdapterProtocol::on_debug_stack_frame_var(const Array &p_data) {
11021142
DebuggerMarshalls::ScriptStackVariable stack_var;
11031143
stack_var.deserialize(p_data);
11041144

1105-
ERR_FAIL_COND(stackframe_list.is_empty());
1106-
DAP::StackFrame frame;
1107-
frame.id = _current_frame;
1145+
ERR_FAIL_COND(!scope_list.has(_current_frame));
1146+
Vector<int> scope_ids = scope_list.find(_current_frame)->value;
11081147

1109-
List<int> scope_ids = stackframe_list.find(frame)->value;
11101148
ERR_FAIL_COND(scope_ids.size() != 3);
11111149
ERR_FAIL_INDEX(stack_var.type, 4);
11121150
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)