Open CardboardCarl opened 4 months ago
I'm already working on a potential fix, I'll link the pull request here once I'm finished.
It's worth mentioning that this might be a problem with variant casting validation itself and inspector plugins are just one of the ways the crash is triggered.
If anyone has any thoughts or theories, it'd be appreciated! I'm still trying to figure out how the object is slipping past validation...
There's a double no-no here. If the node has multiple properties of TYPE_ARRAY...
@export var a: Array
@export var b: Array
The same Control node will be bound twice, potentially clobbering both properties' data.
As for the crash, the inspector explicitly deletes all property nodes when it's cleared, bypassing any reference counting. dynamic_cast is performed on a freed pointer, resulting in std::bad_cast. Since Godot doesn't make exceptions, the only thing to do is abort.
A proper fix would be to change add_property_editor() to accept and store RefCounted, which if count is != 1 then ERR_FAIL_EDMSG("usage: add_property_editor(name, MyPropertyEditor.new())"), or better worded.
There's a double no-no here. If the node has multiple properties of TYPE_ARRAY...
@export var a: Array @export var b: Array
The same Control node will be bound twice, potentially clobbering both properties' data.
Good point! I'll have to keep that in mind.
As for the crash, the inspector explicitly deletes all property nodes when it's cleared, bypassing any reference counting. dynamic_cast is performed on a freed pointer, resulting in std::bad_cast. Since Godot doesn't make exceptions, the only thing to do is abort.
I wasn't aware the engine couldn't handle bad casts without crashing. I'm still trying to grasp how the engine's error handling works 😅
A proper fix would be to change add_property_editor() to accept and store RefCounted, which if count is != 1 then ERR_FAIL_EDMSG("usage: add_property_editor(name, MyPropertyEditor.new())"), or better worded.
Good idea, I'm just worried about breaking compatibility. I guess I could add versions of the function that take PackedScenes or Scripts instead and fix the current function to check if the referenced object has already been freed, but the exception occurs just before the native add_property_editor
function is called, so that's gonna make things difficult...
I wasn't aware the engine couldn't handle bad casts without crashing. I'm still trying to grasp how the engine's error handling works
Normally when cast fails it will return nullptr
, no problem. But since it's invalid memory, anything could happen. I was wrong to call it bad_cast come to think of it, it's undefined behavior.
I guess I could add versions of the function that take PackedScenes or Scripts instead and fix the current function to check if the referenced object has already been freed, but the exception occurs just before the native add_property_editor function is called, so that's gonna make things difficult...
Yeah it's a lot of work. It needn't be a complete fix, a documentation change to make it clear "EditorProperty instances are deleted when the inspector is done with them. Don't reuse them!" would certainly be a magnitude easier to both write and get merged.
Tested versions
Reproducible in v4.3.beta[6b281c0c0] and 4.2.2.stable
System information
Godot v4.3.beta (6b281c0c0) - Arch Linux #1 SMP PREEMPT_DYNAMIC Fri, 31 May 2024 15:14:45 +0000 - X11 - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 3050 Laptop GPU - AMD Ryzen 5 5600H with Radeon Graphics (12 Threads)
Issue description
EditorInspectorPlugin's
add_property_editor()
function fails to catch a stale reference from a freed object, resulting in an editor crash when it tries to cast the object before adding it to the inspector. An easy way to trigger this is by deselecting a node with a custom property editor, causing it to be freed when the inspector is hidden, then opening the node's inspector again.instantiating a property like this, although already written unsafe, should at least throw an error:
I'm positive the bug is coming from this function specifically, as when I encountered this crash accidentally I slowly removed my code until the crash stopped happening, and this is where it happened. As to why it happens, I'm not sure. I'm going to investigate it further.
Note: It seems the error is sometimes caught, but not always, as this error is thrown sometimes:
My best guess as to why is that the memory block where the old object was might not have been reallocated and overwritten when the node was selected again, so the inspector cast goes through with little to no corruption despite the inspector node itself being freed.
Here's a (sanitized) stack dump (debug symbols enabled):
Steps to reproduce
preload
anyEditorProperty
script into anEditorInspectorPlugin
withadd_property_editor
without creating a new instance for theEditorProperty
Minimal reproduction project (MRP)
EditorBugExample.zip
Edit: Failed to notice the segfault error code at the top of the stack dump. Whoops!