From 727066fe1f70198ffd02c7a350e62c4c1c61b42c Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Fri, 26 Sep 2025 18:15:08 +0800 Subject: [PATCH] Free script and extension instance before object deconstructing Co-Authored-By: Lukas Tenbrink --- core/object/object.cpp | 71 ++++++++++++++++++--------------- tests/core/object/test_object.h | 14 ++++--- 2 files changed, 46 insertions(+), 39 deletions(-) diff --git a/core/object/object.cpp b/core/object/object.cpp index a5f6f48b7f8..a8bea5a9b31 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -254,11 +254,45 @@ Object::Connection::Connection(const Variant &p_variant) { bool Object::_predelete() { _predelete_ok = 1; notification(NOTIFICATION_PREDELETE, true); - if (_predelete_ok) { - _class_name_ptr = nullptr; // Must restore, so constructors/destructors have proper class name access at each stage. - notification(NOTIFICATION_PREDELETE_CLEANUP, true); + if (!_predelete_ok) { + return false; } - return _predelete_ok; + + _class_name_ptr = nullptr; // Must restore, so constructors/destructors have proper class name access at each stage. + notification(NOTIFICATION_PREDELETE_CLEANUP, true); + + // Destruction order starts with the most derived class, and progresses towards the base Object class: + // Script subclasses -> GDExtension subclasses -> C++ subclasses -> Object + if (script_instance) { + memdelete(script_instance); + } + script_instance = nullptr; + + if (_extension) { +#ifdef TOOLS_ENABLED + if (_extension->untrack_instance) { + _extension->untrack_instance(_extension->tracking_userdata, this); + } +#endif + if (_extension->free_instance) { + _extension->free_instance(_extension->class_userdata, _extension_instance); + } + _extension = nullptr; + _extension_instance = nullptr; + } +#ifdef TOOLS_ENABLED + else if (_instance_bindings != nullptr) { + Engine *engine = Engine::get_singleton(); + GDExtensionManager *gdextension_manager = GDExtensionManager::get_singleton(); + if (engine && gdextension_manager && engine->is_extension_reloading_enabled()) { + for (uint32_t i = 0; i < _instance_binding_count; i++) { + gdextension_manager->untrack_instance_binding(_instance_bindings[i].token, this); + } + } + } +#endif + + return true; } void Object::cancel_free() { @@ -2273,35 +2307,6 @@ void Object::assign_class_name_static(const Span &p_name, StringName &r_ta } Object::~Object() { - if (script_instance) { - memdelete(script_instance); - } - script_instance = nullptr; - - if (_extension) { -#ifdef TOOLS_ENABLED - if (_extension->untrack_instance) { - _extension->untrack_instance(_extension->tracking_userdata, this); - } -#endif - if (_extension->free_instance) { - _extension->free_instance(_extension->class_userdata, _extension_instance); - } - _extension = nullptr; - _extension_instance = nullptr; - } -#ifdef TOOLS_ENABLED - else if (_instance_bindings != nullptr) { - Engine *engine = Engine::get_singleton(); - GDExtensionManager *gdextension_manager = GDExtensionManager::get_singleton(); - if (engine && gdextension_manager && engine->is_extension_reloading_enabled()) { - for (uint32_t i = 0; i < _instance_binding_count; i++) { - gdextension_manager->untrack_instance_binding(_instance_bindings[i].token, this); - } - } - } -#endif - if (_emitting) { //@todo this may need to actually reach the debugger prioritarily somehow because it may crash before ERR_PRINT(vformat("Object '%s' was freed or unreferenced while a signal is being emitted from it. Try connecting to the signal using 'CONNECT_DEFERRED' flag, or use queue_free() to free the object (if this object is a Node) to avoid this error and potential crashes.", to_string())); diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h index 0ca0b17fd2a..be98990884f 100644 --- a/tests/core/object/test_object.h +++ b/tests/core/object/test_object.h @@ -215,12 +215,12 @@ TEST_CASE("[Object] Construction") { } TEST_CASE("[Object] Script instance property setter") { - Object object; + Object *object = memnew(Object); _MockScriptInstance *script_instance = memnew(_MockScriptInstance); - object.set_script_instance(script_instance); + object->set_script_instance(script_instance); bool valid = false; - object.set("some_name", 100, &valid); + object->set("some_name", 100, &valid); CHECK(valid); Variant actual_value; CHECK_MESSAGE( @@ -229,20 +229,22 @@ TEST_CASE("[Object] Script instance property setter") { CHECK_MESSAGE( actual_value == Variant(100), "The returned value should equal the one which was set by the object."); + memdelete(object); } TEST_CASE("[Object] Script instance property getter") { - Object object; + Object *object = memnew(Object); _MockScriptInstance *script_instance = memnew(_MockScriptInstance); script_instance->set("some_name", 100); // Make sure script instance has the property - object.set_script_instance(script_instance); + object->set_script_instance(script_instance); bool valid = false; - const Variant &actual_value = object.get("some_name", &valid); + const Variant &actual_value = object->get("some_name", &valid); CHECK(valid); CHECK_MESSAGE( actual_value == Variant(100), "The returned value should equal the one which was set by the script instance."); + memdelete(object); } TEST_CASE("[Object] Built-in property setter") {