Skip to content

Commit

Permalink
Implement conditional, printing, and disabled breakpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
ydeltastar committed Dec 24, 2024
1 parent 024efda commit 4d08122
Show file tree
Hide file tree
Showing 38 changed files with 923 additions and 183 deletions.
10 changes: 10 additions & 0 deletions core/core_bind.compat.inc
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ void OS::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::_execute_with_pipe_bind_compat_94434);
}

// Debugger

void EngineDebugger::_insert_breakpoint_bind_compat_100516(int p_line, const StringName &p_source) {
insert_breakpoint(p_line, p_source);
}

void EngineDebugger::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("insert_breakpoint", "line", "source"), &EngineDebugger::_insert_breakpoint_bind_compat_100516);
}

} // namespace core_bind

#endif // DISABLE_DEPRECATED
11 changes: 8 additions & 3 deletions core/core_bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2128,9 +2128,14 @@ bool EngineDebugger::is_skipping_breakpoints() const {
return ::EngineDebugger::get_script_debugger()->is_skipping_breakpoints();
}

void EngineDebugger::insert_breakpoint(int p_line, const StringName &p_source) {
void EngineDebugger::insert_breakpoint(int p_line, const StringName &p_source, bool p_enabled, bool p_suspend, const String &p_condition, const String &p_print) {
ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't insert breakpoint. No active debugger");
::EngineDebugger::get_script_debugger()->insert_breakpoint(p_line, p_source);
Breakpoint bp = Breakpoint(p_source, p_line);
bp.enabled = p_enabled;
bp.suspend = p_suspend;
bp.condition = p_condition;
bp.print = p_print;
::EngineDebugger::get_script_debugger()->insert_breakpoint(p_line, p_source, bp);
}

void EngineDebugger::remove_breakpoint(int p_line, const StringName &p_source) {
Expand Down Expand Up @@ -2182,7 +2187,7 @@ void EngineDebugger::_bind_methods() {

ClassDB::bind_method(D_METHOD("is_breakpoint", "line", "source"), &EngineDebugger::is_breakpoint);
ClassDB::bind_method(D_METHOD("is_skipping_breakpoints"), &EngineDebugger::is_skipping_breakpoints);
ClassDB::bind_method(D_METHOD("insert_breakpoint", "line", "source"), &EngineDebugger::insert_breakpoint);
ClassDB::bind_method(D_METHOD("insert_breakpoint", "line", "source", "enabled", "suspend", "condition", "print"), &EngineDebugger::insert_breakpoint, DEFVAL(true), DEFVAL(true), DEFVAL(String()), DEFVAL(String()));
ClassDB::bind_method(D_METHOD("remove_breakpoint", "line", "source"), &EngineDebugger::remove_breakpoint);
ClassDB::bind_method(D_METHOD("clear_breakpoints"), &EngineDebugger::clear_breakpoints);
}
Expand Down
7 changes: 5 additions & 2 deletions core/core_bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ class OS : public Object {

#ifndef DISABLE_DEPRECATED
Dictionary _execute_with_pipe_bind_compat_94434(const String &p_path, const Vector<String> &p_arguments);

String _read_string_from_stdin_bind_compat_91201();
static void _bind_compatibility_methods();
#endif
Expand Down Expand Up @@ -612,6 +611,10 @@ class EngineDebugger : public Object {
static void _bind_methods();
static EngineDebugger *singleton;

#ifndef DISABLE_DEPRECATED
void _insert_breakpoint_bind_compat_100516(int p_line, const StringName &p_source);
static void _bind_compatibility_methods();
#endif
public:
static EngineDebugger *get_singleton() { return singleton; }

Expand Down Expand Up @@ -644,7 +647,7 @@ class EngineDebugger : public Object {

bool is_breakpoint(int p_line, const StringName &p_source) const;
bool is_skipping_breakpoints() const;
void insert_breakpoint(int p_line, const StringName &p_source);
void insert_breakpoint(int p_line, const StringName &p_source, bool p_enabled = true, bool p_suspend = true, const String &p_condition = "", const String &p_print = "");
void remove_breakpoint(int p_line, const StringName &p_source);
void clear_breakpoints();

Expand Down
36 changes: 32 additions & 4 deletions core/debugger/engine_debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "core/debugger/remote_debugger_peer.h"
#include "core/debugger/script_debugger.h"
#include "core/os/os.h"
#include "core/variant/variant_utility.h"

EngineDebugger *EngineDebugger::singleton = nullptr;
ScriptDebugger *EngineDebugger::script_debugger = nullptr;
Expand Down Expand Up @@ -162,11 +163,38 @@ void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, co
singleton_script_debugger->set_skip_breakpoints(p_skip_breakpoints);

for (int i = 0; i < p_breakpoints.size(); i++) {
const String &bp = p_breakpoints[i];
int sp = bp.rfind_char(':');
ERR_CONTINUE_MSG(sp == -1, vformat("Invalid breakpoint: '%s', expected file:line format.", bp));
Breakpoint bp;

const String &bp_arg = p_breakpoints[i];
int set_sp = bp_arg.find_char('|');
String source_line = bp_arg.substr(0, set_sp == -1 ? bp_arg.length() : set_sp);

int sp = source_line.rfind_char(':');
ERR_CONTINUE_MSG(sp == -1, vformat("Invalid breakpoint: '%s', expected file:line format.", bp_arg));

bp.source = bp_arg.substr(0, sp);
bp.line = bp_arg.substr(sp + 1, source_line.length()).to_int();

if (set_sp > -1) {
const PackedStringArray &bp_settings = bp_arg.substr(set_sp + 1, bp_arg.length()).split("|");
for (const String &setting : bp_settings) {
String var = setting.get_slice("=", 0);
String value = setting.get_slice("=", 1);
ERR_CONTINUE_MSG(var.is_empty() || value.is_empty(), vformat("Invalid breakpoint condition: '%s', expected var=value format.", setting));

if (var == "enabled") {
bp.enabled = VariantUtilityFunctions::str_to_var(value);
} else if (var == "suspend") {
bp.suspend = VariantUtilityFunctions::str_to_var(value);
} else if (var == "condition") {
bp.condition = value;
} else if (var == "print") {
bp.print = value;
}
}
}

singleton_script_debugger->insert_breakpoint(bp.substr(sp + 1, bp.length()).to_int(), bp.substr(0, sp));
singleton_script_debugger->insert_breakpoint(bp.line, bp.source, bp);
}

allow_focus_steal_fn = p_allow_focus_steal_fn;
Expand Down
14 changes: 8 additions & 6 deletions core/debugger/local_debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,30 +241,32 @@ void LocalDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {

} else if (line.begins_with("br") || line.begins_with("break")) {
if (line.get_slice_count(" ") <= 1) {
const HashMap<int, HashSet<StringName>> &breakpoints = script_debugger->get_breakpoints();
const HashMap<StringName, HashMap<int, Breakpoint>> &breakpoints = script_debugger->get_breakpoints();
if (breakpoints.size() == 0) {
print_line("No Breakpoints.");
continue;
}

print_line("Breakpoint(s): " + itos(breakpoints.size()));
for (const KeyValue<int, HashSet<StringName>> &E : breakpoints) {
print_line("\t" + String(*E.value.begin()) + ":" + itos(E.key));
for (const KeyValue<StringName, HashMap<int, Breakpoint>> &E : breakpoints) {
for (const KeyValue<int, Breakpoint> &T : E.value) {
print_line("\t" + String(E.key) + ":" + itos(T.value.line));
}
}

} else {
Pair<String, int> breakpoint = to_breakpoint(line);

String source = breakpoint.first;
int linenr = breakpoint.second;
int bp_line = breakpoint.second;

if (source.is_empty()) {
continue;
}

script_debugger->insert_breakpoint(linenr, source);
script_debugger->insert_breakpoint(bp_line, source, Breakpoint(source, bp_line));

print_line("Added breakpoint at " + source + ":" + itos(linenr));
print_line("Added breakpoint at " + source + ":" + itos(bp_line));
}

} else if (line == "q" || line == "quit" ||
Expand Down
113 changes: 103 additions & 10 deletions core/debugger/remote_debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,97 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
}
}

if (!p_is_error_breakpoint) {
const int frame = 0;
ScriptLanguage *script_lang = script_debugger->get_break_language();
const String &source = script_lang->debug_get_stack_level_source(frame);
const int &line = script_lang->debug_get_stack_level_line(frame);

if (EngineDebugger::get_script_debugger()->is_breakpoint(line, source)) {
const HashMap<StringName, HashMap<int, Breakpoint>> &breakpoints = EngineDebugger::get_script_debugger()->get_breakpoints();
const Breakpoint &breakpoint = breakpoints[source][line];

if (!breakpoint.enabled) {
return;
}

if (!breakpoint.condition.is_empty()) {
ScriptInstance *breaked_instance = script_lang->debug_get_stack_level_instance(frame);
if (breaked_instance) {
List<String> locals;
List<Variant> local_vals;
script_lang->debug_get_stack_level_locals(frame, &locals, &local_vals);
ERR_FAIL_COND(locals.size() != local_vals.size());

PackedStringArray locals_vector;
for (const String &S : locals) {
locals_vector.append(S);
}

Array local_vals_array;
for (const Variant &V : local_vals) {
local_vals_array.append(V);
}

Expression expression;
expression.parse(breakpoint.condition, locals_vector);
const Variant return_val = expression.execute(local_vals_array, breaked_instance->get_owner(), false);

String error = expression.get_error_text();
if (!error.is_empty()) {
WARN_VERBOSE(vformat("Breakpoint's condition expression '%s' error: %s", breakpoint.condition, error));
return;
}

if (!return_val.booleanize()) {
return; // Breakpoint's condition failed.
}
}
}

if (!breakpoint.print.is_empty()) {
ScriptInstance *breaked_instance = script_lang->debug_get_stack_level_instance(frame);
if (breaked_instance) {
String expression_str = vformat("print(\"%s\".format(input_dict))", breakpoint.print);

Dictionary input_dict;
{
List<String> locals;
List<Variant> local_vals;
script_lang->debug_get_stack_level_locals(frame, &locals, &local_vals);
ERR_FAIL_COND(locals.size() != local_vals.size());

const List<Variant>::Element *V = local_vals.front();
for (const String &S : locals) {
const Variant &value = V->get();
input_dict[S] = value;
V = V->next();
}
}
{
List<String> members;
List<Variant> member_vals;
script_lang->debug_get_stack_level_members(frame, &members, &member_vals);
ERR_FAIL_COND(members.size() != member_vals.size());

const List<Variant>::Element *V = member_vals.front();
for (const String &S : members) {
const Variant &value = V->get();
input_dict[S] = value;
V = V->next();
}
}

print_line(breakpoint.print.format(input_dict));
}
}

if (!breakpoint.suspend) {
return;
}
}
}

ScriptLanguage *script_lang = script_debugger->get_break_language();
const String error_str = script_lang ? script_lang->debug_get_error() : "";
Array msg;
Expand Down Expand Up @@ -519,12 +610,13 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
} else if (command == "reload_all_scripts") {
reload_all_scripts = true;
} else if (command == "breakpoint") {
ERR_FAIL_COND(data.size() < 3);
bool set = data[2];
if (set) {
script_debugger->insert_breakpoint(data[1], data[0]);
ERR_FAIL_COND(data.size() < 7);
Breakpoint bp = Breakpoint(data[0], data[1], data[3], data[4], data[5], data[6]);
bool breakpointed = data[2];
if (breakpointed && bp.enabled) {
script_debugger->insert_breakpoint(bp.line, bp.source, bp);
} else {
script_debugger->remove_breakpoint(data[1], data[0]);
script_debugger->remove_breakpoint(bp.line, bp.source);
}

} else if (command == "set_skip_breakpoints") {
Expand Down Expand Up @@ -658,12 +750,13 @@ Error RemoteDebugger::_core_capture(const String &p_cmd, const Array &p_data, bo
} else if (p_cmd == "reload_all_scripts") {
reload_all_scripts = true;
} else if (p_cmd == "breakpoint") {
ERR_FAIL_COND_V(p_data.size() < 3, ERR_INVALID_DATA);
bool set = p_data[2];
if (set) {
script_debugger->insert_breakpoint(p_data[1], p_data[0]);
ERR_FAIL_COND_V(p_data.size() < 7, ERR_INVALID_DATA);
Breakpoint bp = Breakpoint(p_data[0], p_data[1], p_data[3], p_data[4], p_data[5], p_data[6]);
bool breakpointed = p_data[2];
if (breakpointed && bp.enabled) {
script_debugger->insert_breakpoint(bp.line, bp.source, bp);
} else {
script_debugger->remove_breakpoint(p_data[1], p_data[0]);
script_debugger->remove_breakpoint(bp.line, bp.source);
}

} else if (p_cmd == "set_skip_breakpoints") {
Expand Down
4 changes: 2 additions & 2 deletions core/debugger/remote_debugger_peer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,12 @@ Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_po
for (int i = 0; i < tries; i++) {
tcp_client->poll();
if (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED) {
print_verbose("Remote Debugger: Connected!");
WARN_VERBOSE("Remote Debugger: Connected!");
break;
} else {
const int ms = waits[i];
OS::get_singleton()->delay_usec(ms * 1000);
print_verbose("Remote Debugger: Connection failed with status: '" + String::num(tcp_client->get_status()) + "', retrying in " + String::num(ms) + " msec.");
WARN_VERBOSE("Remote Debugger: Connection failed with status: '" + String::num(tcp_client->get_status()) + "', retrying in " + String::num(ms) + " msec.");
}
}

Expand Down
16 changes: 8 additions & 8 deletions core/debugger/script_debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,21 @@ void ScriptDebugger::set_depth(int p_depth) {
depth = p_depth;
}

void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source) {
if (!breakpoints.has(p_line)) {
breakpoints[p_line] = HashSet<StringName>();
void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source, const Breakpoint &p_breakpoint) {
if (!breakpoints.has(p_source)) {
breakpoints[p_source] = HashMap<int, Breakpoint>();
}
breakpoints[p_line].insert(p_source);
breakpoints[p_source].insert(p_line, p_breakpoint);
}

void ScriptDebugger::remove_breakpoint(int p_line, const StringName &p_source) {
if (!breakpoints.has(p_line)) {
if (!breakpoints.has(p_source)) {
return;
}

breakpoints[p_line].erase(p_source);
if (breakpoints[p_line].size() == 0) {
breakpoints.erase(p_line);
breakpoints[p_source].erase(p_line);
if (breakpoints[p_source].size() == 0) {
breakpoints.erase(p_source);
}
}

Expand Down
Loading

0 comments on commit 4d08122

Please sign in to comment.