1/**************************************************************************/
2/* debug_adapter_parser.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "debug_adapter_parser.h"
32
33#include "editor/debugger/editor_debugger_node.h"
34#include "editor/debugger/script_editor_debugger.h"
35#include "editor/export/editor_export_platform.h"
36#include "editor/gui/editor_run_bar.h"
37#include "editor/plugins/script_editor_plugin.h"
38
39void DebugAdapterParser::_bind_methods() {
40 // Requests
41 ClassDB::bind_method(D_METHOD("req_initialize", "params"), &DebugAdapterParser::req_initialize);
42 ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::req_disconnect);
43 ClassDB::bind_method(D_METHOD("req_launch", "params"), &DebugAdapterParser::req_launch);
44 ClassDB::bind_method(D_METHOD("req_attach", "params"), &DebugAdapterParser::req_attach);
45 ClassDB::bind_method(D_METHOD("req_restart", "params"), &DebugAdapterParser::req_restart);
46 ClassDB::bind_method(D_METHOD("req_terminate", "params"), &DebugAdapterParser::req_terminate);
47 ClassDB::bind_method(D_METHOD("req_configurationDone", "params"), &DebugAdapterParser::prepare_success_response);
48 ClassDB::bind_method(D_METHOD("req_pause", "params"), &DebugAdapterParser::req_pause);
49 ClassDB::bind_method(D_METHOD("req_continue", "params"), &DebugAdapterParser::req_continue);
50 ClassDB::bind_method(D_METHOD("req_threads", "params"), &DebugAdapterParser::req_threads);
51 ClassDB::bind_method(D_METHOD("req_stackTrace", "params"), &DebugAdapterParser::req_stackTrace);
52 ClassDB::bind_method(D_METHOD("req_setBreakpoints", "params"), &DebugAdapterParser::req_setBreakpoints);
53 ClassDB::bind_method(D_METHOD("req_breakpointLocations", "params"), &DebugAdapterParser::req_breakpointLocations);
54 ClassDB::bind_method(D_METHOD("req_scopes", "params"), &DebugAdapterParser::req_scopes);
55 ClassDB::bind_method(D_METHOD("req_variables", "params"), &DebugAdapterParser::req_variables);
56 ClassDB::bind_method(D_METHOD("req_next", "params"), &DebugAdapterParser::req_next);
57 ClassDB::bind_method(D_METHOD("req_stepIn", "params"), &DebugAdapterParser::req_stepIn);
58 ClassDB::bind_method(D_METHOD("req_evaluate", "params"), &DebugAdapterParser::req_evaluate);
59 ClassDB::bind_method(D_METHOD("req_godot/put_msg", "params"), &DebugAdapterParser::req_godot_put_msg);
60}
61
62Dictionary DebugAdapterParser::prepare_base_event() const {
63 Dictionary event;
64 event["type"] = "event";
65
66 return event;
67}
68
69Dictionary DebugAdapterParser::prepare_success_response(const Dictionary &p_params) const {
70 Dictionary response;
71 response["type"] = "response";
72 response["request_seq"] = p_params["seq"];
73 response["command"] = p_params["command"];
74 response["success"] = true;
75
76 return response;
77}
78
79Dictionary DebugAdapterParser::prepare_error_response(const Dictionary &p_params, DAP::ErrorType err_type, const Dictionary &variables) const {
80 Dictionary response, body;
81 response["type"] = "response";
82 response["request_seq"] = p_params["seq"];
83 response["command"] = p_params["command"];
84 response["success"] = false;
85 response["body"] = body;
86
87 DAP::Message message;
88 String error, error_desc;
89 switch (err_type) {
90 case DAP::ErrorType::WRONG_PATH:
91 error = "wrong_path";
92 error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\"";
93 break;
94 case DAP::ErrorType::NOT_RUNNING:
95 error = "not_running";
96 error_desc = "Can't attach to a running session since there isn't one.";
97 break;
98 case DAP::ErrorType::TIMEOUT:
99 error = "timeout";
100 error_desc = "Timeout reached while processing a request.";
101 break;
102 case DAP::ErrorType::UNKNOWN_PLATFORM:
103 error = "unknown_platform";
104 error_desc = "The specified platform is unknown.";
105 break;
106 case DAP::ErrorType::MISSING_DEVICE:
107 error = "missing_device";
108 error_desc = "There's no connected device with specified id.";
109 break;
110 case DAP::ErrorType::UNKNOWN:
111 default:
112 error = "unknown";
113 error_desc = "An unknown error has occurred when processing the request.";
114 break;
115 }
116
117 message.id = err_type;
118 message.format = error_desc;
119 message.variables = variables;
120 response["message"] = error;
121 body["error"] = message.to_json();
122
123 return response;
124}
125
126Dictionary DebugAdapterParser::req_initialize(const Dictionary &p_params) const {
127 Dictionary response = prepare_success_response(p_params);
128 Dictionary args = p_params["arguments"];
129
130 Ref<DAPeer> peer = DebugAdapterProtocol::get_singleton()->get_current_peer();
131
132 peer->linesStartAt1 = args.get("linesStartAt1", false);
133 peer->columnsStartAt1 = args.get("columnsStartAt1", false);
134 peer->supportsVariableType = args.get("supportsVariableType", false);
135 peer->supportsInvalidatedEvent = args.get("supportsInvalidatedEvent", false);
136
137 DAP::Capabilities caps;
138 response["body"] = caps.to_json();
139
140 DebugAdapterProtocol::get_singleton()->notify_initialized();
141
142 if (DebugAdapterProtocol::get_singleton()->_sync_breakpoints) {
143 // Send all current breakpoints
144 List<String> breakpoints;
145 ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
146 for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
147 String breakpoint = E->get();
148
149 String path = breakpoint.left(breakpoint.find(":", 6)); // Skip initial part of path, aka "res://"
150 int line = breakpoint.substr(path.size()).to_int();
151
152 DebugAdapterProtocol::get_singleton()->on_debug_breakpoint_toggled(path, line, true);
153 }
154 } else {
155 // Remove all current breakpoints
156 EditorDebuggerNode::get_singleton()->get_default_debugger()->_clear_breakpoints();
157 }
158
159 return response;
160}
161
162Dictionary DebugAdapterParser::req_disconnect(const Dictionary &p_params) const {
163 if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->attached) {
164 EditorRunBar::get_singleton()->stop_playing();
165 }
166
167 return prepare_success_response(p_params);
168}
169
170Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) const {
171 Dictionary args = p_params["arguments"];
172 if (args.has("project") && !is_valid_path(args["project"])) {
173 Dictionary variables;
174 variables["clientPath"] = args["project"];
175 variables["editorPath"] = ProjectSettings::get_singleton()->get_resource_path();
176 return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables);
177 }
178
179 if (args.has("godot/custom_data")) {
180 DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsCustomData = args["godot/custom_data"];
181 }
182
183 ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger();
184 if ((bool)args["noDebug"] != dbg->is_skip_breakpoints()) {
185 dbg->debug_skip_breakpoints();
186 }
187
188 String platform_string = args.get("platform", "host");
189 if (platform_string == "host") {
190 EditorRunBar::get_singleton()->play_main_scene();
191 } else {
192 int device = args.get("device", -1);
193 int idx = -1;
194 if (platform_string == "android") {
195 for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
196 if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Android") {
197 idx = i;
198 break;
199 }
200 }
201 } else if (platform_string == "web") {
202 for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
203 if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Web") {
204 idx = i;
205 break;
206 }
207 }
208 }
209
210 if (idx == -1) {
211 return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN_PLATFORM);
212 }
213
214 EditorRunBar *run_bar = EditorRunBar::get_singleton();
215 Error err = platform_string == "android" ? run_bar->start_native_device(device * 10000 + idx) : run_bar->start_native_device(idx);
216 if (err) {
217 if (err == ERR_INVALID_PARAMETER && platform_string == "android") {
218 return prepare_error_response(p_params, DAP::ErrorType::MISSING_DEVICE);
219 } else {
220 return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN);
221 }
222 }
223 }
224
225 DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = false;
226 DebugAdapterProtocol::get_singleton()->notify_process();
227
228 return prepare_success_response(p_params);
229}
230
231Dictionary DebugAdapterParser::req_attach(const Dictionary &p_params) const {
232 ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger();
233 if (!dbg->is_session_active()) {
234 return prepare_error_response(p_params, DAP::ErrorType::NOT_RUNNING);
235 }
236
237 DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = true;
238 DebugAdapterProtocol::get_singleton()->notify_process();
239 return prepare_success_response(p_params);
240}
241
242Dictionary DebugAdapterParser::req_restart(const Dictionary &p_params) const {
243 // Extract embedded "arguments" so it can be given to req_launch/req_attach
244 Dictionary params = p_params, args;
245 args = params["arguments"];
246 args = args["arguments"];
247 params["arguments"] = args;
248
249 Dictionary response = DebugAdapterProtocol::get_singleton()->get_current_peer()->attached ? req_attach(params) : req_launch(params);
250 if (!response["success"]) {
251 response["command"] = p_params["command"];
252 return response;
253 }
254
255 return prepare_success_response(p_params);
256}
257
258Dictionary DebugAdapterParser::req_terminate(const Dictionary &p_params) const {
259 EditorRunBar::get_singleton()->stop_playing();
260
261 return prepare_success_response(p_params);
262}
263
264Dictionary DebugAdapterParser::req_pause(const Dictionary &p_params) const {
265 EditorRunBar::get_singleton()->get_pause_button()->set_pressed(true);
266 EditorDebuggerNode::get_singleton()->_paused();
267
268 DebugAdapterProtocol::get_singleton()->notify_stopped_paused();
269
270 return prepare_success_response(p_params);
271}
272
273Dictionary DebugAdapterParser::req_continue(const Dictionary &p_params) const {
274 EditorRunBar::get_singleton()->get_pause_button()->set_pressed(false);
275 EditorDebuggerNode::get_singleton()->_paused();
276
277 DebugAdapterProtocol::get_singleton()->notify_continued();
278
279 return prepare_success_response(p_params);
280}
281
282Dictionary DebugAdapterParser::req_threads(const Dictionary &p_params) const {
283 Dictionary response = prepare_success_response(p_params), body;
284 response["body"] = body;
285
286 Array arr;
287 DAP::Thread thread;
288
289 thread.id = 1; // Hardcoded because Godot only supports debugging one thread at the moment
290 thread.name = "Main";
291 arr.push_back(thread.to_json());
292 body["threads"] = arr;
293
294 return response;
295}
296
297Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const {
298 if (DebugAdapterProtocol::get_singleton()->_processing_stackdump) {
299 return Dictionary();
300 }
301
302 Dictionary response = prepare_success_response(p_params), body;
303 response["body"] = body;
304
305 bool lines_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->linesStartAt1;
306 bool columns_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->columnsStartAt1;
307
308 Array arr;
309 DebugAdapterProtocol *dap = DebugAdapterProtocol::get_singleton();
310 for (const KeyValue<DAP::StackFrame, List<int>> &E : dap->stackframe_list) {
311 DAP::StackFrame sf = E.key;
312 if (!lines_at_one) {
313 sf.line--;
314 }
315 if (!columns_at_one) {
316 sf.column--;
317 }
318
319 arr.push_back(sf.to_json());
320 }
321
322 body["stackFrames"] = arr;
323 return response;
324}
325
326Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) const {
327 Dictionary response = prepare_success_response(p_params), body;
328 response["body"] = body;
329
330 Dictionary args = p_params["arguments"];
331 DAP::Source source;
332 source.from_json(args["source"]);
333
334 bool lines_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->linesStartAt1;
335
336 if (!is_valid_path(source.path)) {
337 Dictionary variables;
338 variables["clientPath"] = source.path;
339 variables["editorPath"] = ProjectSettings::get_singleton()->get_resource_path();
340 return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables);
341 }
342
343 // If path contains \, it's a Windows path, so we need to convert it to /, and make the drive letter uppercase
344 if (source.path.find("\\") != -1) {
345 source.path = source.path.replace("\\", "/");
346 source.path = source.path.substr(0, 1).to_upper() + source.path.substr(1);
347 }
348
349 Array breakpoints = args["breakpoints"], lines;
350 for (int i = 0; i < breakpoints.size(); i++) {
351 DAP::SourceBreakpoint breakpoint;
352 breakpoint.from_json(breakpoints[i]);
353
354 lines.push_back(breakpoint.line + !lines_at_one);
355 }
356
357 Array updated_breakpoints = DebugAdapterProtocol::get_singleton()->update_breakpoints(source.path, lines);
358 body["breakpoints"] = updated_breakpoints;
359
360 return response;
361}
362
363Dictionary DebugAdapterParser::req_breakpointLocations(const Dictionary &p_params) const {
364 Dictionary response = prepare_success_response(p_params), body;
365 response["body"] = body;
366 Dictionary args = p_params["arguments"];
367
368 Array locations;
369 DAP::BreakpointLocation location;
370 location.line = args["line"];
371 if (args.has("endLine")) {
372 location.endLine = args["endLine"];
373 }
374 locations.push_back(location.to_json());
375
376 body["breakpoints"] = locations;
377 return response;
378}
379
380Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) const {
381 Dictionary response = prepare_success_response(p_params), body;
382 response["body"] = body;
383
384 Dictionary args = p_params["arguments"];
385 int frame_id = args["frameId"];
386 Array scope_list;
387
388 DAP::StackFrame frame;
389 frame.id = frame_id;
390 HashMap<DAP::StackFrame, List<int>, DAP::StackFrame>::Iterator E = DebugAdapterProtocol::get_singleton()->stackframe_list.find(frame);
391 if (E) {
392 ERR_FAIL_COND_V(E->value.size() != 3, prepare_error_response(p_params, DAP::ErrorType::UNKNOWN));
393 for (int i = 0; i < 3; i++) {
394 DAP::Scope scope;
395 scope.variablesReference = E->value[i];
396 switch (i) {
397 case 0:
398 scope.name = "Locals";
399 scope.presentationHint = "locals";
400 break;
401 case 1:
402 scope.name = "Members";
403 scope.presentationHint = "members";
404 break;
405 case 2:
406 scope.name = "Globals";
407 scope.presentationHint = "globals";
408 }
409
410 scope_list.push_back(scope.to_json());
411 }
412 }
413
414 EditorDebuggerNode::get_singleton()->get_default_debugger()->request_stack_dump(frame_id);
415 DebugAdapterProtocol::get_singleton()->_current_frame = frame_id;
416
417 body["scopes"] = scope_list;
418 return response;
419}
420
421Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const {
422 // If _remaining_vars > 0, the debuggee is still sending a stack dump to the editor.
423 if (DebugAdapterProtocol::get_singleton()->_remaining_vars > 0) {
424 return Dictionary();
425 }
426
427 Dictionary response = prepare_success_response(p_params), body;
428 response["body"] = body;
429
430 Dictionary args = p_params["arguments"];
431 int variable_id = args["variablesReference"];
432
433 HashMap<int, Array>::Iterator E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id);
434
435 if (E) {
436 if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) {
437 for (int i = 0; i < E->value.size(); i++) {
438 Dictionary variable = E->value[i];
439 variable.erase("type");
440 }
441 }
442 body["variables"] = E ? E->value : Array();
443 return response;
444 } else {
445 return Dictionary();
446 }
447}
448
449Dictionary DebugAdapterParser::req_next(const Dictionary &p_params) const {
450 EditorDebuggerNode::get_singleton()->get_default_debugger()->debug_next();
451 DebugAdapterProtocol::get_singleton()->_stepping = true;
452
453 return prepare_success_response(p_params);
454}
455
456Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const {
457 EditorDebuggerNode::get_singleton()->get_default_debugger()->debug_step();
458 DebugAdapterProtocol::get_singleton()->_stepping = true;
459
460 return prepare_success_response(p_params);
461}
462
463Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const {
464 Dictionary response = prepare_success_response(p_params), body;
465 response["body"] = body;
466
467 Dictionary args = p_params["arguments"];
468
469 String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression"]);
470 body["result"] = value;
471
472 return response;
473}
474
475Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const {
476 Dictionary args = p_params["arguments"];
477
478 String msg = args["message"];
479 Array data = args["data"];
480
481 EditorDebuggerNode::get_singleton()->get_default_debugger()->_put_msg(msg, data);
482
483 return prepare_success_response(p_params);
484}
485
486Dictionary DebugAdapterParser::ev_initialized() const {
487 Dictionary event = prepare_base_event();
488 event["event"] = "initialized";
489
490 return event;
491}
492
493Dictionary DebugAdapterParser::ev_process(const String &p_command) const {
494 Dictionary event = prepare_base_event(), body;
495 event["event"] = "process";
496 event["body"] = body;
497
498 body["name"] = OS::get_singleton()->get_executable_path();
499 body["startMethod"] = p_command;
500
501 return event;
502}
503
504Dictionary DebugAdapterParser::ev_terminated() const {
505 Dictionary event = prepare_base_event();
506 event["event"] = "terminated";
507
508 return event;
509}
510
511Dictionary DebugAdapterParser::ev_exited(const int &p_exitcode) const {
512 Dictionary event = prepare_base_event(), body;
513 event["event"] = "exited";
514 event["body"] = body;
515
516 body["exitCode"] = p_exitcode;
517
518 return event;
519}
520
521Dictionary DebugAdapterParser::ev_stopped() const {
522 Dictionary event = prepare_base_event(), body;
523 event["event"] = "stopped";
524 event["body"] = body;
525
526 body["threadId"] = 1;
527
528 return event;
529}
530
531Dictionary DebugAdapterParser::ev_stopped_paused() const {
532 Dictionary event = ev_stopped();
533 Dictionary body = event["body"];
534
535 body["reason"] = "paused";
536 body["description"] = "Paused";
537
538 return event;
539}
540
541Dictionary DebugAdapterParser::ev_stopped_exception(const String &p_error) const {
542 Dictionary event = ev_stopped();
543 Dictionary body = event["body"];
544
545 body["reason"] = "exception";
546 body["description"] = "Exception";
547 body["text"] = p_error;
548
549 return event;
550}
551
552Dictionary DebugAdapterParser::ev_stopped_breakpoint(const int &p_id) const {
553 Dictionary event = ev_stopped();
554 Dictionary body = event["body"];
555
556 body["reason"] = "breakpoint";
557 body["description"] = "Breakpoint";
558
559 Array breakpoints;
560 breakpoints.push_back(p_id);
561 body["hitBreakpointIds"] = breakpoints;
562
563 return event;
564}
565
566Dictionary DebugAdapterParser::ev_stopped_step() const {
567 Dictionary event = ev_stopped();
568 Dictionary body = event["body"];
569
570 body["reason"] = "step";
571 body["description"] = "Breakpoint";
572
573 return event;
574}
575
576Dictionary DebugAdapterParser::ev_continued() const {
577 Dictionary event = prepare_base_event(), body;
578 event["event"] = "continued";
579 event["body"] = body;
580
581 body["threadId"] = 1;
582
583 return event;
584}
585
586Dictionary DebugAdapterParser::ev_output(const String &p_message) const {
587 Dictionary event = prepare_base_event(), body;
588 event["event"] = "output";
589 event["body"] = body;
590
591 body["category"] = "stdout";
592 body["output"] = p_message + "\r\n";
593
594 return event;
595}
596
597Dictionary DebugAdapterParser::ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const {
598 Dictionary event = prepare_base_event(), body;
599 event["event"] = "breakpoint";
600 event["body"] = body;
601
602 body["reason"] = p_enabled ? "new" : "removed";
603 body["breakpoint"] = p_breakpoint.to_json();
604
605 return event;
606}
607
608Dictionary DebugAdapterParser::ev_custom_data(const String &p_msg, const Array &p_data) const {
609 Dictionary event = prepare_base_event(), body;
610 event["event"] = "godot/custom_data";
611 event["body"] = body;
612
613 body["message"] = p_msg;
614 body["data"] = p_data;
615
616 return event;
617}
618