mirror of
				https://github.com/godotengine/godot.git
				synced 2025-11-03 23:21:15 +00:00 
			
		
		
		
	Merge pull request #89990 from dalexeev/gds-reset-uninit-local-vars
GDScript: Fix uninitialized local variables not being reset
This commit is contained in:
		
						commit
						69a23e64e4
					
				
					 10 changed files with 190 additions and 73 deletions
				
			
		| 
						 | 
					@ -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) {
 | 
					uint32_t GDScriptByteCodeGenerator::add_local(const StringName &p_name, const GDScriptDataType &p_type) {
 | 
				
			||||||
	int stack_pos = locals.size() + RESERVED_STACK;
 | 
						int stack_pos = locals.size() + GDScriptFunction::FIXED_ADDRESSES_MAX;
 | 
				
			||||||
	locals.push_back(StackSlot(p_type.builtin_type));
 | 
						locals.push_back(StackSlot(p_type.builtin_type, p_type.can_contain_object()));
 | 
				
			||||||
	add_stack_identifier(p_name, stack_pos);
 | 
						add_stack_identifier(p_name, stack_pos);
 | 
				
			||||||
	return 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];
 | 
						List<int> &pool = temporaries_pool[temp_type];
 | 
				
			||||||
	if (pool.is_empty()) {
 | 
						if (pool.is_empty()) {
 | 
				
			||||||
		StackSlot new_temp(temp_type);
 | 
							StackSlot new_temp(temp_type, p_type.can_contain_object());
 | 
				
			||||||
		int idx = temporaries.size();
 | 
							int idx = temporaries.size();
 | 
				
			||||||
		pool.push_back(idx);
 | 
							pool.push_back(idx);
 | 
				
			||||||
		temporaries.push_back(new_temp);
 | 
							temporaries.push_back(new_temp);
 | 
				
			||||||
| 
						 | 
					@ -136,15 +136,14 @@ uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type
 | 
				
			||||||
void GDScriptByteCodeGenerator::pop_temporary() {
 | 
					void GDScriptByteCodeGenerator::pop_temporary() {
 | 
				
			||||||
	ERR_FAIL_COND(used_temporaries.is_empty());
 | 
						ERR_FAIL_COND(used_temporaries.is_empty());
 | 
				
			||||||
	int slot_idx = used_temporaries.back()->get();
 | 
						int slot_idx = used_temporaries.back()->get();
 | 
				
			||||||
	const StackSlot &slot = temporaries[slot_idx];
 | 
						if (temporaries[slot_idx].can_contain_object) {
 | 
				
			||||||
	if (slot.type == Variant::NIL) {
 | 
					 | 
				
			||||||
		// Avoid keeping in the stack long-lived references to objects,
 | 
							// 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
 | 
							// However, the cleanup will be performed an the end of the
 | 
				
			||||||
		// statement, to allow object references to survive chaining.
 | 
							// 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();
 | 
						used_temporaries.pop_back();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -187,7 +186,7 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
 | 
				
			||||||
	append_opcode(GDScriptFunction::OPCODE_END);
 | 
						append_opcode(GDScriptFunction::OPCODE_END);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (int i = 0; i < temporaries.size(); i++) {
 | 
						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++) {
 | 
							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);
 | 
								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) {
 | 
						if (debug_stack) {
 | 
				
			||||||
		function->stack_debug = stack_debug;
 | 
							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;
 | 
						function->_instruction_args_size = instr_args_max;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef DEBUG_ENABLED
 | 
					#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) {
 | 
					void GDScriptByteCodeGenerator::write_assign_true(const Address &p_target) {
 | 
				
			||||||
	append_opcode(GDScriptFunction::OPCODE_ASSIGN_TRUE);
 | 
						append_opcode(GDScriptFunction::OPCODE_ASSIGN_TRUE);
 | 
				
			||||||
	append(p_target);
 | 
						append(p_target);
 | 
				
			||||||
| 
						 | 
					@ -1579,9 +1583,8 @@ void GDScriptByteCodeGenerator::write_for(const Address &p_variable, bool p_use_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (p_use_conversion) {
 | 
						if (p_use_conversion) {
 | 
				
			||||||
		write_assign_with_conversion(p_variable, temp);
 | 
							write_assign_with_conversion(p_variable, temp);
 | 
				
			||||||
		const GDScriptDataType &type = p_variable.type;
 | 
							if (p_variable.type.can_contain_object()) {
 | 
				
			||||||
		if (type.kind != GDScriptDataType::BUILTIN || type.builtin_type == Variant::ARRAY || type.builtin_type == Variant::DICTIONARY) {
 | 
								clear_address(temp); // Can contain `RefCounted`, so clear it.
 | 
				
			||||||
			write_assign_false(temp); // Can contain RefCounted, so clear it.
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1746,21 +1749,54 @@ void GDScriptByteCodeGenerator::end_block() {
 | 
				
			||||||
	pop_stack_identifiers();
 | 
						pop_stack_identifiers();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void GDScriptByteCodeGenerator::clean_temporaries() {
 | 
					void GDScriptByteCodeGenerator::clear_temporaries() {
 | 
				
			||||||
	List<int>::Element *E = temporaries_pending_clear.front();
 | 
						for (int slot_idx : temporaries_pending_clear) {
 | 
				
			||||||
	while (E) {
 | 
							// The temporary may have been re-used as something else since it was added to the list.
 | 
				
			||||||
		// The temporary may have been re-used as something else than an object
 | 
							// In that case, there's **no** need to clear it.
 | 
				
			||||||
		// since it was added to the list. In that case, there's no need to clear it.
 | 
							if (temporaries[slot_idx].can_contain_object) {
 | 
				
			||||||
		int slot_idx = E->get();
 | 
								clear_address(Address(Address::TEMPORARY, slot_idx)); // Can contain `RefCounted`, so clear it.
 | 
				
			||||||
		const StackSlot &slot = temporaries[slot_idx];
 | 
					 | 
				
			||||||
		if (slot.type == Variant::NIL) {
 | 
					 | 
				
			||||||
			write_assign_false(Address(Address::TEMPORARY, slot_idx));
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		List<int>::Element *next = E->next();
 | 
					 | 
				
			||||||
		E->erase();
 | 
					 | 
				
			||||||
		E = next;
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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() {
 | 
					GDScriptByteCodeGenerator::~GDScriptByteCodeGenerator() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,15 +38,14 @@
 | 
				
			||||||
class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
 | 
					class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
 | 
				
			||||||
	struct StackSlot {
 | 
						struct StackSlot {
 | 
				
			||||||
		Variant::Type type = Variant::NIL;
 | 
							Variant::Type type = Variant::NIL;
 | 
				
			||||||
 | 
							bool can_contain_object = true;
 | 
				
			||||||
		Vector<int> bytecode_indices;
 | 
							Vector<int> bytecode_indices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		StackSlot() = default;
 | 
							StackSlot() = default;
 | 
				
			||||||
		StackSlot(Variant::Type p_type) :
 | 
							StackSlot(Variant::Type p_type, bool p_can_contain_object) :
 | 
				
			||||||
				type(p_type) {}
 | 
									type(p_type), can_contain_object(p_can_contain_object) {}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const static int RESERVED_STACK = 3; // For self, class, and nil.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	struct CallTarget {
 | 
						struct CallTarget {
 | 
				
			||||||
		Address target;
 | 
							Address target;
 | 
				
			||||||
		bool is_new_temporary = false;
 | 
							bool is_new_temporary = false;
 | 
				
			||||||
| 
						 | 
					@ -85,9 +84,11 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
 | 
				
			||||||
	RBMap<StringName, int> local_constants;
 | 
						RBMap<StringName, int> local_constants;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Vector<StackSlot> locals;
 | 
						Vector<StackSlot> locals;
 | 
				
			||||||
 | 
						HashSet<int> dirty_locals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Vector<StackSlot> temporaries;
 | 
						Vector<StackSlot> temporaries;
 | 
				
			||||||
	List<int> used_temporaries;
 | 
						List<int> used_temporaries;
 | 
				
			||||||
	List<int> temporaries_pending_clear;
 | 
						HashSet<int> temporaries_pending_clear;
 | 
				
			||||||
	RBMap<Variant::Type, List<int>> temporaries_pool;
 | 
						RBMap<Variant::Type, List<int>> temporaries_pool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	List<GDScriptFunction::StackDebug> stack_debug;
 | 
						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()));
 | 
								ERR_PRINT("Leaving block with non-zero temporary variables: " + itos(used_temporaries.size()));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
							for (int i = current_locals; i < locals.size(); i++) {
 | 
				
			||||||
 | 
								dirty_locals.insert(i + GDScriptFunction::FIXED_ADDRESSES_MAX);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		locals.resize(current_locals);
 | 
							locals.resize(current_locals);
 | 
				
			||||||
		if (debug_stack) {
 | 
							if (debug_stack) {
 | 
				
			||||||
			for (const KeyValue<StringName, int> &E : block_identifiers) {
 | 
								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_or_get_name(const StringName &p_name) override;
 | 
				
			||||||
	virtual uint32_t add_temporary(const GDScriptDataType &p_type) override;
 | 
						virtual uint32_t add_temporary(const GDScriptDataType &p_type) override;
 | 
				
			||||||
	virtual void pop_temporary() 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 start_parameters() override;
 | 
				
			||||||
	virtual void end_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_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(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_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_true(const Address &p_target) override;
 | 
				
			||||||
	virtual void write_assign_false(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;
 | 
						virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src, bool p_use_conversion) override;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,7 +73,9 @@ public:
 | 
				
			||||||
	virtual uint32_t add_or_get_name(const StringName &p_name) = 0;
 | 
						virtual uint32_t add_or_get_name(const StringName &p_name) = 0;
 | 
				
			||||||
	virtual uint32_t add_temporary(const GDScriptDataType &p_type) = 0;
 | 
						virtual uint32_t add_temporary(const GDScriptDataType &p_type) = 0;
 | 
				
			||||||
	virtual void pop_temporary() = 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 start_parameters() = 0;
 | 
				
			||||||
	virtual void end_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_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(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_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_true(const Address &p_target) = 0;
 | 
				
			||||||
	virtual void write_assign_false(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;
 | 
						virtual void write_assign_default_parameter(const Address &dst, const Address &src, bool p_use_conversion) = 0;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1826,7 +1826,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.");
 | 
						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;
 | 
						List<GDScriptCodeGenerator::Address> addresses;
 | 
				
			||||||
	for (int i = 0; i < p_block->locals.size(); i++) {
 | 
						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) {
 | 
							if (p_block->locals[i].type == GDScriptParser::SuiteNode::Local::PARAMETER || p_block->locals[i].type == GDScriptParser::SuiteNode::Local::FOR_VARIABLE) {
 | 
				
			||||||
| 
						 | 
					@ -1838,27 +1838,25 @@ List<GDScriptCodeGenerator::Address> GDScriptCompiler::_add_locals_in_block(Code
 | 
				
			||||||
	return addresses;
 | 
						return addresses;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Avoid keeping in the stack long-lived references to objects, which may prevent RefCounted objects from being freed.
 | 
					// 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) {
 | 
					void GDScriptCompiler::_clear_block_locals(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_locals) {
 | 
				
			||||||
	for (const List<GDScriptCodeGenerator::Address>::Element *E = p_addresses.front(); E; E = E->next()) {
 | 
						for (const GDScriptCodeGenerator::Address &local : p_locals) {
 | 
				
			||||||
		GDScriptDataType type = E->get().type;
 | 
							if (local.type.can_contain_object()) {
 | 
				
			||||||
		// If not an object and cannot contain an object, no need to clear.
 | 
								codegen.generator->clear_address(local);
 | 
				
			||||||
		if (type.kind != GDScriptDataType::BUILTIN || type.builtin_type == Variant::ARRAY || type.builtin_type == Variant::DICTIONARY) {
 | 
					 | 
				
			||||||
			codegen.generator->write_assign_false(E->get());
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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;
 | 
						Error err = OK;
 | 
				
			||||||
	GDScriptCodeGenerator *gen = codegen.generator;
 | 
						GDScriptCodeGenerator *gen = codegen.generator;
 | 
				
			||||||
	List<GDScriptCodeGenerator::Address> block_locals;
 | 
						List<GDScriptCodeGenerator::Address> block_locals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gen->clean_temporaries();
 | 
						gen->clear_temporaries();
 | 
				
			||||||
	codegen.start_block();
 | 
						codegen.start_block();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (p_add_locals) {
 | 
						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++) {
 | 
						for (int i = 0; i < p_block->statements.size(); i++) {
 | 
				
			||||||
| 
						 | 
					@ -1873,7 +1871,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 | 
				
			||||||
			case GDScriptParser::Node::MATCH: {
 | 
								case GDScriptParser::Node::MATCH: {
 | 
				
			||||||
				const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s);
 | 
									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.
 | 
									// Evaluate the match expression.
 | 
				
			||||||
				GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script));
 | 
									GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script));
 | 
				
			||||||
| 
						 | 
					@ -1914,7 +1912,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 | 
				
			||||||
					codegen.start_block(); // Create an extra block around for binds.
 | 
										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.
 | 
										// 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
 | 
					#ifdef DEBUG_ENABLED
 | 
				
			||||||
					// Add a newline before each branch, since the debugger needs those.
 | 
										// Add a newline before each branch, since the debugger needs those.
 | 
				
			||||||
| 
						 | 
					@ -1961,7 +1959,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 | 
				
			||||||
						return err;
 | 
											return err;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					_clear_addresses(codegen, branch_locals);
 | 
										_clear_block_locals(codegen, branch_locals);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					codegen.end_block(); // Get out of extra block.
 | 
										codegen.end_block(); // Get out of extra block.
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
| 
						 | 
					@ -2003,7 +2001,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 | 
				
			||||||
			case GDScriptParser::Node::FOR: {
 | 
								case GDScriptParser::Node::FOR: {
 | 
				
			||||||
				const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s);
 | 
									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));
 | 
									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));
 | 
									gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype(), codegen.script));
 | 
				
			||||||
| 
						 | 
					@ -2021,14 +2020,21 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				gen->write_for(iterator, for_n->use_conversion_assign);
 | 
									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) {
 | 
									if (err) {
 | 
				
			||||||
					return err;
 | 
										return err;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				gen->write_endfor();
 | 
									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;
 | 
								} break;
 | 
				
			||||||
			case GDScriptParser::Node::WHILE: {
 | 
								case GDScriptParser::Node::WHILE: {
 | 
				
			||||||
				const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s);
 | 
									const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s);
 | 
				
			||||||
| 
						 | 
					@ -2046,12 +2052,19 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 | 
				
			||||||
					codegen.generator->pop_temporary();
 | 
										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) {
 | 
									if (err) {
 | 
				
			||||||
					return err;
 | 
										return err;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				gen->write_endwhile();
 | 
									gen->write_endwhile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									_clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit.
 | 
				
			||||||
			} break;
 | 
								} break;
 | 
				
			||||||
			case GDScriptParser::Node::BREAK: {
 | 
								case GDScriptParser::Node::BREAK: {
 | 
				
			||||||
				gen->write_break();
 | 
									gen->write_break();
 | 
				
			||||||
| 
						 | 
					@ -2134,21 +2147,16 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 | 
				
			||||||
						codegen.generator->pop_temporary();
 | 
											codegen.generator->pop_temporary();
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					initialized = true;
 | 
										initialized = true;
 | 
				
			||||||
				} else if (local_type.has_type) {
 | 
									} else if ((local_type.has_type && local_type.kind == GDScriptDataType::BUILTIN) || codegen.generator->is_local_dirty(local)) {
 | 
				
			||||||
					// Initialize with default for type.
 | 
										// Initialize with default for the type. Built-in types must always be cleared (they cannot be `null`).
 | 
				
			||||||
					if (local_type.has_container_element_type(0)) {
 | 
										// Objects and untyped variables are assigned to `null` only if the stack address has been re-used and not cleared.
 | 
				
			||||||
						codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
 | 
										codegen.generator->clear_address(local);
 | 
				
			||||||
						initialized = true;
 | 
										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`.
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// 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) {
 | 
									if (!initialized && p_block->is_in_loop) {
 | 
				
			||||||
					codegen.generator->write_construct(local, Variant::NIL, Vector<GDScriptCodeGenerator::Address>());
 | 
										codegen.generator->clear_address(local);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			} break;
 | 
								} break;
 | 
				
			||||||
			case GDScriptParser::Node::CONSTANT: {
 | 
								case GDScriptParser::Node::CONSTANT: {
 | 
				
			||||||
| 
						 | 
					@ -2180,11 +2188,11 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
 | 
				
			||||||
			} break;
 | 
								} break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		gen->clean_temporaries();
 | 
							gen->clear_temporaries();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (p_add_locals && p_reset_locals) {
 | 
						if (p_add_locals && p_clear_locals) {
 | 
				
			||||||
		_clear_addresses(codegen, block_locals);
 | 
							_clear_block_locals(codegen, block_locals);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	codegen.end_block();
 | 
						codegen.end_block();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -153,9 +153,9 @@ class GDScriptCompiler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false);
 | 
						GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false);
 | 
				
			||||||
	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);
 | 
						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);
 | 
						List<GDScriptCodeGenerator::Address> _add_block_locals(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
 | 
				
			||||||
	void _clear_addresses(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_addresses);
 | 
						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_reset_locals = true);
 | 
						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 *_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);
 | 
						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);
 | 
						Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -360,6 +360,13 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				incr += 3;
 | 
									incr += 3;
 | 
				
			||||||
			} break;
 | 
								} break;
 | 
				
			||||||
 | 
								case OPCODE_ASSIGN_NULL: {
 | 
				
			||||||
 | 
									text += "assign ";
 | 
				
			||||||
 | 
									text += DADDR(1);
 | 
				
			||||||
 | 
									text += " = null";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									incr += 2;
 | 
				
			||||||
 | 
								} break;
 | 
				
			||||||
			case OPCODE_ASSIGN_TRUE: {
 | 
								case OPCODE_ASSIGN_TRUE: {
 | 
				
			||||||
				text += "assign ";
 | 
									text += "assign ";
 | 
				
			||||||
				text += DADDR(1);
 | 
									text += DADDR(1);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,17 +78,17 @@ public:
 | 
				
			||||||
				if (valid && builtin_type == Variant::ARRAY && has_container_element_type(0)) {
 | 
									if (valid && builtin_type == Variant::ARRAY && has_container_element_type(0)) {
 | 
				
			||||||
					Array array = p_variant;
 | 
										Array array = p_variant;
 | 
				
			||||||
					if (array.is_typed()) {
 | 
										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();
 | 
											Variant::Type array_builtin_type = (Variant::Type)array.get_typed_builtin();
 | 
				
			||||||
						StringName array_native_type = array.get_typed_class_name();
 | 
											StringName array_native_type = array.get_typed_class_name();
 | 
				
			||||||
						Ref<Script> array_script_type_ref = array.get_typed_script();
 | 
											Ref<Script> array_script_type_ref = array.get_typed_script();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						if (array_script_type_ref.is_valid()) {
 | 
											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()) {
 | 
											} 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 {
 | 
											} 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 {
 | 
										} else {
 | 
				
			||||||
						valid = false;
 | 
											valid = false;
 | 
				
			||||||
| 
						 | 
					@ -147,6 +147,25 @@ public:
 | 
				
			||||||
		return false;
 | 
							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) {
 | 
						void set_container_element_type(int p_index, const GDScriptDataType &p_element_type) {
 | 
				
			||||||
		ERR_FAIL_COND(p_index < 0);
 | 
							ERR_FAIL_COND(p_index < 0);
 | 
				
			||||||
		while (p_index >= container_element_types.size()) {
 | 
							while (p_index >= container_element_types.size()) {
 | 
				
			||||||
| 
						 | 
					@ -218,6 +237,7 @@ public:
 | 
				
			||||||
		OPCODE_SET_STATIC_VARIABLE, // Only for GDScript.
 | 
							OPCODE_SET_STATIC_VARIABLE, // Only for GDScript.
 | 
				
			||||||
		OPCODE_GET_STATIC_VARIABLE, // Only for GDScript.
 | 
							OPCODE_GET_STATIC_VARIABLE, // Only for GDScript.
 | 
				
			||||||
		OPCODE_ASSIGN,
 | 
							OPCODE_ASSIGN,
 | 
				
			||||||
 | 
							OPCODE_ASSIGN_NULL,
 | 
				
			||||||
		OPCODE_ASSIGN_TRUE,
 | 
							OPCODE_ASSIGN_TRUE,
 | 
				
			||||||
		OPCODE_ASSIGN_FALSE,
 | 
							OPCODE_ASSIGN_FALSE,
 | 
				
			||||||
		OPCODE_ASSIGN_TYPED_BUILTIN,
 | 
							OPCODE_ASSIGN_TYPED_BUILTIN,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -234,6 +234,7 @@ void (*type_init_function_table[])(Variant *) = {
 | 
				
			||||||
		&&OPCODE_SET_STATIC_VARIABLE,                  \
 | 
							&&OPCODE_SET_STATIC_VARIABLE,                  \
 | 
				
			||||||
		&&OPCODE_GET_STATIC_VARIABLE,                  \
 | 
							&&OPCODE_GET_STATIC_VARIABLE,                  \
 | 
				
			||||||
		&&OPCODE_ASSIGN,                               \
 | 
							&&OPCODE_ASSIGN,                               \
 | 
				
			||||||
 | 
							&&OPCODE_ASSIGN_NULL,                          \
 | 
				
			||||||
		&&OPCODE_ASSIGN_TRUE,                          \
 | 
							&&OPCODE_ASSIGN_TRUE,                          \
 | 
				
			||||||
		&&OPCODE_ASSIGN_FALSE,                         \
 | 
							&&OPCODE_ASSIGN_FALSE,                         \
 | 
				
			||||||
		&&OPCODE_ASSIGN_TYPED_BUILTIN,                 \
 | 
							&&OPCODE_ASSIGN_TYPED_BUILTIN,                 \
 | 
				
			||||||
| 
						 | 
					@ -1256,6 +1257,16 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			DISPATCH_OPCODE;
 | 
								DISPATCH_OPCODE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								OPCODE(OPCODE_ASSIGN_NULL) {
 | 
				
			||||||
 | 
									CHECK_SPACE(2);
 | 
				
			||||||
 | 
									GET_VARIANT_PTR(dst, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									*dst = Variant();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									ip += 2;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								DISPATCH_OPCODE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			OPCODE(OPCODE_ASSIGN_TRUE) {
 | 
								OPCODE(OPCODE_ASSIGN_TRUE) {
 | 
				
			||||||
				CHECK_SPACE(2);
 | 
									CHECK_SPACE(2);
 | 
				
			||||||
				GET_VARIANT_PTR(dst, 0);
 | 
									GET_VARIANT_PTR(dst, 0);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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)
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					GDTEST_OK
 | 
				
			||||||
 | 
					<null>
 | 
				
			||||||
 | 
					<null>
 | 
				
			||||||
 | 
					<null>
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue