Merge pull request from adamscott/make-gdscript-clear-less-prone-to-heap-use-after-free

Resolve `GDScript::clear()` `heap-use-after-free` ASAN errors
This commit is contained in:
Rémi Verschelde 2023-01-09 09:22:39 +01:00
commit b6be2ac621
No known key found for this signature in database
GPG key ID: C3336907360768E1
2 changed files with 58 additions and 37 deletions
modules/gdscript

View file

@ -1307,7 +1307,7 @@ GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) {
}
void GDScript::_get_dependencies(RBSet<GDScript *> &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<StringName, Ref<GDScript>> &E : subclasses) {
p_clear_data->scripts.insert(E.value);
}
subclasses.clear();
// subclasses are also held by constants, clear those as well
for (KeyValue<StringName, Variant> &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;
GDScript::ClearData data;
GDScript::ClearData *clear_data = p_clear_data;
bool is_root = false;
// 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<GDScript *> must_clear_dependencies = get_must_clear_dependencies();
HashMap<GDScript *, ObjectID> must_clear_dependencies_objectids;
// 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());
clear_data->scripts.insert(E);
E->clear(clear_data);
}
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());
}
RBSet<StringName> member_function_names;
for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
member_function_names.insert(E.key);
clear_data->functions.insert(E.value);
}
for (const StringName &E : member_function_names) {
if (member_functions.has(E)) {
memdelete(member_functions[E]);
}
}
member_function_names.clear();
member_functions.clear();
for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) {
clear_data->scripts.insert(E.value.data_type.script_type_ref);
E.value.data_type.script_type_ref = Ref<Script>();
}
if (implicit_initializer) {
memdelete(implicit_initializer);
clear_data->functions.insert(implicit_initializer);
implicit_initializer = nullptr;
}
implicit_initializer = nullptr;
if (implicit_ready) {
memdelete(implicit_ready);
clear_data->functions.insert(implicit_ready);
implicit_ready = nullptr;
}
implicit_ready = nullptr;
_save_orphaned_subclasses();
_save_orphaned_subclasses(clear_data);
#ifdef TOOLS_ENABLED
// Clearing inner class doc, script doc only cleared when the script source deleted.
@ -1474,7 +1473,21 @@ void GDScript::clear() {
_clear_doc();
}
#endif
clearing = false;
// If it's not the root, skip clearing the data
if (is_root) {
// All dependencies have been accounted for
for (GDScriptFunction *E : clear_data->functions) {
memdelete(E);
}
for (Ref<Script> &E : clear_data->scripts) {
Ref<GDScript> gdscr = E;
if (gdscr.is_valid()) {
GDScriptCache::remove_script(gdscr->get_path());
}
}
clear_data->clear();
}
}
GDScript::~GDScript() {

View file

@ -62,7 +62,6 @@ class GDScript : public Script {
bool tool = false;
bool valid = false;
bool reloading = false;
bool skip_dependencies = false;
struct MemberInfo {
int index = 0;
@ -71,6 +70,15 @@ class GDScript : public Script {
GDScriptDataType data_type;
};
struct ClearData {
RBSet<GDScriptFunction *> functions;
RBSet<Ref<Script>> scripts;
void clear() {
functions.clear();
scripts.clear();
}
};
friend class GDScriptInstance;
friend class GDScriptFunction;
friend class GDScriptAnalyzer;
@ -157,7 +165,7 @@ class GDScript : public Script {
bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false, PlaceHolderScriptInstance *p_instance_to_update = nullptr);
void _save_orphaned_subclasses();
void _save_orphaned_subclasses(GDScript::ClearData *p_clear_data);
void _init_rpc_methods_properties();
void _get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const;
@ -180,7 +188,7 @@ protected:
static void _bind_methods();
public:
void clear();
void clear(GDScript::ClearData *p_clear_data = nullptr);
virtual bool is_valid() const override { return valid; }