Преглед на файлове

GDScript: Fix uninitialized local variables not being reset

Danil Alexeev преди 1 година
родител
ревизия
27d7760f41

+ 61 - 25
modules/gdscript/gdscript_byte_codegen.cpp

@@ -45,8 +45,8 @@ uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool
 }
 
 uint32_t GDScriptByteCodeGenerator::add_local(const StringName &p_name, const GDScriptDataType &p_type) {
-	int stack_pos = locals.size() + RESERVED_STACK;
-	locals.push_back(StackSlot(p_type.builtin_type));
+	int stack_pos = locals.size() + GDScriptFunction::FIXED_ADDRESSES_MAX;
+	locals.push_back(StackSlot(p_type.builtin_type, p_type.can_contain_object()));
 	add_stack_identifier(p_name, stack_pos);
 	return stack_pos;
 }
@@ -122,7 +122,7 @@ uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type
 
 	List<int> &pool = temporaries_pool[temp_type];
 	if (pool.is_empty()) {
-		StackSlot new_temp(temp_type);
+		StackSlot new_temp(temp_type, p_type.can_contain_object());
 		int idx = temporaries.size();
 		pool.push_back(idx);
 		temporaries.push_back(new_temp);
@@ -136,15 +136,14 @@ uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type
 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::NIL) {
+	if (temporaries[slot_idx].can_contain_object) {
 		// Avoid keeping in the stack long-lived references to objects,
-		// which may prevent RefCounted objects from being freed.
+		// which may prevent `RefCounted` objects from being freed.
 		// However, the cleanup will be performed an the end of the
 		// statement, to allow object references to survive chaining.
-		temporaries_pending_clear.push_back(slot_idx);
+		temporaries_pending_clear.insert(slot_idx);
 	}
-	temporaries_pool[slot.type].push_back(slot_idx);
+	temporaries_pool[temporaries[slot_idx].type].push_back(slot_idx);
 	used_temporaries.pop_back();
 }
 
@@ -187,7 +186,7 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
 	append_opcode(GDScriptFunction::OPCODE_END);
 
 	for (int i = 0; i < temporaries.size(); i++) {
-		int stack_index = i + max_locals + RESERVED_STACK;
+		int stack_index = i + max_locals + GDScriptFunction::FIXED_ADDRESSES_MAX;
 		for (int j = 0; j < temporaries[i].bytecode_indices.size(); j++) {
 			opcodes.write[temporaries[i].bytecode_indices[j]] = stack_index | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
 		}
@@ -398,7 +397,7 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
 	if (debug_stack) {
 		function->stack_debug = stack_debug;
 	}
-	function->_stack_size = RESERVED_STACK + max_locals + temporaries.size();
+	function->_stack_size = GDScriptFunction::FIXED_ADDRESSES_MAX + max_locals + temporaries.size();
 	function->_instruction_args_size = instr_args_max;
 
 #ifdef DEBUG_ENABLED
@@ -945,6 +944,11 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr
 	}
 }
 
+void GDScriptByteCodeGenerator::write_assign_null(const Address &p_target) {
+	append_opcode(GDScriptFunction::OPCODE_ASSIGN_NULL);
+	append(p_target);
+}
+
 void GDScriptByteCodeGenerator::write_assign_true(const Address &p_target) {
 	append_opcode(GDScriptFunction::OPCODE_ASSIGN_TRUE);
 	append(p_target);
@@ -1579,9 +1583,8 @@ void GDScriptByteCodeGenerator::write_for(const Address &p_variable, bool p_use_
 
 	if (p_use_conversion) {
 		write_assign_with_conversion(p_variable, temp);
-		const GDScriptDataType &type = p_variable.type;
-		if (type.kind != GDScriptDataType::BUILTIN || type.builtin_type == Variant::ARRAY || type.builtin_type == Variant::DICTIONARY) {
-			write_assign_false(temp); // Can contain RefCounted, so clear it.
+		if (p_variable.type.can_contain_object()) {
+			clear_address(temp); // Can contain `RefCounted`, so clear it.
 		}
 	}
 }
@@ -1746,23 +1749,56 @@ void GDScriptByteCodeGenerator::end_block() {
 	pop_stack_identifiers();
 }
 
-void GDScriptByteCodeGenerator::clean_temporaries() {
-	List<int>::Element *E = temporaries_pending_clear.front();
-	while (E) {
-		// The temporary may have been re-used as something else than an object
-		// since it was added to the list. In that case, there's no need to clear it.
-		int slot_idx = E->get();
-		const StackSlot &slot = temporaries[slot_idx];
-		if (slot.type == Variant::NIL) {
-			write_assign_false(Address(Address::TEMPORARY, slot_idx));
+void GDScriptByteCodeGenerator::clear_temporaries() {
+	for (int slot_idx : temporaries_pending_clear) {
+		// The temporary may have been re-used as something else since it was added to the list.
+		// In that case, there's **no** need to clear it.
+		if (temporaries[slot_idx].can_contain_object) {
+			clear_address(Address(Address::TEMPORARY, slot_idx)); // Can contain `RefCounted`, so clear it.
+		}
+	}
+	temporaries_pending_clear.clear();
+}
+
+void GDScriptByteCodeGenerator::clear_address(const Address &p_address) {
+	// Do not check `is_local_dirty()` here! Always clear the address since the codegen doesn't track the compiler.
+	// Also, this method is used to initialize local variables of built-in types, since they cannot be `null`.
+
+	if (p_address.type.has_type && p_address.type.kind == GDScriptDataType::BUILTIN) {
+		switch (p_address.type.builtin_type) {
+			case Variant::BOOL:
+				write_assign_false(p_address);
+				break;
+			case Variant::ARRAY:
+				if (p_address.type.has_container_element_type(0)) {
+					write_construct_typed_array(p_address, p_address.type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
+				} else {
+					write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
+				}
+				break;
+			case Variant::NIL:
+			case Variant::OBJECT:
+				write_assign_null(p_address);
+				break;
+			default:
+				write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
+				break;
 		}
+	} else {
+		write_assign_null(p_address);
+	}
 
-		List<int>::Element *next = E->next();
-		E->erase();
-		E = next;
+	if (p_address.mode == Address::LOCAL_VARIABLE) {
+		dirty_locals.erase(p_address.address);
 	}
 }
 
+// Returns `true` if the local has been re-used and not cleaned up with `clear_address()`.
+bool GDScriptByteCodeGenerator::is_local_dirty(const Address &p_address) const {
+	ERR_FAIL_COND_V(p_address.mode != Address::LOCAL_VARIABLE, false);
+	return dirty_locals.has(p_address.address);
+}
+
 GDScriptByteCodeGenerator::~GDScriptByteCodeGenerator() {
 	if (!ended && function != nullptr) {
 		memdelete(function);

+ 13 - 6
modules/gdscript/gdscript_byte_codegen.h

@@ -38,15 +38,14 @@
 class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
 	struct StackSlot {
 		Variant::Type type = Variant::NIL;
+		bool can_contain_object = true;
 		Vector<int> bytecode_indices;
 
 		StackSlot() = default;
-		StackSlot(Variant::Type p_type) :
-				type(p_type) {}
+		StackSlot(Variant::Type p_type, bool p_can_contain_object) :
+				type(p_type), can_contain_object(p_can_contain_object) {}
 	};
 
-	const static int RESERVED_STACK = 3; // For self, class, and nil.
-
 	struct CallTarget {
 		Address target;
 		bool is_new_temporary = false;
@@ -85,9 +84,11 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
 	RBMap<StringName, int> local_constants;
 
 	Vector<StackSlot> locals;
+	HashSet<int> dirty_locals;
+
 	Vector<StackSlot> temporaries;
 	List<int> used_temporaries;
-	List<int> temporaries_pending_clear;
+	HashSet<int> temporaries_pending_clear;
 	RBMap<Variant::Type, List<int>> temporaries_pool;
 
 	List<GDScriptFunction::StackDebug> stack_debug;
@@ -193,6 +194,9 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
 			ERR_PRINT("Leaving block with non-zero temporary variables: " + itos(used_temporaries.size()));
 		}
 #endif
+		for (int i = current_locals; i < locals.size(); i++) {
+			dirty_locals.insert(i + GDScriptFunction::FIXED_ADDRESSES_MAX);
+		}
 		locals.resize(current_locals);
 		if (debug_stack) {
 			for (const KeyValue<StringName, int> &E : block_identifiers) {
@@ -455,7 +459,9 @@ public:
 	virtual uint32_t add_or_get_name(const StringName &p_name) override;
 	virtual uint32_t add_temporary(const GDScriptDataType &p_type) override;
 	virtual void pop_temporary() override;
-	virtual void clean_temporaries() override;
+	virtual void clear_temporaries() override;
+	virtual void clear_address(const Address &p_address) override;
+	virtual bool is_local_dirty(const Address &p_address) const override;
 
 	virtual void start_parameters() override;
 	virtual void end_parameters() override;
@@ -496,6 +502,7 @@ public:
 	virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) override;
 	virtual void write_assign(const Address &p_target, const Address &p_source) override;
 	virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) override;
+	virtual void write_assign_null(const Address &p_target) override;
 	virtual void write_assign_true(const Address &p_target) override;
 	virtual void write_assign_false(const Address &p_target) override;
 	virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src, bool p_use_conversion) override;

+ 4 - 1
modules/gdscript/gdscript_codegen.h

@@ -73,7 +73,9 @@ public:
 	virtual uint32_t add_or_get_name(const StringName &p_name) = 0;
 	virtual uint32_t add_temporary(const GDScriptDataType &p_type) = 0;
 	virtual void pop_temporary() = 0;
-	virtual void clean_temporaries() = 0;
+	virtual void clear_temporaries() = 0;
+	virtual void clear_address(const Address &p_address) = 0;
+	virtual bool is_local_dirty(const Address &p_address) const = 0;
 
 	virtual void start_parameters() = 0;
 	virtual void end_parameters() = 0;
@@ -114,6 +116,7 @@ public:
 	virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) = 0;
 	virtual void write_assign(const Address &p_target, const Address &p_source) = 0;
 	virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) = 0;
+	virtual void write_assign_null(const Address &p_target) = 0;
 	virtual void write_assign_true(const Address &p_target) = 0;
 	virtual void write_assign_false(const Address &p_target) = 0;
 	virtual void write_assign_default_parameter(const Address &dst, const Address &src, bool p_use_conversion) = 0;

+ 41 - 33
modules/gdscript/gdscript_compiler.cpp

@@ -1828,7 +1828,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
 	ERR_FAIL_V_MSG(p_previous_test, "Reaching the end of pattern compilation without matching a pattern.");
 }
 
-List<GDScriptCodeGenerator::Address> GDScriptCompiler::_add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block) {
+List<GDScriptCodeGenerator::Address> GDScriptCompiler::_add_block_locals(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block) {
 	List<GDScriptCodeGenerator::Address> addresses;
 	for (int i = 0; i < p_block->locals.size(); i++) {
 		if (p_block->locals[i].type == GDScriptParser::SuiteNode::Local::PARAMETER || p_block->locals[i].type == GDScriptParser::SuiteNode::Local::FOR_VARIABLE) {
@@ -1840,27 +1840,25 @@ List<GDScriptCodeGenerator::Address> GDScriptCompiler::_add_locals_in_block(Code
 	return addresses;
 }
 
-// Avoid keeping in the stack long-lived references to objects, which may prevent RefCounted objects from being freed.
-void GDScriptCompiler::_clear_addresses(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_addresses) {
-	for (const List<GDScriptCodeGenerator::Address>::Element *E = p_addresses.front(); E; E = E->next()) {
-		GDScriptDataType type = E->get().type;
-		// If not an object and cannot contain an object, no need to clear.
-		if (type.kind != GDScriptDataType::BUILTIN || type.builtin_type == Variant::ARRAY || type.builtin_type == Variant::DICTIONARY) {
-			codegen.generator->write_assign_false(E->get());
+// Avoid keeping in the stack long-lived references to objects, which may prevent `RefCounted` objects from being freed.
+void GDScriptCompiler::_clear_block_locals(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_locals) {
+	for (const GDScriptCodeGenerator::Address &local : p_locals) {
+		if (local.type.can_contain_object()) {
+			codegen.generator->clear_address(local);
 		}
 	}
 }
 
-Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals, bool p_reset_locals) {
+Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals, bool p_clear_locals) {
 	Error err = OK;
 	GDScriptCodeGenerator *gen = codegen.generator;
 	List<GDScriptCodeGenerator::Address> block_locals;
 
-	gen->clean_temporaries();
+	gen->clear_temporaries();
 	codegen.start_block();
 
 	if (p_add_locals) {
-		block_locals = _add_locals_in_block(codegen, p_block);
+		block_locals = _add_block_locals(codegen, p_block);
 	}
 
 	for (int i = 0; i < p_block->statements.size(); i++) {
@@ -1875,7 +1873,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 			case GDScriptParser::Node::MATCH: {
 				const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s);
 
-				codegen.start_block();
+				codegen.start_block(); // Add an extra block, since the binding pattern and @special variables belong to the branch scope.
 
 				// Evaluate the match expression.
 				GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script));
@@ -1916,7 +1914,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 					codegen.start_block(); // Create an extra block around for binds.
 
 					// Add locals in block before patterns, so temporaries don't use the stack address for binds.
-					List<GDScriptCodeGenerator::Address> branch_locals = _add_locals_in_block(codegen, branch->block);
+					List<GDScriptCodeGenerator::Address> branch_locals = _add_block_locals(codegen, branch->block);
 
 #ifdef DEBUG_ENABLED
 					// Add a newline before each branch, since the debugger needs those.
@@ -1963,7 +1961,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 						return err;
 					}
 
-					_clear_addresses(codegen, branch_locals);
+					_clear_block_locals(codegen, branch_locals);
 
 					codegen.end_block(); // Get out of extra block.
 				}
@@ -2005,7 +2003,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 			case GDScriptParser::Node::FOR: {
 				const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s);
 
-				codegen.start_block();
+				codegen.start_block(); // Add an extra block, since the iterator and @special variables belong to the loop scope.
+
 				GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype(), codegen.script));
 
 				gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype(), codegen.script));
@@ -2023,14 +2022,21 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 
 				gen->write_for(iterator, for_n->use_conversion_assign);
 
-				err = _parse_block(codegen, for_n->loop);
+				// Loop variables must be cleared even when `break`/`continue` is used.
+				List<GDScriptCodeGenerator::Address> loop_locals = _add_block_locals(codegen, for_n->loop);
+
+				//_clear_block_locals(codegen, loop_locals); // Inside loop, before block - for `continue`. // TODO
+
+				err = _parse_block(codegen, for_n->loop, false); // Don't add locals again.
 				if (err) {
 					return err;
 				}
 
 				gen->write_endfor();
 
-				codegen.end_block();
+				_clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit.
+
+				codegen.end_block(); // Get out of extra block.
 			} break;
 			case GDScriptParser::Node::WHILE: {
 				const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s);
@@ -2048,12 +2054,19 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 					codegen.generator->pop_temporary();
 				}
 
-				err = _parse_block(codegen, while_n->loop);
+				// Loop variables must be cleared even when `break`/`continue` is used.
+				List<GDScriptCodeGenerator::Address> loop_locals = _add_block_locals(codegen, while_n->loop);
+
+				//_clear_block_locals(codegen, loop_locals); // Inside loop, before block - for `continue`. // TODO
+
+				err = _parse_block(codegen, while_n->loop, false); // Don't add locals again.
 				if (err) {
 					return err;
 				}
 
 				gen->write_endwhile();
+
+				_clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit.
 			} break;
 			case GDScriptParser::Node::BREAK: {
 				gen->write_break();
@@ -2136,21 +2149,16 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 						codegen.generator->pop_temporary();
 					}
 					initialized = true;
-				} else if (local_type.has_type) {
-					// Initialize with default for type.
-					if (local_type.has_container_element_type(0)) {
-						codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
-						initialized = true;
-					} else if (local_type.kind == GDScriptDataType::BUILTIN) {
-						codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
-						initialized = true;
-					}
-					// The `else` branch is for objects, in such case we leave it as `null`.
+				} else if ((local_type.has_type && local_type.kind == GDScriptDataType::BUILTIN) || codegen.generator->is_local_dirty(local)) {
+					// Initialize with default for the type. Built-in types must always be cleared (they cannot be `null`).
+					// Objects and untyped variables are assigned to `null` only if the stack address has been re-used and not cleared.
+					codegen.generator->clear_address(local);
+					initialized = true;
 				}
 
-				// Assigns a null for the unassigned variables in loops.
+				// Don't check `is_local_dirty()` since the variable must be assigned to `null` **on each iteration**.
 				if (!initialized && p_block->is_in_loop) {
-					codegen.generator->write_construct(local, Variant::NIL, Vector<GDScriptCodeGenerator::Address>());
+					codegen.generator->clear_address(local);
 				}
 			} break;
 			case GDScriptParser::Node::CONSTANT: {
@@ -2182,11 +2190,11 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 			} break;
 		}
 
-		gen->clean_temporaries();
+		gen->clear_temporaries();
 	}
 
-	if (p_add_locals && p_reset_locals) {
-		_clear_addresses(codegen, block_locals);
+	if (p_add_locals && p_clear_locals) {
+		_clear_block_locals(codegen, block_locals);
 	}
 
 	codegen.end_block();

+ 3 - 3
modules/gdscript/gdscript_compiler.h

@@ -157,9 +157,9 @@ class GDScriptCompiler {
 	GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
 	GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
 	GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested);
-	List<GDScriptCodeGenerator::Address> _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
-	void _clear_addresses(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_addresses);
-	Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true, bool p_reset_locals = true);
+	List<GDScriptCodeGenerator::Address> _add_block_locals(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
+	void _clear_block_locals(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_locals);
+	Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true, bool p_clear_locals = true);
 	GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false);
 	GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class);
 	Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);

+ 7 - 0
modules/gdscript/gdscript_disassembler.cpp

@@ -360,6 +360,13 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
 
 				incr += 3;
 			} break;
+			case OPCODE_ASSIGN_NULL: {
+				text += "assign ";
+				text += DADDR(1);
+				text += " = null";
+
+				incr += 2;
+			} break;
 			case OPCODE_ASSIGN_TRUE: {
 				text += "assign ";
 				text += DADDR(1);

+ 24 - 4
modules/gdscript/gdscript_function.h

@@ -78,17 +78,17 @@ public:
 				if (valid && builtin_type == Variant::ARRAY && has_container_element_type(0)) {
 					Array array = p_variant;
 					if (array.is_typed()) {
-						GDScriptDataType array_container_type = get_container_element_type(0);
+						const GDScriptDataType &elem_type = container_element_types[0];
 						Variant::Type array_builtin_type = (Variant::Type)array.get_typed_builtin();
 						StringName array_native_type = array.get_typed_class_name();
 						Ref<Script> array_script_type_ref = array.get_typed_script();
 
 						if (array_script_type_ref.is_valid()) {
-							valid = (array_container_type.kind == SCRIPT || array_container_type.kind == GDSCRIPT) && array_container_type.script_type == array_script_type_ref.ptr();
+							valid = (elem_type.kind == SCRIPT || elem_type.kind == GDSCRIPT) && elem_type.script_type == array_script_type_ref.ptr();
 						} else if (array_native_type != StringName()) {
-							valid = array_container_type.kind == NATIVE && array_container_type.native_type == array_native_type;
+							valid = elem_type.kind == NATIVE && elem_type.native_type == array_native_type;
 						} else {
-							valid = array_container_type.kind == BUILTIN && array_container_type.builtin_type == array_builtin_type;
+							valid = elem_type.kind == BUILTIN && elem_type.builtin_type == array_builtin_type;
 						}
 					} else {
 						valid = false;
@@ -147,6 +147,25 @@ public:
 		return false;
 	}
 
+	bool can_contain_object() const {
+		if (has_type && kind == BUILTIN) {
+			switch (builtin_type) {
+				case Variant::ARRAY:
+					if (has_container_element_type(0)) {
+						return container_element_types[0].can_contain_object();
+					}
+					return true;
+				case Variant::DICTIONARY:
+				case Variant::NIL:
+				case Variant::OBJECT:
+					return true;
+				default:
+					return false;
+			}
+		}
+		return true;
+	}
+
 	void set_container_element_type(int p_index, const GDScriptDataType &p_element_type) {
 		ERR_FAIL_COND(p_index < 0);
 		while (p_index >= container_element_types.size()) {
@@ -218,6 +237,7 @@ public:
 		OPCODE_SET_STATIC_VARIABLE, // Only for GDScript.
 		OPCODE_GET_STATIC_VARIABLE, // Only for GDScript.
 		OPCODE_ASSIGN,
+		OPCODE_ASSIGN_NULL,
 		OPCODE_ASSIGN_TRUE,
 		OPCODE_ASSIGN_FALSE,
 		OPCODE_ASSIGN_TYPED_BUILTIN,

+ 11 - 0
modules/gdscript/gdscript_vm.cpp

@@ -234,6 +234,7 @@ void (*type_init_function_table[])(Variant *) = {
 		&&OPCODE_SET_STATIC_VARIABLE,                  \
 		&&OPCODE_GET_STATIC_VARIABLE,                  \
 		&&OPCODE_ASSIGN,                               \
+		&&OPCODE_ASSIGN_NULL,                          \
 		&&OPCODE_ASSIGN_TRUE,                          \
 		&&OPCODE_ASSIGN_FALSE,                         \
 		&&OPCODE_ASSIGN_TYPED_BUILTIN,                 \
@@ -1256,6 +1257,16 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 			}
 			DISPATCH_OPCODE;
 
+			OPCODE(OPCODE_ASSIGN_NULL) {
+				CHECK_SPACE(2);
+				GET_VARIANT_PTR(dst, 0);
+
+				*dst = Variant();
+
+				ip += 2;
+			}
+			DISPATCH_OPCODE;
+
 			OPCODE(OPCODE_ASSIGN_TRUE) {
 				CHECK_SPACE(2);
 				GET_VARIANT_PTR(dst, 0);

+ 21 - 0
modules/gdscript/tests/scripts/runtime/features/reset_uninit_local_vars.gd

@@ -0,0 +1,21 @@
+# GH-89958
+
+func test():
+	if true:
+		@warning_ignore("unused_variable")
+		var a = 1
+		@warning_ignore("unused_variable")
+		var b := 1
+		@warning_ignore("unused_variable")
+		var c := 1
+
+	if true:
+		@warning_ignore("unassigned_variable")
+		var a
+		print(a)
+		@warning_ignore("unassigned_variable")
+		var b
+		print(b)
+		@warning_ignore("unassigned_variable")
+		var c: Object
+		print(c)

+ 4 - 0
modules/gdscript/tests/scripts/runtime/features/reset_uninit_local_vars.out

@@ -0,0 +1,4 @@
+GDTEST_OK
+<null>
+<null>
+<null>