From 0e6aa6fc3879c4c1c37bc598a83a44cb5cddfe85 Mon Sep 17 00:00:00 2001
From: George Marques <george@gmarqu.es>
Date: Fri, 24 Feb 2023 21:39:10 -0300
Subject: [PATCH] GDScript: Initialize all defaults beforehand in implicit
 constructor

Set all the default values for typed variables before actually trying to
initialize them, including `@onready` ones.

This ensures that if validated calls are being used there will be a
value of the correct type, even if the resolution is done out of order
or deferred because of `@onready`.
---
 modules/gdscript/gdscript_compiler.cpp        | 36 +++++++++++++------
 .../features/default_set_beforehand.gd        | 20 +++++++++++
 .../features/default_set_beforehand.out       |  2 ++
 3 files changed, 48 insertions(+), 10 deletions(-)
 create mode 100644 modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
 create mode 100644 modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.out

diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index fad2bf334b4..7feb19c8ed0 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -2022,6 +2022,32 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
 	bool is_initializer = p_func && !p_for_lambda && p_func->identifier->name == GDScriptLanguage::get_singleton()->strings._init;
 	bool is_implicit_ready = !p_func && p_for_ready;
 
+	if (!p_for_lambda && is_implicit_initializer) {
+		// Initialize the default values for type variables before anything.
+		// This avoids crashes if they are accessed with validated calls before being properly initialized.
+		// It may happen with out-of-order access or with `@onready` variables.
+		for (const GDScriptParser::ClassNode::Member &member : p_class->members) {
+			if (member.type != GDScriptParser::ClassNode::Member::VARIABLE) {
+				continue;
+			}
+
+			const GDScriptParser::VariableNode *field = member.variable;
+			GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script);
+			GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
+
+			if (field_type.has_type) {
+				codegen.generator->write_newline(field->start_line);
+
+				if (field_type.has_container_element_type()) {
+					codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
+				} else if (field_type.kind == GDScriptDataType::BUILTIN) {
+					codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
+				}
+				// The `else` branch is for objects, in such case we leave it as `null`.
+			}
+		}
+	}
+
 	if (!p_for_lambda && (is_implicit_initializer || is_implicit_ready)) {
 		// Initialize class fields.
 		for (int i = 0; i < p_class->members.size(); i++) {
@@ -2055,16 +2081,6 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
 				if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
 					codegen.generator->pop_temporary();
 				}
-			} else if (field_type.has_type) {
-				codegen.generator->write_newline(field->start_line);
-
-				// Initialize with default for type.
-				if (field_type.has_container_element_type()) {
-					codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>());
-				} else if (field_type.kind == GDScriptDataType::BUILTIN) {
-					codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
-				}
-				// The `else` branch is for objects, in such case we leave it as `null`.
 			}
 		}
 	}
diff --git a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
new file mode 100644
index 00000000000..03278e453f5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
@@ -0,0 +1,20 @@
+extends Node
+
+@onready var later_inferred := [1]
+@onready var later_static : Array
+@onready var later_static_with_init : Array = [1]
+@onready var later_untyped = [1]
+
+func test():
+	assert(typeof(later_inferred) == TYPE_ARRAY)
+	assert(later_inferred.size() == 0)
+
+	assert(typeof(later_static) == TYPE_ARRAY)
+	assert(later_static.size() == 0)
+
+	assert(typeof(later_static_with_init) == TYPE_ARRAY)
+	assert(later_static_with_init.size() == 0)
+
+	assert(typeof(later_untyped) == TYPE_NIL)
+
+	print("ok")
diff --git a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.out b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.out
new file mode 100644
index 00000000000..1b47ed10dc0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok