diff options
Diffstat (limited to 'modules/gdscript')
17 files changed, 292 insertions, 95 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 923b2fe30d..0b7e4e50e6 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -4,7 +4,7 @@ Built-in GDScript functions. </brief_description> <description> - A list of GDScript-specific utility functions accessed in any script. + A list of GDScript-specific utility functions and annotations accessible from any script. For the list of the global functions and constants see [@GlobalScope]. </description> <tutorials> @@ -20,7 +20,7 @@ <description> Returns a [Color] constructed from red ([param r8]), green ([param g8]), blue ([param b8]), and optionally alpha ([param a8]) integer channels, each divided by [code]255.0[/code] for their final value. [codeblock] - var red = Color8(255, 0, 0) # Same as Color(1, 0, 0) + var red = Color8(255, 0, 0) # Same as Color(1, 0, 0). var dark_blue = Color8(0, 0, 51) # Same as Color(0, 0, 0.2). var my_color = Color8(306, 255, 0, 102) # Same as Color(1.2, 1, 0, 0.4). [/codeblock] @@ -37,10 +37,10 @@ [codeblock] # Imagine we always want speed to be between 0 and 20. var speed = -10 - assert(speed < 20) # True, the program will continue - assert(speed >= 0) # False, the program will stop - assert(speed >= 0 and speed < 20) # You can also combine the two conditional statements in one check - assert(speed < 20, "the speed limit is 20") # Show a message + assert(speed < 20) # True, the program will continue. + assert(speed >= 0) # False, the program will stop. + assert(speed >= 0 and speed < 20) # You can also combine the two conditional statements in one check. + assert(speed < 20, "the speed limit is 20") # Show a message. [/codeblock] </description> </method> @@ -140,7 +140,7 @@ <param index="0" name="path" type="String" /> <description> Returns a [Resource] from the filesystem located at the absolute [param path]. Unless it's already referenced elsewhere (such as in another script or in the scene), the resource is loaded from disk on function call, which might cause a slight delay, especially when loading large scenes. To avoid unnecessary delays when loading something multiple times, either store the resource in a variable or use [method preload]. - [b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the FileSystem dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. + [b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the FileSystem dock and choosing "Copy Path", or by dragging the file from the FileSystem dock into the current script. [codeblock] # Load a scene called "main" located in the root of the project directory and cache it in a variable. var main = load("res://main.tscn") # main will contain a PackedScene resource. @@ -155,7 +155,7 @@ <param index="0" name="path" type="String" /> <description> Returns a [Resource] from the filesystem located at [param path]. During run-time, the resource is loaded when the script is being parsed. This function effectively acts as a reference to that resource. Note that this function requires [param path] to be a constant [String]. If you want to load a resource from a dynamic/variable path, use [method load]. - [b]Note:[/b] Resource paths can be obtained by right clicking on a resource in the Assets Panel and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. + [b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the Assets Panel and choosing "Copy Path", or by dragging the file from the FileSystem dock into the current script. [codeblock] # Create instance of a scene. var diamond = preload("res://diamond.tscn").instantiate() @@ -259,10 +259,12 @@ <annotation name="@export"> <return type="void" /> <description> - Mark the following property as exported (editable in the Inspector dock and saved to disk). To control the type of the exported property use the type hint notation. + Mark the following property as exported (editable in the Inspector dock and saved to disk). To control the type of the exported property, use the type hint notation. [codeblock] + @export var string = "" @export var int_number = 5 @export var float_number: float = 5 + @export var image : Image [/codeblock] </description> </annotation> @@ -273,20 +275,20 @@ Define a new category for the following exported properties. This helps to organize properties in the Inspector dock. See also [constant PROPERTY_USAGE_CATEGORY]. [codeblock] - @export_category("My Properties") - @export var number = 3 - @export var string = "" + @export_category("Statistics") + @export var hp = 30 + @export var speed = 1.25 [/codeblock] - [b]Note:[/b] Categories in the property list are supposed to indicate different base types, so the use of this annotation is not encouraged. See [annotation @export_group] and [annotation @export_subgroup] instead. + [b]Note:[/b] Categories in the Inspector dock's list usually divide properties coming from different classes (Node, Node2D, Sprite, etc.). For better clarity, it's recommended to use [annotation @export_group] and [annotation @export_subgroup], instead. </description> </annotation> <annotation name="@export_color_no_alpha"> <return type="void" /> <description> - Export a [Color] property without transparency (its alpha fixed as [code]1.0[/code]). + Export a [Color] property without allowing its transparency ([member Color.a]) to be edited. See also [constant PROPERTY_HINT_COLOR_NO_ALPHA]. [codeblock] - @export_color_no_alpha var modulate_color: Color + @export_color_no_alpha var dye_color : Color [/codeblock] </description> </annotation> @@ -296,7 +298,7 @@ Export a [String] property as a path to a directory. The path will be limited to the project folder and its subfolders. See [annotation @export_global_dir] to allow picking from the entire filesystem. See also [constant PROPERTY_HINT_DIR]. [codeblock] - @export_dir var sprite_folder: String + @export_dir var sprite_folder_path: String [/codeblock] </description> </annotation> @@ -343,8 +345,8 @@ If [param filter] is provided, only matching files will be available for picking. See also [constant PROPERTY_HINT_FILE]. [codeblock] - @export_file var sound_effect_file: String - @export_file("*.txt") var notes_file: String + @export_file var sound_effect_path: String + @export_file("*.txt") var notes_path: String [/codeblock] </description> </annotation> @@ -436,10 +438,10 @@ <annotation name="@export_global_dir"> <return type="void" /> <description> - Export a [String] property as a path to a directory. The path can be picked from the entire filesystem. See [annotation @export_dir] to limit it to the project folder and its subfolders. + Export a [String] property as an absolute path to a directory. The path can be picked from the entire filesystem. See [annotation @export_dir] to limit it to the project folder and its subfolders. See also [constant PROPERTY_HINT_GLOBAL_DIR]. [codeblock] - @export_global_dir var sprite_folder: String + @export_global_dir var sprite_folder_path: String [/codeblock] </description> </annotation> @@ -447,12 +449,12 @@ <return type="void" /> <param index="0" name="filter" type="String" default="""" /> <description> - Export a [String] property as a path to a file. The path can be picked from the entire filesystem. See [annotation @export_file] to limit it to the project folder and its subfolders. + Export a [String] property as an absolute path to a file. The path can be picked from the entire filesystem. See [annotation @export_file] to limit it to the project folder and its subfolders. If [param filter] is provided, only matching files will be available for picking. See also [constant PROPERTY_HINT_GLOBAL_FILE]. [codeblock] - @export_global_file var sound_effect_file: String - @export_global_file("*.txt") var notes_file: String + @export_global_file var sound_effect_path: String + @export_global_file("*.txt") var notes_path: String [/codeblock] </description> </annotation> @@ -466,13 +468,13 @@ Groups cannot be nested, use [annotation @export_subgroup] to add subgroups within groups. See also [constant PROPERTY_USAGE_GROUP]. [codeblock] - @export_group("My Properties") - @export var number = 3 - @export var string = "" + @export_group("Racer Properties") + @export var nickname = "Nick" + @export var age = 26 - @export_group("Prefixed Properties", "prefix_") - @export var prefix_number = 3 - @export var prefix_string = "" + @export_group("Car Properties", "car_") + @export var car_label = "Speedy" + @export var car_number = 3 @export_group("", "") @export var ungrouped_number = 3 @@ -544,13 +546,13 @@ Define a new subgroup for the following exported properties. This helps to organize properties in the Inspector dock. Subgroups work exactly like groups, except they need a parent group to exist. See [annotation @export_group]. See also [constant PROPERTY_USAGE_SUBGROUP]. [codeblock] - @export_group("My Properties") - @export var number = 3 - @export var string = "" + @export_group("Racer Properties") + @export var nickname = "Nick" + @export var age = 26 - @export_subgroup("My Prefixed Properties", "prefix_") - @export var prefix_number = 3 - @export var prefix_string = "" + @export_subgroup("Car Properties", "car_") + @export var car_label = "Speedy" + @export var car_number = 3 [/codeblock] [b]Note:[/b] Subgroups cannot be nested, they only provide one extra level of depth. Just like the next group ends the previous group, so do the subsequent subgroups. </description> @@ -571,7 +573,7 @@ <annotation name="@onready"> <return type="void" /> <description> - Mark the following property as assigned on [Node]'s ready state change. Values for these properties are not assigned immediately upon the node's creation, and instead are computed and stored right before [method Node._ready]. + Mark the following property as assigned when the [Node] is ready. Values for these properties are not assigned immediately when the node is initialized ([method Object._init]), and instead are computed and stored right before [method Node._ready]. [codeblock] @onready var character_name: Label = $Label [/codeblock] diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index fe79f37454..00f8d2817a 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -2563,7 +2563,7 @@ GDScriptLanguage::GDScriptLanguage() { script_frame_time = 0; _debug_call_stack_pos = 0; - int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater"), 1024); + 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! diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index de0dacece3..c8dfdbdd68 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1742,6 +1742,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } type.is_constant = is_constant; + type.is_read_only = false; p_assignable->set_datatype(type); } @@ -4251,18 +4252,22 @@ Variant GDScriptAnalyzer::make_subscript_reduced_value(GDScriptParser::Subscript Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node) { Array array; - Ref<Script> script_type = p_element_datatype.script_type; - if (p_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { - Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err); - if (err) { - push_error(vformat(R"(Error while getting cache for script "%s".)", p_element_datatype.script_path), p_source_node); - return array; + if (p_element_datatype.builtin_type == Variant::OBJECT) { + Ref<Script> script_type = p_element_datatype.script_type; + if (p_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { + Error err = OK; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err); + if (err) { + push_error(vformat(R"(Error while getting cache for script "%s".)", p_element_datatype.script_path), p_source_node); + return array; + } + script_type.reference_ptr(scr->find_class(p_element_datatype.class_type->fqcn)); } - script_type.reference_ptr(scr->find_class(p_element_datatype.class_type->fqcn)); - } - array.set_typed(p_element_datatype.builtin_type, p_element_datatype.native_type, script_type); + array.set_typed(p_element_datatype.builtin_type, p_element_datatype.native_type, script_type); + } else { + array.set_typed(p_element_datatype.builtin_type, StringName(), Variant()); + } return array; } @@ -4278,11 +4283,15 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo } } else { GDScriptParser::DataType datatype = p_variable->get_datatype(); - if (datatype.is_hard_type() && datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) { - if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type()) { - result = make_array_from_element_datatype(datatype.get_container_element_type()); - } else { - VariantInternal::initialize(&result, datatype.builtin_type); + if (datatype.is_hard_type()) { + if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) { + if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type()) { + result = make_array_from_element_datatype(datatype.get_container_element_type()); + } else { + VariantInternal::initialize(&result, datatype.builtin_type); + } + } else if (datatype.kind == GDScriptParser::DataType::ENUM) { + result = 0; } } } diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index ec7a2b0f1c..45008b0e87 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -143,6 +143,11 @@ void GDScriptByteCodeGenerator::pop_temporary() { ERR_FAIL_COND(used_temporaries.is_empty()); int slot_idx = used_temporaries.back()->get(); const StackSlot &slot = temporaries[slot_idx]; + if (slot.type == Variant::OBJECT) { + // Avoid keeping in the stack long-lived references to objects, + // which may prevent RefCounted objects from being freed. + write_assign_false(Address(Address::TEMPORARY, slot_idx)); + } temporaries_pool[slot.type].push_back(slot_idx); used_temporaries.pop_back(); } @@ -954,7 +959,7 @@ void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Addres append(index); } -GDScriptCodeGenerator::Address GDScriptByteCodeGenerator::get_call_target(const GDScriptCodeGenerator::Address &p_target, Variant::Type p_type) { +GDScriptByteCodeGenerator::CallTarget GDScriptByteCodeGenerator::get_call_target(const GDScriptCodeGenerator::Address &p_target, Variant::Type p_type) { if (p_target.mode == Address::NIL) { GDScriptDataType type; if (p_type != Variant::NIL) { @@ -963,10 +968,9 @@ GDScriptCodeGenerator::Address GDScriptByteCodeGenerator::get_call_target(const type.builtin_type = p_type; } uint32_t addr = add_temporary(type); - pop_temporary(); - return Address(Address::TEMPORARY, addr, type); + return CallTarget(Address(Address::TEMPORARY, addr, type), true, this); } else { - return p_target; + return CallTarget(p_target, false, this); } } @@ -976,9 +980,11 @@ void GDScriptByteCodeGenerator::write_call(const Address &p_target, const Addres append(p_arguments[i]); } append(p_base); - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size()); append(p_function_name); + ct.cleanup(); } void GDScriptByteCodeGenerator::write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { @@ -986,9 +992,11 @@ void GDScriptByteCodeGenerator::write_super_call(const Address &p_target, const for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size()); append(p_function_name); + ct.cleanup(); } void GDScriptByteCodeGenerator::write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { @@ -997,9 +1005,11 @@ void GDScriptByteCodeGenerator::write_call_async(const Address &p_target, const append(p_arguments[i]); } append(p_base); - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size()); append(p_function_name); + ct.cleanup(); } void GDScriptByteCodeGenerator::write_call_gdscript_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) { @@ -1008,9 +1018,11 @@ void GDScriptByteCodeGenerator::write_call_gdscript_utility(const Address &p_tar for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size()); append(gds_function); + ct.cleanup(); #ifdef DEBUG_ENABLED add_debug_name(gds_utilities_names, get_gds_utility_pos(gds_function), p_function); #endif @@ -1034,18 +1046,19 @@ void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, cons if (is_validated) { Variant::Type result_type = Variant::has_utility_function_return_value(p_function) ? Variant::get_utility_function_return_type(p_function) : Variant::NIL; - Address target = get_call_target(p_target, result_type); - Variant::Type temp_type = temporaries[target.address].type; + CallTarget ct = get_call_target(p_target, result_type); + Variant::Type temp_type = temporaries[ct.target.address].type; if (result_type != temp_type) { - write_type_adjust(target, result_type); + write_type_adjust(ct.target, result_type); } append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_UTILITY_VALIDATED, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(target); + append(ct.target); append(p_arguments.size()); append(Variant::get_validated_utility_function(p_function)); + ct.cleanup(); #ifdef DEBUG_ENABLED add_debug_name(utilities_names, get_utility_pos(Variant::get_validated_utility_function(p_function)), p_function); #endif @@ -1054,9 +1067,11 @@ void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, cons for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size()); append(p_function); + ct.cleanup(); } } @@ -1085,10 +1100,12 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_type); append(p_method); append(p_arguments.size()); + ct.cleanup(); } else { write_call(p_target, p_base, p_method, p_arguments); } @@ -1096,10 +1113,10 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, } Variant::Type result_type = Variant::get_builtin_method_return_type(p_type, p_method); - Address target = get_call_target(p_target, result_type); - Variant::Type temp_type = temporaries[target.address].type; + CallTarget ct = get_call_target(p_target, result_type); + Variant::Type temp_type = temporaries[ct.target.address].type; if (result_type != temp_type) { - write_type_adjust(target, result_type); + write_type_adjust(ct.target, result_type); } append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size()); @@ -1108,9 +1125,11 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, append(p_arguments[i]); } append(p_base); - append(target); + append(ct.target); append(p_arguments.size()); append(Variant::get_validated_builtin_method(p_type, p_method)); + ct.cleanup(); + #ifdef DEBUG_ENABLED add_debug_name(builtin_methods_names, get_builtin_method_pos(Variant::get_validated_builtin_method(p_type, p_method)), p_method); #endif @@ -1135,9 +1154,11 @@ void GDScriptByteCodeGenerator::write_call_native_static(const Address &p_target for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(method); append(p_arguments.size()); + ct.cleanup(); return; } } @@ -1147,10 +1168,12 @@ void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target, for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } + CallTarget ct = get_call_target(p_target); append(p_base); - append(get_call_target(p_target)); + append(ct.target); append(p_arguments.size()); append(p_method); + ct.cleanup(); } void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) { @@ -1212,9 +1235,11 @@ void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, cons append(p_arguments[i]); } append(p_base); - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size()); append(p_method); + ct.cleanup(); if (is_ptrcall) { alloc_ptrcall(p_arguments.size()); } @@ -1228,9 +1253,11 @@ void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const S append(p_arguments[i]); } append(GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size()); append(p_function_name); + ct.cleanup(); } void GDScriptByteCodeGenerator::write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { @@ -1239,9 +1266,11 @@ void GDScriptByteCodeGenerator::write_call_self_async(const Address &p_target, c append(p_arguments[i]); } append(GDScriptFunction::ADDR_SELF); - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size()); append(p_function_name); + ct.cleanup(); } void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { @@ -1250,9 +1279,11 @@ void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_targ append(p_arguments[i]); } append(p_base); - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size()); append(p_function_name); + ct.cleanup(); } void GDScriptByteCodeGenerator::write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures, bool p_use_self) { @@ -1261,9 +1292,11 @@ void GDScriptByteCodeGenerator::write_lambda(const Address &p_target, GDScriptFu append(p_captures[i]); } - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_captures.size()); append(p_function); + ct.cleanup(); } void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) { @@ -1300,9 +1333,11 @@ void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size()); append(Variant::get_validated_constructor(p_type, valid_constructor)); + ct.cleanup(); #ifdef DEBUG_ENABLED add_debug_name(constructors_names, get_constructor_pos(Variant::get_validated_constructor(p_type, valid_constructor)), Variant::get_type_name(p_type)); #endif @@ -1314,9 +1349,11 @@ void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size()); append(p_type); + ct.cleanup(); } void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) { @@ -1324,8 +1361,10 @@ void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, c for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size()); + ct.cleanup(); } void GDScriptByteCodeGenerator::write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) { @@ -1333,11 +1372,13 @@ void GDScriptByteCodeGenerator::write_construct_typed_array(const Address &p_tar for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(get_constant_pos(p_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(p_arguments.size()); append(p_element_type.builtin_type); append(p_element_type.native_type); + ct.cleanup(); } void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) { @@ -1345,8 +1386,10 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(get_call_target(p_target)); + CallTarget ct = get_call_target(p_target); + append(ct.target); append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments. + ct.cleanup(); } void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) { diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 8aa02b86c4..1d1b22e196 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -48,6 +48,33 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { const static int RESERVED_STACK = 3; // For self, class, and nil. + struct CallTarget { + Address target; + bool is_new_temporary = false; + GDScriptByteCodeGenerator *codegen = nullptr; +#ifdef DEV_ENABLED + bool cleaned = false; +#endif + + void cleanup() { + DEV_ASSERT(!cleaned); + if (is_new_temporary) { + codegen->pop_temporary(); + } +#ifdef DEV_ENABLED + cleaned = true; +#endif + } + + CallTarget(Address p_target, bool p_is_new_temporary, GDScriptByteCodeGenerator *p_codegen) : + target(p_target), + is_new_temporary(p_is_new_temporary), + codegen(p_codegen) {} + ~CallTarget() { DEV_ASSERT(cleaned); } + CallTarget(const CallTarget &) = delete; + CallTarget &operator=(CallTarget &) = delete; + }; + bool ended = false; GDScriptFunction *function = nullptr; bool debug_stack = false; @@ -326,7 +353,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { } } - Address get_call_target(const Address &p_target, Variant::Type p_type = Variant::NIL); + CallTarget get_call_target(const Address &p_target, Variant::Type p_type = Variant::NIL); int address_of(const Address &p_address) { switch (p_address.mode) { diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 46cd4b0d55..b34be11169 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -254,7 +254,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (codegen.script->member_indices.has(identifier)) { if (codegen.script->member_indices[identifier].getter != StringName() && codegen.script->member_indices[identifier].getter != codegen.function_name) { // Perform getter. - GDScriptCodeGenerator::Address temp = codegen.add_temporary(); + GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->member_indices[identifier].data_type); Vector<GDScriptCodeGenerator::Address> args; // No argument needed. gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args); return temp; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index f45c1f9577..2624fb8dd9 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -544,6 +544,8 @@ private: #endif public: + static constexpr int MAX_CALL_DEPTH = 2048; // Limit to try to avoid crash because of a stack overflow. + struct CallState { GDScript *script = nullptr; GDScriptInstance *instance = nullptr; diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 7a11ea52f0..9f7d27d841 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -459,6 +459,33 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a r_err.error = Callable::CallError::CALL_OK; + static thread_local int call_depth = 0; + if (unlikely(++call_depth > MAX_CALL_DEPTH)) { + call_depth--; +#ifdef DEBUG_ENABLED + String err_file; + if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && !p_instance->script->path.is_empty()) { + err_file = p_instance->script->path; + } else if (_script) { + err_file = _script->path; + } + if (err_file.is_empty()) { + err_file = "<built-in>"; + } + String err_func = name; + if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && !p_instance->script->name.is_empty()) { + err_func = p_instance->script->name + "." + err_func; + } + int err_line = _initial_line; + const char *err_text = "Stack overflow. Check for infinite recursion in your script."; + if (!GDScriptLanguage::get_singleton()->debug_break(err_text, false)) { + // Debugger break did not happen. + _err_print_error(err_func.utf8().get_data(), err_file.utf8().get_data(), err_line, err_text, false, ERR_HANDLER_SCRIPT); + } +#endif + return _get_default_variant_for_data_type(return_type); + } + Variant retvalue; Variant *stack = nullptr; Variant **instruction_args = nullptr; @@ -493,10 +520,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_err.argument = _argument_count; + call_depth--; return _get_default_variant_for_data_type(return_type); } else if (p_argcount < _argument_count - _default_arg_count) { r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_err.argument = _argument_count - _default_arg_count; + call_depth--; return _get_default_variant_for_data_type(return_type); } else { defarg = _argument_count - p_argcount; @@ -524,6 +553,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_err.argument = i; r_err.expected = argument_types[i].builtin_type; + call_depth--; return _get_default_variant_for_data_type(return_type); } if (argument_types[i].kind == GDScriptDataType::BUILTIN) { @@ -1244,12 +1274,20 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a "' to a variable of type '" + nc->get_name() + "'."; OPCODE_BREAK; } - Object *src_obj = src->operator Object *(); - if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { - err_text = "Trying to assign value of type '" + src_obj->get_class_name() + - "' to a variable of type '" + nc->get_name() + "'."; - OPCODE_BREAK; + if (src->get_type() == Variant::OBJECT) { + bool was_freed = false; + Object *src_obj = src->get_validated_object_with_check(was_freed); + if (!src_obj && was_freed) { + err_text = "Trying to assign invalid previously freed instance."; + OPCODE_BREAK; + } + + if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { + err_text = "Trying to assign value of type '" + src_obj->get_class_name() + + "' to a variable of type '" + nc->get_name() + "'."; + OPCODE_BREAK; + } } #endif // DEBUG_ENABLED *dst = *src; @@ -1274,15 +1312,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_BREAK; } - if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) { - ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); + if (src->get_type() == Variant::OBJECT) { + bool was_freed = false; + Object *val_obj = src->get_validated_object_with_check(was_freed); + if (!val_obj && was_freed) { + err_text = "Trying to assign invalid previously freed instance."; + OPCODE_BREAK; + } + + ScriptInstance *scr_inst = val_obj->get_script_instance(); if (!scr_inst) { - err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() + + err_text = "Trying to assign value of type '" + val_obj->get_class_name() + "' to a variable of type '" + base_type->get_path().get_file() + "'."; OPCODE_BREAK; } - Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); + Script *src_type = val_obj->get_script_instance()->get_script().ptr(); bool valid = false; while (src_type) { @@ -1294,7 +1339,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } if (!valid) { - err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() + + err_text = "Trying to assign value of type '" + val_obj->get_script_instance()->get_script()->get_path().get_file() + "' to a variable of type '" + base_type->get_path().get_file() + "'."; OPCODE_BREAK; } @@ -3582,5 +3627,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a stack[i].~Variant(); } + call_depth--; + return retvalue; } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/native_freed_instance.gd b/modules/gdscript/tests/scripts/analyzer/errors/native_freed_instance.gd new file mode 100644 index 0000000000..dd2708b21d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/native_freed_instance.gd @@ -0,0 +1,7 @@ +func test(): + var x = Node.new() + + x.free() + + var ok = x + var bad : Node = x diff --git a/modules/gdscript/tests/scripts/analyzer/errors/native_freed_instance.out b/modules/gdscript/tests/scripts/analyzer/errors/native_freed_instance.out new file mode 100644 index 0000000000..679e51ed81 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/native_freed_instance.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> analyzer/errors/native_freed_instance.gd +>> 7 +>> Trying to assign invalid previously freed instance. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/script_freed_instance.gd b/modules/gdscript/tests/scripts/analyzer/errors/script_freed_instance.gd new file mode 100644 index 0000000000..758fbaccc9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/script_freed_instance.gd @@ -0,0 +1,10 @@ +class A extends Node: + pass + +func test(): + var x = A.new() + + x.free() + + var ok = x + var bad : A = x diff --git a/modules/gdscript/tests/scripts/analyzer/errors/script_freed_instance.out b/modules/gdscript/tests/scripts/analyzer/errors/script_freed_instance.out new file mode 100644 index 0000000000..dec7090322 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/script_freed_instance.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> analyzer/errors/script_freed_instance.gd +>> 10 +>> Trying to assign invalid previously freed instance. diff --git a/modules/gdscript/tests/scripts/analyzer/features/getter_return_type.gd b/modules/gdscript/tests/scripts/analyzer/features/getter_return_type.gd new file mode 100644 index 0000000000..38bb7f6e9c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/getter_return_type.gd @@ -0,0 +1,9 @@ +var Value:int = 8 : + get: + return Value + set(v): + Value = v + +func test(): + var f:float = Value + print(int(f)) diff --git a/modules/gdscript/tests/scripts/analyzer/features/getter_return_type.out b/modules/gdscript/tests/scripts/analyzer/features/getter_return_type.out new file mode 100644 index 0000000000..b0cb63ef59 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/getter_return_type.out @@ -0,0 +1,2 @@ +GDTEST_OK +8 diff --git a/modules/gdscript/tests/scripts/analyzer/features/native_typed_assign_null.gd b/modules/gdscript/tests/scripts/analyzer/features/native_typed_assign_null.gd new file mode 100644 index 0000000000..c197062d9f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/native_typed_assign_null.gd @@ -0,0 +1,15 @@ +extends Node + +func test(): + var typed: Variant = get_node_or_null("does_not_exist") + var untyped = null + var node_1: Node = typed + var node_2: Node = untyped + var node_3 = typed + var node_4 = untyped + print(typed) + print(untyped) + print(node_1) + print(node_2) + print(node_3) + print(node_4) diff --git a/modules/gdscript/tests/scripts/analyzer/features/native_typed_assign_null.out b/modules/gdscript/tests/scripts/analyzer/features/native_typed_assign_null.out new file mode 100644 index 0000000000..d66b72f5c3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/native_typed_assign_null.out @@ -0,0 +1,7 @@ +GDTEST_OK +<Object#null> +<null> +<Object#null> +<null> +<Object#null> +<null> diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd index 092ae49d00..26542a9e2f 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd @@ -86,7 +86,8 @@ func test(): var typed_int := 556 var converted_floats: Array[float] = [typed_int] - assert(str(converted_floats) == '[556]') + converted_floats.push_back(498) + assert(str(converted_floats) == '[556, 498]') assert(converted_floats.get_typed_builtin() == TYPE_FLOAT) @@ -200,6 +201,10 @@ func test(): assert(str(typed_enums) == '[391]') assert(typed_enums.get_typed_builtin() == TYPE_INT) + const const_enums: Array[E] = [] + assert(const_enums.get_typed_builtin() == TYPE_INT) + assert(const_enums.get_typed_class_name() == &'') + var a := A.new() var typed_natives: Array[RefCounted] = [a] |