Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve GDScript::clear() heap-use-after-free ASAN errors #71028

Conversation

adamscott
Copy link
Member

@adamscott adamscott commented Jan 7, 2023

Depending of the code changed, ASAN would complain about heap-use-after-free errors.

That happens to #69590, the build fails, but the code is valid. It just shows a flaw in the current GDScript::clear() implementation.

This PR makes it so that the parent script collects every functions and scripts about to be cleaned by the descendants, then cleans them itself, rather than having to deal with deleted references (and to deal with heap-use-after-free instances).

Removed the now useless GDScript::skip_dependencies boolean.

@adamscott adamscott requested a review from a team as a code owner January 7, 2023 15:19
@akien-mga akien-mga added this to the 4.0 milestone Jan 7, 2023
@adamscott adamscott force-pushed the make-gdscript-clear-less-prone-to-heap-use-after-free branch from 29a5d6d to d5940e1 Compare January 7, 2023 15:56
@adamscott adamscott changed the title Make GDScript::clear() less prone to heap-use-after-free ASAN error Resolve GDScript::clear() heap-use-after-free ASAN errors Jan 7, 2023
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to consider situations like const EnumNewName = SomeOtherClass.NamedEnum? Does this count as some sort of weak reference?

Copy link
Member Author

@adamscott adamscott Jan 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cannot have enums without a GDScript instance.

What I mean is that SomeOtherClass (and its components) is a dependency of ThisClass

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I trust what you say! It just occurred to me as someone who isn't knowledgeable about this code :)

So basically, SomeOtherClass had to show up somewhere in the code, and therefore has already been taken into account somewhere else.

What happens with

class_name A
  const B = preload("b.gd")

I take it it is also accounted for as dependency by this code? Or is it taken to be a subclass?

Let me know if this isn't useful - this is me taking shots in the dark trying to make sense of the code :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens with

class_name A
  const B = preload("b.gd")

I take it it is also accounted for as dependency by this code? Or is it taken to be a subclass?

From that example, B is a dependency of A

Copy link
Member Author

@adamscott adamscott Jan 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is it taken to be a subclass?

A subclass is a class within a gdscript file. It has it's own GDScript instance.

class_name ThisIsNotASubclass

class ThisIsASubclass:
    pass

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hahah, the naming of all this is messing with me :) I would call that an inner class, whereas a subclass would be class B extends A!

Sounds like you've gotten all this figured out though, so I'm happy to see this merged! The changes make sense!

// 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());
GDScript::ClearData data;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment in this section of code stating that this defines whether this is the "parent" which is responsible for destroying the scripts later would make sense, I think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, maybe the name initialized_clear_data ought to be is_responsible_for_clearing or something that's a bit more obvious?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the code.

@adamscott adamscott force-pushed the make-gdscript-clear-less-prone-to-heap-use-after-free branch 2 times, most recently from 193c644 to d83efbb Compare January 7, 2023 16:46
@adamscott adamscott force-pushed the make-gdscript-clear-less-prone-to-heap-use-after-free branch from d83efbb to d221999 Compare January 7, 2023 16:51
@akien-mga akien-mga merged commit b6be2ac into godotengine:master Jan 9, 2023
@akien-mga
Copy link
Member

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants