From d22199990e66d1ed18e05f10039b01e4f40528dc Mon Sep 17 00:00:00 2001 From: Adam Scott Date: Sat, 7 Jan 2023 10:13:45 -0500 Subject: Resolve `GDScript::clear()` `heap-use-after-free` ASAN errors --- modules/gdscript/gdscript.cpp | 77 +++++++++++++++++++++++++------------------ modules/gdscript/gdscript.h | 14 ++++++-- 2 files changed, 56 insertions(+), 35 deletions(-) diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 8dc47cbfd5..7b79c9cf7e 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1307,7 +1307,7 @@ GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) { } void GDScript::_get_dependencies(RBSet &p_dependencies, const GDScript *p_except) { - if (skip_dependencies || p_dependencies.has(this)) { + if (p_dependencies.has(this)) { return; } p_dependencies.insert(this); @@ -1365,7 +1365,7 @@ GDScript::GDScript() : } } -void GDScript::_save_orphaned_subclasses() { +void GDScript::_save_orphaned_subclasses(GDScript::ClearData *p_clear_data) { struct ClassRefWithName { ObjectID id; String fully_qualified_name; @@ -1381,8 +1381,17 @@ void GDScript::_save_orphaned_subclasses() { } // clear subclasses to allow unused subclasses to be deleted + for (KeyValue> &E : subclasses) { + p_clear_data->scripts.insert(E.value); + } subclasses.clear(); // subclasses are also held by constants, clear those as well + for (KeyValue &E : constants) { + GDScript *gdscr = _get_gdscript_from_variant(E.value); + if (gdscr != nullptr) { + p_clear_data->scripts.insert(gdscr); + } + } constants.clear(); // keep orphan subclass only for subclasses that are still in use @@ -1413,60 +1422,50 @@ void GDScript::_init_rpc_methods_properties() { } } -void GDScript::clear() { +void GDScript::clear(GDScript::ClearData *p_clear_data) { if (clearing) { return; } clearing = true; - RBSet must_clear_dependencies = get_must_clear_dependencies(); - HashMap must_clear_dependencies_objectids; + GDScript::ClearData data; + GDScript::ClearData *clear_data = p_clear_data; + bool is_root = false; - // Log the objectids before clearing, as a cascade of clear could - // remove instances that are still in the clear loop - for (GDScript *E : must_clear_dependencies) { - must_clear_dependencies_objectids.insert(E, E->get_instance_id()); + // If `clear_data` is `nullptr`, it means that it's the root. + // The root is in charge to clear functions and scripts of itself and its dependencies + if (clear_data == nullptr) { + clear_data = &data; + is_root = true; } + RBSet must_clear_dependencies = get_must_clear_dependencies(); for (GDScript *E : must_clear_dependencies) { - Object *obj = ObjectDB::get_instance(must_clear_dependencies_objectids[E]); - if (obj == nullptr) { - continue; - } - - E->skip_dependencies = true; - E->clear(); - E->skip_dependencies = false; - GDScriptCache::remove_script(E->get_path()); + clear_data->scripts.insert(E); + E->clear(clear_data); } - RBSet member_function_names; for (const KeyValue &E : member_functions) { - member_function_names.insert(E.key); - } - for (const StringName &E : member_function_names) { - if (member_functions.has(E)) { - memdelete(member_functions[E]); - } + clear_data->functions.insert(E.value); } - member_function_names.clear(); member_functions.clear(); for (KeyValue &E : member_indices) { + clear_data->scripts.insert(E.value.data_type.script_type_ref); E.value.data_type.script_type_ref = Ref