-
-
Notifications
You must be signed in to change notification settings - Fork 584
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
Fix use-after-free at shutdown #892
Conversation
daadd05
to
0a56141
Compare
This might highlight an deeper issue, I wonder: This problem occurs because an extension library needed to represent a Godot object through its wrappers, library side. And to do so, had to store a pointer to those wrappers inside Godot objects so they can be easily retrieved when passed again. If so, I guess it confines extensions to the flow of loading on startup, no unload and no hot-reload possible, then unload only when Godot shuts down. And any objects that "came in contact" (and are not singletons, with this PR changes) really should better be freed while the library is still loaded. What about And what if another extension library B was holding a ref on an object that came into contact with library A? That object might get destroyed (refcount) later too from A's perspective. Each time, the crash cause being that Godot will attempt to call into [unloaded] library A's function to free instance bindings that said library isn't keeping track of. |
(I'm fairly new to the Godot codebase, so take all this with a grain of salt.)
I would imagine so, yes.
Yes, the thing that does throw a spanner into this whole presumption is the exposed bindings of
As far as I can tell, it gets deleted pretty early in the cleanup, before extensions are unloaded.
So long as the object itself doesn't outlast either of the two libraries you'd be fine I suppose. I imagine most types that could be shared between the two (assuming the libraries are always communicating through the engine) would end up living in some sort of manager within the engine that gets deleted before the extensions do. Even stuff like custom singletons registered through Again, I'm fairly new to all this, and I haven't looked too deeply into it, so I'd be happy to have someone confirm/deny the statements here. |
(emphasis mine) Note however that without extension unloading/reloading, the main workflow of game development through extensions is obstructed, see godotengine/godot#66231. Seems like getting hot-reloading working reliable might be a huge endeavor, but to me that's even more of a reason to start sooner rather than later 😉 So, regarding the problem here:
I'm not very familiar with the inner workings of godot-cpp, could you explain why the library permanently stores this instance binding inside the texture? As far as I know, this is not required to access the texture via GDExtension?
Could this be implemented on Godot side at all, or is it just the extension library that knows about these bindings? |
GodotCpp wraps object pointers into classes that have the same API as the real class, such that they act the same as if Godot had a C++ API. But instances of these wrappers are not the instance, the real object is instead stored in a private member, via a C-style opaque pointer, because GDExtension's API is C. The wrapper is not absolutely required, but then all you have is a C-style pointer.
That's not always the case though, because of
Godot might be able to do it. But doing it on library side might be more direct. It depends how implementing that turns out to be. Not every library needs it also, someone could use C directly, or a language that happens to not need "instance bindings". I was thinking of a simple pool where each instance binding pointer is stored in a growing array, and classes would keep track of the index if they need to unregister it. And when the library unloads, that array is iterated and all valid slots would call the function to free those instance bindings. That would also have to be protected by mutex the first time instance bindings are created, and when they are removed. An alternative would be to exploit de-initialization levels: after the very last level, Godot could enforce that all libraries have been de-initialized and every Object has been destroyed, so we can unload each library. Although such approach doesn't allow unloading or reloading just one library at runtime. |
0a56141
to
90d69fa
Compare
6c0e5ec
to
4fb0646
Compare
4fb0646
to
9a7bc10
Compare
9a7bc10
to
84565e4
Compare
84565e4
to
02714f0
Compare
02714f0
to
d48c16e
Compare
Fixes #889.
Depends on godotengine/godot#67285.
This PR adds a local static struct instance to the generated singleton getters, which has its destructor called when the extension library unloads. This allows the singleton bindings to be cleared and deallocated through
gdn_interface->object_clear_instance_binding
before the underlying singletons gets destroyed, thereby preventing the use-after-free described in the issue.To help with the case where the extension library unload happens after a singleton is destroyed, and thereby making
singleton_obj
stale, I've also added a*_destroyed
bool that gets set in the bindings destructor.Here is an example of what the generated singleton bindings would look like: