Fix GDScript leak avoidance (3.2) #42323
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Commit message:
For the records, this issue was much more complicated than it seems. Let's tell the full story:
On the surface, we could just say that just if we had been enforcing
explicit
, we would have detected that unintended conversion toVariant
.However, we can ask, why
Variant
wasn't doing the right thing? Answer:Variant
has a constructor forObject *
that is intended to be used only for non-Reference
classes. ForReference
s you have to initialize it from aRefPtr
.Before the dangling
Variant
fix, the only issue with using theObject *
constructor for aReference
was that theVariant
could become dangling at a later point, when that shouldn't happen at all forReference
: the object would have just been treated as non ref-counted, so that its reference count wouldn't have been updated. If it was freed, yet theVariant
would still believe it was around.After the dangling
Variant
fix, in debug mode even non-Reference
s have a reference count mechanism. So if you construct aVariant
with aReference
via theObject *
constructor, you run into a very weird situation: the ref counting mechanism ofReference
wouldn't be used at all, but the one for the other kinds of objects would be! And that's indeed what was happening in this bug: theVariant
was initialized with aScript
, but treated as a non-Reference
, so when it was being asked back to be converted toRefPtr
it was returning null, since that conversion only applies toReference
s. And so the crash.At first I thought that to make the fix complete,
Variant
should have anObject *
constructor like this:That way it would realize
p_object
is aReference
and do the right thing.But... The
GDScript
class is already taking advantage of the "feature": it has aVariant _static_ref
member which is initialized tothis
. IfVariant
is fixed as in the code snippet above, a cyclic reference betweenGDscript
and its member_static_ref
is created, that makes it impossible for the script resource to be released ever.And I haven't been able to come up with a reasonable fix that doesn't add hacks to fix that. Discarded ideas have been messing with the reference count, and creating special utility, high danger functions to initialize and clear a
Reference
Variant
without touching the reference count, to letGDScript::_static_ref
don't worry about the lifetime of the script by itself, because, as a member, it's lifetime is tied to the one of the script.So my take has been to do the easy fix, document the problem and leave the door open for a deeper fix that addresses the whole problem up to its roots.
NOTE: Godot 4.0 is already fine.
Fixes #42305.