diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index db7177118f4..022188dc016 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -84,7 +84,7 @@ - Returns an array of dictionaries representing the current call stack. See also [method print_stack]. + Returns an array of dictionaries representing the current call stack. [codeblock] func _ready(): foo() @@ -99,8 +99,8 @@ [codeblock lang=text] [{function:bar, line:12, source:res://script.gd}, {function:foo, line:9, source:res://script.gd}, {function:_ready, line:6, source:res://script.gd}] [/codeblock] - [b]Note:[/b] This function only works if the running instance is connected to a debugging server (i.e. an editor instance). [method get_stack] will not work in projects exported in release mode, or in projects exported in debug mode if not connected to a debugging server. - [b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will return an empty array. + See also [method print_debug], [method print_stack], and [method Engine.capture_script_backtraces]. + [b]Note:[/b] By default, backtraces are only available in editor builds and debug builds. To enable them for release builds as well, you need to enable [member ProjectSettings.debug/settings/gdscript/always_track_call_stacks]. @@ -197,19 +197,20 @@ Test print At: res://test.gd:15:_process() [/codeblock] - [b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will instead print the thread ID. + See also [method print_stack], [method get_stack], and [method Engine.capture_script_backtraces]. + [b]Note:[/b] By default, backtraces are only available in editor builds and debug builds. To enable them for release builds as well, you need to enable [member ProjectSettings.debug/settings/gdscript/always_track_call_stacks]. - Prints a stack trace at the current code location. See also [method get_stack]. + Prints a stack trace at the current code location. The output in the console may look like the following: [codeblock lang=text] Frame 0 - res://test.gd:16 in function '_process' [/codeblock] - [b]Note:[/b] This function only works if the running instance is connected to a debugging server (i.e. an editor instance). [method print_stack] will not work in projects exported in release mode, or in projects exported in debug mode if not connected to a debugging server. - [b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will instead print the thread ID. + See also [method print_debug], [method get_stack], and [method Engine.capture_script_backtraces]. + [b]Note:[/b] By default, backtraces are only available in editor builds and debug builds. To enable them for release builds as well, you need to enable [member ProjectSettings.debug/settings/gdscript/always_track_call_stacks]. diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 645400f07c1..8360d7f97a7 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -340,13 +340,9 @@ struct GDScriptUtilityFunctionsDefinitions { s += p_args[i]->operator String(); } - if (Thread::get_caller_id() == Thread::get_main_id()) { - ScriptLanguage *script = GDScriptLanguage::get_singleton(); - if (script->debug_get_stack_level_count() > 0) { - s += "\n At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()"; - } - } else { - s += "\n At: Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id()); + ScriptLanguage *script = GDScriptLanguage::get_singleton(); + if (script->debug_get_stack_level_count() > 0) { + s += "\n At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()"; } print_line(s); @@ -356,11 +352,6 @@ struct GDScriptUtilityFunctionsDefinitions { static inline void print_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { DEBUG_VALIDATE_ARG_COUNT(0, 0); - if (Thread::get_caller_id() != Thread::get_main_id()) { - print_line("Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id())); - return; - } - ScriptLanguage *script = GDScriptLanguage::get_singleton(); for (int i = 0; i < script->debug_get_stack_level_count(); i++) { print_line("Frame " + itos(i) + " - " + script->debug_get_stack_level_source(i) + ":" + itos(script->debug_get_stack_level_line(i)) + " in function '" + script->debug_get_stack_level_function(i) + "'"); @@ -371,11 +362,6 @@ struct GDScriptUtilityFunctionsDefinitions { static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { DEBUG_VALIDATE_ARG_COUNT(0, 0); - if (Thread::get_caller_id() != Thread::get_main_id()) { - *r_ret = TypedArray(); - return; - } - ScriptLanguage *script = GDScriptLanguage::get_singleton(); TypedArray ret; for (int i = 0; i < script->debug_get_stack_level_count(); i++) { diff --git a/modules/gdscript/tests/scripts/project.godot b/modules/gdscript/tests/scripts/project.godot index 0757bec5c4e..910a95d72b5 100644 --- a/modules/gdscript/tests/scripts/project.godot +++ b/modules/gdscript/tests/scripts/project.godot @@ -9,6 +9,10 @@ config_version=5 config/name="GDScript Integration Test Suite" +[debug] + +settings/gdscript/always_track_call_stacks=true + [input] test_input_action={ diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index 225bcb3008a..2d775899ee1 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -1,10 +1,28 @@ +@warning_ignore_start("unsafe_call_argument") + class_name Utils + # `assert()` is not evaluated in non-debug builds. Do not use `assert()` # for anything other than testing the `assert()` itself. static func check(condition: Variant) -> void: - if not condition: - printerr("Check failed.") + if condition: + return + + printerr("Check failed. Backtrace (most recent call first):") + for stack: ScriptBacktrace in Engine.capture_script_backtraces(): + if stack.get_language_name() == "GDScript": + var dir: String + for i: int in stack.get_frame_count(): + if i == 0: + dir = stack.get_frame_file(i).trim_suffix("utils.notest.gd") + else: + printerr(" %s:%d @ %s()" % [ + stack.get_frame_file(i).trim_prefix(dir), + stack.get_frame_line(i), + stack.get_frame_function(i), + ]) + break static func get_type(property: Dictionary, is_return: bool = false) -> String: @@ -34,7 +52,11 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String: return type_string(property.type) -static func get_property_signature(property: Dictionary, base: Object = null, is_static: bool = false) -> String: +static func get_property_signature( + property: Dictionary, + base: Object = null, + is_static: bool = false, +) -> String: if property.usage & PROPERTY_USAGE_CATEGORY: return '@export_category("%s")' % str(property.name).c_escape() if property.usage & PROPERTY_USAGE_GROUP: @@ -88,13 +110,17 @@ static func get_human_readable_hint_string(property: Dictionary) -> String: return property.hint_string -static func print_property_extended_info(property: Dictionary, base: Object = null, is_static: bool = false) -> void: +static func print_property_extended_info( + property: Dictionary, + base: Object = null, + is_static: bool = false, +) -> void: print(get_property_signature(property, base, is_static)) print(' hint=%s hint_string="%s" usage=%s class_name=&"%s"' % [ get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"), get_human_readable_hint_string(property).c_escape(), get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""), - property.class_name.c_escape(), + str(property.class_name).c_escape(), ]) @@ -107,7 +133,7 @@ static func get_method_signature(method: Dictionary, is_signal: bool = false) -> var args: Array[Dictionary] = method.args var default_args: Array = method.default_args var mandatory_argc: int = args.size() - default_args.size() - for i in args.size(): + for i: int in args.size(): if i > 0: result += ", " var arg: Dictionary = args[i] @@ -253,7 +279,7 @@ static func get_property_usage_string(usage: int) -> String: result += "PROPERTY_USAGE_DEFAULT|" usage &= ~PROPERTY_USAGE_DEFAULT - for flag in FLAGS: + for flag: Array in FLAGS: if usage & flag[0]: result += flag[1] + "|" usage &= ~flag[0]