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 22, 2024
1 parent 024efda commit 36c6ad5
Show file tree
Hide file tree
Showing 32 changed files with 861 additions and 174 deletions.
2 changes: 1 addition & 1 deletion core/core_bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2130,7 +2130,7 @@ bool EngineDebugger::is_skipping_breakpoints() const {

void EngineDebugger::insert_breakpoint(int p_line, const StringName &p_source) {
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);
::EngineDebugger::get_script_debugger()->insert_breakpoint(p_line, p_source, Breakpoint(p_source, p_line));
}

void EngineDebugger::remove_breakpoint(int p_line, const StringName &p_source) {
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
69 changes: 63 additions & 6 deletions core/debugger/script_debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,70 @@
#include "core/templates/rb_map.h"
#include "core/templates/vector.h"

struct Breakpoint {
public:
String source;
int line = 0;
bool enabled = true;
bool suspend = true;
String condition;
String print;

static uint32_t hash(const Breakpoint &p_val) {
uint32_t h = HashMapHasherDefault::hash(p_val.source);
return hash_murmur3_one_32(p_val.line, h);
}
bool operator==(const Breakpoint &p_b) const {
return (line == p_b.line && source == p_b.source);
}

bool operator<(const Breakpoint &p_b) const {
if (line == p_b.line) {
return source < p_b.source;
}
return line < p_b.line;
}

Dictionary serialize() const {
Dictionary dict;
dict["source"] = source;
dict["enabled"] = enabled;
dict["suspend"] = suspend;
dict["line"] = line;
dict["condition"] = condition;
dict["print"] = print;
return dict;
}

static Breakpoint deserialize(const Dictionary &dict) {
Breakpoint bp = Breakpoint();
bp.source = dict["source"];
bp.enabled = dict["enabled"];
bp.suspend = dict["suspend"];
bp.line = dict["line"];
bp.condition = dict["condition"];
bp.print = dict["print"];
return bp;
}

Breakpoint() {}

Breakpoint(const String &p_source, int p_line, bool p_enabled = true, bool p_suspend = true, const String &p_condition = "", const String &p_print = "") {
line = p_line;
source = p_source;
enabled = p_enabled;
suspend = p_suspend;
condition = p_condition;
print = p_print;
}
};

class ScriptDebugger {
typedef ScriptLanguage::StackInfo StackInfo;

HashMap<StringName, HashMap<int, Breakpoint>> breakpoints;
bool skip_breakpoints = false;

HashMap<int, HashSet<StringName>> breakpoints;

static thread_local int lines_left;
static thread_local int depth;
static thread_local ScriptLanguage *break_lang;
Expand All @@ -65,16 +122,16 @@ class ScriptDebugger {
ScriptLanguage *get_break_language() { return break_lang; }
void set_skip_breakpoints(bool p_skip_breakpoints);
bool is_skipping_breakpoints();
void insert_breakpoint(int p_line, const StringName &p_source);
void insert_breakpoint(int p_line, const StringName &p_source, const Breakpoint &p_breakpoint);
void remove_breakpoint(int p_line, const StringName &p_source);
_ALWAYS_INLINE_ bool is_breakpoint(int p_line, const StringName &p_source) const {
if (likely(!breakpoints.has(p_line))) {
if (likely(!breakpoints.has(p_source))) {
return false;
}
return breakpoints[p_line].has(p_source);
return breakpoints[p_source].has(p_line);
}
void clear_breakpoints();
const HashMap<int, HashSet<StringName>> &get_breakpoints() const { return breakpoints; }
const HashMap<StringName, HashMap<int, Breakpoint>> &get_breakpoints() const { return breakpoints; }

void debug(ScriptLanguage *p_lang, bool p_can_continue = true, bool p_is_error_breakpoint = false);
ScriptLanguage *get_break_language() const;
Expand Down
Loading

0 comments on commit 36c6ad5

Please sign in to comment.