diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 6974d98234cc..035f7605e989 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -210,8 +210,55 @@ void ResourceSaver::_bind_methods() { BIND_BITFIELD_FLAG(FLAG_REPLACE_SUBRESOURCE_PATHS); } +////// LOGGER /////// + +void Logger::_bind_methods() { + GDVIRTUAL_BIND(_log_error, "function", "file", "line", "code", "rationale", "editor_notify", "error_type", "script_backtrace"); + GDVIRTUAL_BIND(_log_message, "message", "error"); + BIND_ENUM_CONSTANT(ERROR_TYPE_ERROR); + BIND_ENUM_CONSTANT(ERROR_TYPE_WARNING); + BIND_ENUM_CONSTANT(ERROR_TYPE_SCRIPT); + BIND_ENUM_CONSTANT(ERROR_TYPE_SHADER); +} + +void Logger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const char *p_script_backtrace) { + GDVIRTUAL_CALL(_log_error, p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type, p_script_backtrace); +} + +void Logger::log_message(const String &p_text, bool p_error) { + GDVIRTUAL_CALL(_log_message, p_text, p_error); +} + ////// OS ////// +void OS::LoggerBind::logv(const char *p_format, va_list p_list, bool p_err) { + if (!should_log(p_err)) { + return; + } + + const int static_buf_size = 1024; + char static_buf[static_buf_size]; + char *buf = static_buf; + va_list list_copy; + va_copy(list_copy, p_list); + int len = vsnprintf(buf, static_buf_size, p_format, p_list); + if (len >= static_buf_size) { + buf = (char *)Memory::alloc_static(len + 1); + vsnprintf(buf, len + 1, p_format, list_copy); + } + va_end(list_copy); + + String str; + str.parse_utf8(buf, len); + for (uint32_t i = 0; i < loggers.size(); i++) { + loggers[i]->log_message(str, p_err); + } + + if (len >= static_buf_size) { + Memory::free_static(buf); + } +} + PackedByteArray OS::get_entropy(int p_bytes) { PackedByteArray pba; pba.resize(p_bytes); @@ -628,6 +675,22 @@ String OS::get_unique_id() const { return ::OS::get_singleton()->get_unique_id(); } +void OS::add_logger(Ref p_logger) { + ERR_FAIL_COND(p_logger.is_null()); + if (!logger_bind) { + logger_bind = memnew(LoggerBind); + ::OS::get_singleton()->add_logger(logger_bind); + } + + ERR_FAIL_COND(logger_bind->loggers.find(p_logger) != -1); + logger_bind->loggers.push_back(p_logger); +} + +void OS::remove_logger(Ref p_logger) { + ERR_FAIL_COND(p_logger.is_null()); + logger_bind->loggers.erase(p_logger); +} + OS *OS::singleton = nullptr; void OS::_bind_methods() { @@ -734,6 +797,9 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_granted_permissions"), &OS::get_granted_permissions); ClassDB::bind_method(D_METHOD("revoke_granted_permissions"), &OS::revoke_granted_permissions); + ClassDB::bind_method(D_METHOD("add_logger", "logger"), &OS::add_logger); + ClassDB::bind_method(D_METHOD("remove_logger", "logger"), &OS::remove_logger); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "low_processor_usage_mode"), "set_low_processor_usage_mode", "is_in_low_processor_usage_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "low_processor_usage_mode_sleep_usec"), "set_low_processor_usage_mode_sleep_usec", "get_low_processor_usage_mode_sleep_usec"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "delta_smoothing"), "set_delta_smoothing", "is_delta_smoothing_enabled"); @@ -764,6 +830,16 @@ void OS::_bind_methods() { BIND_ENUM_CONSTANT(STD_HANDLE_UNKNOWN); } +OS::OS() { + singleton = this; +} + +OS::~OS() { + if (logger_bind) { + logger_bind->clear(); + } +} + ////// Geometry2D ////// Geometry2D *Geometry2D::singleton = nullptr; diff --git a/core/core_bind.h b/core/core_bind.h index b96dc56b0dc0..2f4a15909573 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -120,11 +120,52 @@ class ResourceSaver : public Object { ResourceSaver() { singleton = this; } }; +class Logger : public RefCounted { + GDCLASS(Logger, RefCounted); + +public: + enum ErrorType { + ERROR_TYPE_ERROR, + ERROR_TYPE_WARNING, + ERROR_TYPE_SCRIPT, + ERROR_TYPE_SHADER + }; + +protected: + GDVIRTUAL2(_log_message, String, bool); + GDVIRTUAL8(_log_error, String, String, int, String, String, bool, int, String); + static void _bind_methods(); + +public: + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERROR_TYPE_ERROR, const char *p_script_backtrace = nullptr); + virtual void log_message(const String &p_text, bool p_error); +}; + class OS : public Object { GDCLASS(OS, Object); mutable HashMap feature_cache; + class LoggerBind : public ::Logger { + public: + LocalVector> loggers; + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR, const char *p_script_backtrace = nullptr) override { + if (!should_log(true)) { + return; + } + + for (uint32_t i = 0; i < loggers.size(); i++) { + loggers[i]->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, core_bind::Logger::ErrorType(p_type), p_script_backtrace); + } + } + + virtual void logv(const char *p_format, va_list p_list, bool p_err) override _PRINTF_FORMAT_ATTRIBUTE_2_0; + + void clear() { loggers.clear(); } + }; + + LoggerBind *logger_bind = nullptr; + protected: static void _bind_methods(); static OS *singleton; @@ -275,9 +316,13 @@ class OS : public Object { Vector get_granted_permissions() const; void revoke_granted_permissions(); + void add_logger(Ref p_logger); + void remove_logger(Ref p_logger); + static OS *get_singleton() { return singleton; } - OS() { singleton = this; } + OS(); + ~OS(); }; class Geometry2D : public Object { @@ -654,6 +699,7 @@ class EngineDebugger : public Object { } // namespace core_bind +VARIANT_ENUM_CAST(core_bind::Logger::ErrorType); VARIANT_ENUM_CAST(core_bind::ResourceLoader::ThreadLoadStatus); VARIANT_ENUM_CAST(core_bind::ResourceLoader::CacheMode); diff --git a/core/error/error_macros.cpp b/core/error/error_macros.cpp index a2369992e608..ac085d0fbeae 100644 --- a/core/error/error_macros.cpp +++ b/core/error/error_macros.cpp @@ -31,6 +31,7 @@ #include "error_macros.h" #include "core/io/logger.h" +#include "core/object/script_language.h" #include "core/os/os.h" #include "core/string/ustring.h" @@ -90,7 +91,8 @@ void _err_print_error(const char *p_function, const char *p_file, int p_line, co // Main error printing function. void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_message, bool p_editor_notify, ErrorHandlerType p_type) { if (OS::get_singleton()) { - OS::get_singleton()->print_error(p_function, p_file, p_line, p_error, p_message, p_editor_notify, (Logger::ErrorType)p_type); + String script_backtrace = ScriptServer::get_current_script_backtrace(); + OS::get_singleton()->print_error(p_function, p_file, p_line, p_error, p_message, p_editor_notify, (Logger::ErrorType)p_type, script_backtrace.utf8().get_data()); } else { // Fallback if errors happen before OS init or after it's destroyed. const char *err_details = (p_message && *p_message) ? p_message : p_error; diff --git a/core/io/logger.cpp b/core/io/logger.cpp index f6a61282044b..616b09696157 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -55,7 +55,7 @@ void Logger::set_flush_stdout_on_print(bool value) { _flush_stdout_on_print = value; } -void Logger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { +void Logger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const char *p_script_backtrace) { if (!should_log(true)) { return; } @@ -87,7 +87,12 @@ void Logger::log_error(const char *p_function, const char *p_file, int p_line, c } logf_error("%s: %s\n", err_type, err_details); - logf_error(" at: %s (%s:%i)\n", p_function, p_file, p_line); + + if (p_script_backtrace && p_script_backtrace[0] != 0) { + logf_error(" at: %s (%s:%i)\n%s\n", p_function, p_file, p_line, p_script_backtrace); + } else { + logf_error(" at: %s (%s:%i)\n", p_function, p_file, p_line); + } } void Logger::logf(const char *p_format, ...) { @@ -261,13 +266,13 @@ void CompositeLogger::logv(const char *p_format, va_list p_list, bool p_err) { } } -void CompositeLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { +void CompositeLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const char *p_script_backtrace) { if (!should_log(true)) { return; } for (int i = 0; i < loggers.size(); ++i) { - loggers[i]->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type); + loggers[i]->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type, p_script_backtrace); } } diff --git a/core/io/logger.h b/core/io/logger.h index e55f73f6305f..9c2f689d0ccf 100644 --- a/core/io/logger.h +++ b/core/io/logger.h @@ -56,7 +56,7 @@ class Logger { static void set_flush_stdout_on_print(bool value); virtual void logv(const char *p_format, va_list p_list, bool p_err) _PRINTF_FORMAT_ATTRIBUTE_2_0 = 0; - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR); + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR, const char *p_script_backtrace = nullptr); void logf(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; void logf_error(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; @@ -103,7 +103,7 @@ class CompositeLogger : public Logger { explicit CompositeLogger(const Vector &p_loggers); virtual void logv(const char *p_format, va_list p_list, bool p_err) override _PRINTF_FORMAT_ATTRIBUTE_2_0; - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type = ERR_ERROR) override; + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type = ERR_ERROR, const char *p_script_backtrace = nullptr) override; void add_logger(Logger *p_logger); diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index 4ef53dba1dff..b245e29b60df 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -518,6 +518,21 @@ void ScriptServer::save_global_classes() { ProjectSettings::get_singleton()->store_global_class_list(gcarr); } +String ScriptServer::get_current_script_backtrace() { + String trace; + for (int si = 0; si < get_language_count(); si++) { + ScriptLanguage *sl = get_language(si); + Vector stack = sl->debug_get_current_stack_info(); + if (stack.size()) { + trace += "stack_language: " + sl->get_name(); + for (int i = 0; i < stack.size(); i++) { + trace += "\n" + itos(i) + ": " + stack[i].func + " (" + stack[i].file + " : " + itos(stack[i].line) + ")"; + } + } + } + return trace; +} + //////////////////// ScriptCodeCompletionCache *ScriptCodeCompletionCache::singleton = nullptr; diff --git a/core/object/script_language.h b/core/object/script_language.h index 6ceeb4287512..c362ea2f9d46 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -98,6 +98,8 @@ class ScriptServer { static void get_inheriters_list(const StringName &p_base_type, List *r_classes); static void save_global_classes(); + static String get_current_script_backtrace(); + static void init_languages(); static void finish_languages(); static bool are_languages_initialized(); diff --git a/core/os/os.cpp b/core/os/os.cpp index c161b2212f40..73fcb396e61b 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -84,13 +84,13 @@ String OS::get_identifier() const { return get_name().to_lower(); } -void OS::print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, Logger::ErrorType p_type) { +void OS::print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, Logger::ErrorType p_type, const char *p_script_backtrace) { if (!_stderr_enabled) { return; } if (_logger) { - _logger->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type); + _logger->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type, p_script_backtrace); } } diff --git a/core/os/os.h b/core/os/os.h index 15c5715dddc1..fc1112a7fbf8 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -111,7 +111,6 @@ class OS { bool _separate_thread_render = false; // Functions used by Main to initialize/deinitialize the OS. - void add_logger(Logger *p_logger); virtual void initialize() = 0; virtual void initialize_joypads() = 0; @@ -142,7 +141,7 @@ class OS { virtual Vector get_video_adapter_driver_info() const = 0; virtual bool get_user_prefers_integrated_gpu() const { return false; } - void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, Logger::ErrorType p_type = Logger::ERR_ERROR); + void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, Logger::ErrorType p_type = Logger::ERR_ERROR, const char *p_script_backtrace = nullptr); void print(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; void print_rich(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; void printerr(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; @@ -345,6 +344,8 @@ class OS { virtual Error setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path); + void add_logger(Logger *p_logger); + enum PreferredTextureFormat { PREFERRED_TEXTURE_FORMAT_S3TC_BPTC, PREFERRED_TEXTURE_FORMAT_ETC2_ASTC diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 568694bdf4d0..78ff3237ba03 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -244,6 +244,7 @@ void register_core_types() { GDREGISTER_CLASS(core_bind::Thread); GDREGISTER_CLASS(core_bind::Mutex); GDREGISTER_CLASS(core_bind::Semaphore); + GDREGISTER_VIRTUAL_CLASS(core_bind::Logger); GDREGISTER_CLASS(XMLParser); GDREGISTER_CLASS(JSON); diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index ec9492ac5d1c..6541e60662b2 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -32,6 +32,7 @@ #include "core/io/marshalls.h" #include "core/object/ref_counted.h" +#include "core/object/script_language.h" #include "core/os/os.h" #include "core/templates/oa_hash_map.h" #include "core/templates/rid.h" @@ -1236,6 +1237,10 @@ bool VariantUtilityFunctions::is_same(const Variant &p_a, const Variant &p_b) { return p_a.identity_compare(p_b); } +String VariantUtilityFunctions::script_backtrace() { + return ScriptServer::get_current_script_backtrace(); +} + #ifdef DEBUG_METHODS_ENABLED #define VCALLR *ret = p_func(VariantCasterAndValidate

::cast(p_args, Is, r_error)...) #define VCALL p_func(VariantCasterAndValidate

::cast(p_args, Is, r_error)...) @@ -1855,6 +1860,8 @@ void Variant::_register_variant_utility_functions() { FUNCBINDR(rid_from_int64, sarray("base"), Variant::UTILITY_FUNC_TYPE_GENERAL); FUNCBINDR(is_same, sarray("a", "b"), Variant::UTILITY_FUNC_TYPE_GENERAL); + + FUNCBINDR(script_backtrace, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL); } void Variant::_unregister_variant_utility_functions() { diff --git a/core/variant/variant_utility.h b/core/variant/variant_utility.h index 75cde4942b5f..2e0053bd66bf 100644 --- a/core/variant/variant_utility.h +++ b/core/variant/variant_utility.h @@ -154,6 +154,7 @@ struct VariantUtilityFunctions { static uint64_t rid_allocate_id(); static RID rid_from_int64(uint64_t p_base); static bool is_same(const Variant &p_a, const Variant &p_b); + static String script_backtrace(); }; #endif // VARIANT_UTILITY_H diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index fb2dfa3dde55..2e49f727887e 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -1160,6 +1160,12 @@ A type-safe version of [method round], returning an [int]. + + + + Returns a multiple line text with the current call stack information of running scripts. + + diff --git a/doc/classes/Logger.xml b/doc/classes/Logger.xml new file mode 100644 index 000000000000..e4b97b9a0589 --- /dev/null +++ b/doc/classes/Logger.xml @@ -0,0 +1,53 @@ + + + + Custom logger to receive messages from the internal error/warning stream. + + + Custom logger to receive messages from the internal error/warning stream. Loggers are registered via [method OS.add_logger]. + + + + + + + + + + + + + + + + Log an error. The error contains the function, file and line it originated from as well as either the [param code] that generate it or the [param rationale]. + The type of error is described in the [param error_type] enumeration. + Additionally, in debug builds, the [param script_backtrace] will be made available to this function. + Keep in mind that this function may be called from separate threads, so its up to the implementer to do the appropriate locking if required. + + + + + + + + Log a generic message. If [param error] is true, then this message was meant to be send to [b]stderr[/b]. + Keep in mind that this function may be called from separate threads, so its up to the implementer to do the appropriate locking if required. + + + + + + The message received is an error. + + + The message received is a warning. + + + The message received is a script error. + + + The message received is a shader error. + + + diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 72857bae318e..9d864f4e63e3 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -11,6 +11,13 @@ https://godotengine.org/asset-library/asset/2789 + + + + + Add a custom logger to intercept the internal message stream. + + @@ -743,6 +750,13 @@ [b]Note:[/b] On exported Windows builds, run the console wrapper executable to access the terminal. If standard input is console, calling this method without console wrapped will freeze permanently. If standard input is pipe or file, it can be used without console wrapper. If you need a single executable with full console support, use a custom build compiled with the [code]windows_subsystem=console[/code] flag. + + + + + Remove a custom logger added by [method add_logger]. + + diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index a70be9326e8b..05ee3ae7231b 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -1031,7 +1031,7 @@ String OS_Unix::get_executable_path() const { #endif } -void UnixTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { +void UnixTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const char *p_script_backtrace) { if (!should_log(true)) { return; } @@ -1056,6 +1056,8 @@ void UnixTerminalLogger::log_error(const char *p_function, const char *p_file, i const char *magenta_bold = tty ? "\E[1;35m" : ""; const char *cyan = tty ? "\E[0;96m" : ""; const char *cyan_bold = tty ? "\E[1;36m" : ""; + const char *blue = tty ? "\E[0;94m" : ""; + //const char *blue_bold = tty ? "\E[1;34m" : ""; const char *reset = tty ? "\E[0m" : ""; switch (p_type) { @@ -1077,6 +1079,10 @@ void UnixTerminalLogger::log_error(const char *p_function, const char *p_file, i logf_error("%s at: %s (%s:%i)%s\n", gray, p_function, p_file, p_line, reset); break; } + + if (p_script_backtrace && p_script_backtrace[0] != 0) { + logf_error("%s%s\n", blue, p_script_backtrace); + } } UnixTerminalLogger::~UnixTerminalLogger() {} diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index fa6eaef6a4e3..7c8df2adf9d9 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -111,7 +111,7 @@ class OS_Unix : public OS { class UnixTerminalLogger : public StdLogger { public: - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) override; + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR, const char *p_script_backtrace = nullptr) override; virtual ~UnixTerminalLogger(); }; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index db5a3b65ccb0..2e1ca4611547 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -2342,8 +2342,6 @@ void GDScriptLanguage::finish() { } finishing = true; - _call_stack.free(); - // Clear the cache before parsing the script_list GDScriptCache::clear(); @@ -2935,7 +2933,19 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b return c->identifier != nullptr ? String(c->identifier->name) : String(); } -thread_local GDScriptLanguage::CallStack GDScriptLanguage::_call_stack; +thread_local GDScriptLanguage::CallLevel *GDScriptLanguage::_call_stack = nullptr; +thread_local uint32_t GDScriptLanguage::_call_stack_size = 0; + +GDScriptLanguage::CallLevel *GDScriptLanguage::_get_stack_level(uint32_t p_level) { + ERR_FAIL_UNSIGNED_INDEX_V(p_level, _call_stack_size, nullptr); + CallLevel *level = _call_stack; // Start from top + uint32_t level_index = 0; + while (p_level > level_index) { + level_index++; + level = level->prev; + } + return level; +} GDScriptLanguage::GDScriptLanguage() { ERR_FAIL_COND(singleton); @@ -2960,14 +2970,7 @@ GDScriptLanguage::GDScriptLanguage() { #endif int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1"), 1024); - - if (EngineDebugger::is_active()) { - //debugging enabled! - - _debug_max_call_stack = dmcs; - } else { - _debug_max_call_stack = 0; - } + _debug_max_call_stack = dmcs; #ifdef DEBUG_ENABLED GLOBAL_DEF("debug/gdscript/warnings/enable", true); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index ed04482f4811..fb44b7be36ab 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -435,28 +435,18 @@ class GDScriptLanguage : public ScriptLanguage { GDScriptInstance *instance = nullptr; int *ip = nullptr; int *line = nullptr; + CallLevel *prev = nullptr; // Reverse linked list (stack). }; static thread_local int _debug_parse_err_line; static thread_local String _debug_parse_err_file; static thread_local String _debug_error; - struct CallStack { - CallLevel *levels = nullptr; - int stack_pos = 0; - - void free() { - if (levels) { - memdelete(levels); - levels = nullptr; - } - } - ~CallStack() { - free(); - } - }; - static thread_local CallStack _call_stack; - int _debug_max_call_stack = 0; + static thread_local CallLevel *_call_stack; + static thread_local uint32_t _call_stack_size; + uint32_t _debug_max_call_stack = 0; + + static CallLevel *_get_stack_level(uint32_t p_level); void _add_global(const StringName &p_name, const Variant &p_value); void _remove_global(const StringName &p_name); @@ -488,53 +478,64 @@ class GDScriptLanguage : public ScriptLanguage { bool debug_break(const String &p_error, bool p_allow_continue = true); bool debug_break_parse(const String &p_file, int p_line, const String &p_error); - _FORCE_INLINE_ void enter_function(GDScriptInstance *p_instance, GDScriptFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) { - if (unlikely(_call_stack.levels == nullptr)) { - _call_stack.levels = memnew_arr(CallLevel, _debug_max_call_stack + 1); - } - - if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) { - EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() + 1); - } - - if (_call_stack.stack_pos >= _debug_max_call_stack) { + _FORCE_INLINE_ void enter_function(CallLevel *call_level, GDScriptInstance *p_instance, GDScriptFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) { + if (unlikely(_call_stack_size >= _debug_max_call_stack)) { //stack overflow _debug_error = vformat("Stack overflow (stack size: %s). Check for infinite recursion in your script.", _debug_max_call_stack); - EngineDebugger::get_script_debugger()->debug(this); + if (EngineDebugger::get_script_debugger()) { + EngineDebugger::get_script_debugger()->debug(this); + } else { + CRASH_NOW_MSG(_debug_error); + } return; } - _call_stack.levels[_call_stack.stack_pos].stack = p_stack; - _call_stack.levels[_call_stack.stack_pos].instance = p_instance; - _call_stack.levels[_call_stack.stack_pos].function = p_function; - _call_stack.levels[_call_stack.stack_pos].ip = p_ip; - _call_stack.levels[_call_stack.stack_pos].line = p_line; - _call_stack.stack_pos++; + if (EngineDebugger::get_script_debugger() && EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) { + EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() + 1); + } + + call_level->prev = _call_stack; + _call_stack = call_level; + call_level->stack = p_stack; + call_level->instance = p_instance; + call_level->function = p_function; + call_level->ip = p_ip; + call_level->line = p_line; + _call_stack_size++; } _FORCE_INLINE_ void exit_function() { - if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) { - EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() - 1); - } - - if (_call_stack.stack_pos == 0) { - _debug_error = "Stack Underflow (Engine Bug)"; - EngineDebugger::get_script_debugger()->debug(this); + if (unlikely(_call_stack_size == 0)) { + if (EngineDebugger::get_script_debugger()) { + _debug_error = "Stack Underflow (Engine Bug)"; + EngineDebugger::get_script_debugger()->debug(this); + } else { + ERR_PRINT("Stack underflow! (Engine Bug)"); + } return; } - _call_stack.stack_pos--; + _call_stack_size--; + _call_stack = _call_stack->prev; + + if (EngineDebugger::get_script_debugger() && EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) { + EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() - 1); + } } virtual Vector debug_get_current_stack_info() override { Vector csi; - csi.resize(_call_stack.stack_pos); - for (int i = 0; i < _call_stack.stack_pos; i++) { - csi.write[_call_stack.stack_pos - i - 1].line = _call_stack.levels[i].line ? *_call_stack.levels[i].line : 0; - if (_call_stack.levels[i].function) { - csi.write[_call_stack.stack_pos - i - 1].func = _call_stack.levels[i].function->get_name(); - csi.write[_call_stack.stack_pos - i - 1].file = _call_stack.levels[i].function->get_script()->get_script_path(); + csi.resize(_call_stack_size); + CallLevel *cl = _call_stack; + uint32_t idx = 0; + while (cl) { + csi.write[idx].line = *cl->line; + if (cl->function) { + csi.write[idx].func = cl->function->get_name(); + csi.write[idx].file = cl->function->get_script()->get_script_path(); } + idx++; + cl = cl->prev; } return csi; } diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 4b4eb1af148d..6b92fc73df05 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -287,7 +287,7 @@ int GDScriptLanguage::debug_get_stack_level_count() const { return 1; } - return _call_stack.stack_pos; + return _call_stack_size; } int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { @@ -295,11 +295,9 @@ int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { return _debug_parse_err_line; } - ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, -1); + ERR_FAIL_INDEX_V(p_level, (int)_call_stack_size, -1); - int l = _call_stack.stack_pos - p_level - 1; - - return *(_call_stack.levels[l].line); + return *(_get_stack_level(p_level)->line); } String GDScriptLanguage::debug_get_stack_level_function(int p_level) const { @@ -307,9 +305,9 @@ String GDScriptLanguage::debug_get_stack_level_function(int p_level) const { return ""; } - ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, ""); - int l = _call_stack.stack_pos - p_level - 1; - return _call_stack.levels[l].function->get_name(); + ERR_FAIL_INDEX_V(p_level, (int)_call_stack_size, ""); + + return _get_stack_level(p_level)->function->get_name(); } String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { @@ -317,9 +315,8 @@ String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { return _debug_parse_err_file; } - ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, ""); - int l = _call_stack.stack_pos - p_level - 1; - return _call_stack.levels[l].function->get_source(); + ERR_FAIL_INDEX_V(p_level, (int)_call_stack_size, ""); + return _get_stack_level(p_level)->function->get_source(); } void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List *p_locals, List *p_values, int p_max_subitems, int p_max_depth) { @@ -327,17 +324,18 @@ void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List *p return; } - ERR_FAIL_INDEX(p_level, _call_stack.stack_pos); - int l = _call_stack.stack_pos - p_level - 1; + ERR_FAIL_INDEX(p_level, (int)_call_stack_size); + + CallLevel *cl = _get_stack_level(p_level); - GDScriptFunction *f = _call_stack.levels[l].function; + GDScriptFunction *f = cl->function; List> locals; - f->debug_get_stack_member_state(*_call_stack.levels[l].line, &locals); + f->debug_get_stack_member_state(*cl->line, &locals); for (const Pair &E : locals) { p_locals->push_back(E.first); - p_values->push_back(_call_stack.levels[l].stack[E.second]); + p_values->push_back(cl->stack[E.second]); } } @@ -346,10 +344,10 @@ void GDScriptLanguage::debug_get_stack_level_members(int p_level, List * return; } - ERR_FAIL_INDEX(p_level, _call_stack.stack_pos); - int l = _call_stack.stack_pos - p_level - 1; + ERR_FAIL_INDEX(p_level, (int)_call_stack_size); + CallLevel *cl = _get_stack_level(p_level); - GDScriptInstance *instance = _call_stack.levels[l].instance; + GDScriptInstance *instance = cl->instance; if (!instance) { return; @@ -371,10 +369,9 @@ ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) { return nullptr; } - ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, nullptr); + ERR_FAIL_INDEX_V(p_level, (int)_call_stack_size, nullptr); - int l = _call_stack.stack_pos - p_level - 1; - ScriptInstance *instance = _call_stack.levels[l].instance; + ScriptInstance *instance = _get_stack_level(p_level)->instance; return instance; } diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index fb454490cf41..f63263da8a60 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -623,9 +623,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED - if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->enter_function(p_instance, this, stack, &ip, &line); - } + GDScriptLanguage::CallLevel call_level; + GDScriptLanguage::get_singleton()->enter_function(&call_level, p_instance, this, stack, &ip, &line); #define GD_ERR_BREAK(m_cond) \ { \ @@ -3873,9 +3872,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a // If that is the case then we exit the function as normal. Otherwise we postpone it until the last `await` is completed. // This ensures the call stack can be properly shown when using `await`, showing what resumed the function. if (!p_state || awaited) { - if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->exit_function(); - } + GDScriptLanguage::get_singleton()->exit_function(); #endif // Free stack, except reserved addresses. diff --git a/platform/ios/ios_terminal_logger.h b/platform/ios/ios_terminal_logger.h index 7f0bc37a077d..4b9c9f3c14a3 100644 --- a/platform/ios/ios_terminal_logger.h +++ b/platform/ios/ios_terminal_logger.h @@ -37,7 +37,7 @@ class IOSTerminalLogger : public StdLogger { public: - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) override; + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR, const char *p_script_backtrace = nullptr) override; }; #endif // IOS_ENABLED diff --git a/platform/ios/ios_terminal_logger.mm b/platform/ios/ios_terminal_logger.mm index b4c9821cdc79..00199faf6b12 100644 --- a/platform/ios/ios_terminal_logger.mm +++ b/platform/ios/ios_terminal_logger.mm @@ -34,7 +34,7 @@ #include -void IOSTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { +void IOSTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const char *p_script_backtrace) { if (!should_log(true)) { return; } @@ -46,27 +46,31 @@ err_details = p_code; } + bool has_script_backtrace = p_script_backtrace && p_script_backtrace[0] != 0; + const char *newline = has_script_backtrace ? "\n" : ""; + const char *script = has_script_backtrace ? p_script_backtrace : ""; + switch (p_type) { case ERR_WARNING: os_log_info(OS_LOG_DEFAULT, - "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); + "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)%s%s", + err_details, p_function, p_file, p_line, newline, script); break; case ERR_SCRIPT: os_log_error(OS_LOG_DEFAULT, - "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); + "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)%s%s", + err_details, p_function, p_file, p_line, newline, script); break; case ERR_SHADER: os_log_error(OS_LOG_DEFAULT, - "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); + "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)%s%s", + err_details, p_function, p_file, p_line, newline, script); break; case ERR_ERROR: default: os_log_error(OS_LOG_DEFAULT, - "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); + "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)%s%s", + err_details, p_function, p_file, p_line, newline, script); break; } } diff --git a/platform/macos/macos_terminal_logger.h b/platform/macos/macos_terminal_logger.h index 0120240c4f24..c93010e2085c 100644 --- a/platform/macos/macos_terminal_logger.h +++ b/platform/macos/macos_terminal_logger.h @@ -37,7 +37,7 @@ class MacOSTerminalLogger : public StdLogger { public: - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) override; + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR, const char *p_script_backtrace = nullptr) override; }; #endif // MACOS_ENABLED diff --git a/platform/macos/macos_terminal_logger.mm b/platform/macos/macos_terminal_logger.mm index 44f37dc3961f..468aa88e8cdc 100644 --- a/platform/macos/macos_terminal_logger.mm +++ b/platform/macos/macos_terminal_logger.mm @@ -34,7 +34,7 @@ #include -void MacOSTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { +void MacOSTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const char *p_script_backtrace) { if (!should_log(true)) { return; } @@ -77,6 +77,10 @@ logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); break; } + + if (p_script_backtrace && p_script_backtrace[0] != 0) { + logf_error("\E[0;94m%s\n", p_script_backtrace); + } } #endif // MACOS_ENABLED diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp index e25c612008c0..fe6addf1a6c3 100644 --- a/platform/windows/windows_terminal_logger.cpp +++ b/platform/windows/windows_terminal_logger.cpp @@ -74,7 +74,7 @@ void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_er #endif } -void WindowsTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { +void WindowsTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const char *p_script_backtrace) { if (!should_log(true)) { return; } @@ -152,6 +152,11 @@ void WindowsTerminalLogger::log_error(const char *p_function, const char *p_file logf_error("%s (%s:%i)\n", p_function, p_file, p_line); } + if (p_script_backtrace && p_script_backtrace[0] != 0) { + SetConsoleTextAttribute(hCon, FOREGROUND_BLUE | FOREGROUND_INTENSITY); + logf_error("%s\n", p_script_backtrace); + } + SetConsoleTextAttribute(hCon, sbi.wAttributes); } } diff --git a/platform/windows/windows_terminal_logger.h b/platform/windows/windows_terminal_logger.h index 60d82bb93538..4d876d560815 100644 --- a/platform/windows/windows_terminal_logger.h +++ b/platform/windows/windows_terminal_logger.h @@ -38,7 +38,7 @@ class WindowsTerminalLogger : public StdLogger { public: virtual void logv(const char *p_format, va_list p_list, bool p_err) override; - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) override; + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR, const char *p_script_backtrace = nullptr) override; virtual ~WindowsTerminalLogger(); };