Open Kev opened 2 years ago
Apologies, a screwup on my side meant I was running old code. It only crashes if I use the return style, not if I use the pass style. i.e.
set_mesh(surfaceTool->commit());
crashes and
godot::Ref<godot::ArrayMesh> arrayMesh;
arrayMesh.instantiate();
surfaceTool->commit(arrayMesh);
set_mesh(arrayMesh);
does not. Possibly lending weight to Zylann's theory on Discord that this could be down to Ref
being freed early through the bridge.
I'll update the original description now.
The following happens:
Calling surfaceTool->commit()
creates a Ref<ArrayMesh>
with an internal nullptr as reference.
Ref<ArrayMesh> commit(const Ref<ArrayMesh> &existing = nullptr, uint32_t flags = 0);
Variable existing
of type Ref<ArrayMesh>
contains (as said before) a nullptr but is still being accessed by existing->_owner
. operator->()
is overriden in class Ref<>
and returns the internal pointer (variable reference) which is a nullptr.
So this is simply a nullpointer access. In step 3 it will finally crash.
Ref<ArrayMesh> SurfaceTool::commit(const Ref<ArrayMesh> &existing, uint32_t flags) {
static GDNativeMethodBindPtr ___method_bind = internal::gdn_interface->classdb_get_method_bind("SurfaceTool", "commit", 4107864055);
CHECK_METHOD_BIND_RET(___method_bind, Ref<ArrayMesh>());
int64_t flags_encoded;
PtrToArg<int64_t>::encode(flags, &flags_encoded);
return Ref<ArrayMesh>::___internal_constructor(internal::_call_native_mb_ret_obj<ArrayMesh>(___method_bind, _owner, existing->_owner, &flags_encoded));
}
call_native_mb_ret_obj<ArrayMesh>(...)
is called. The std::array creation leads to a segmentation fault.
template <class O, class... Args>
O *_call_native_mb_ret_obj(const GDNativeMethodBindPtr mb, void *instance, const Args &...args) {
GodotObject *ret = nullptr;
std::array<const GDNativeTypePtr, sizeof...(Args)> mb_args = { { (const GDNativeTypePtr)args... } };
internal::gdn_interface->object_method_bind_ptrcall(mb, instance, mb_args.data(), &ret);
if (ret == nullptr) {
return nullptr;
}
return reinterpret_cast<O *>(internal::gdn_interface->object_get_instance_binding(ret, internal::token, &O::___binding_callbacks));
}
Solution (for engine devs):
In step 2 has to be a nullpointer check on the internal reference of variable existing
. If the internal pointer is null, then avoid the operator->()
access and pass a null pointer.
Something like: existing.is_valid() ? existing->_owner : nullptr)
As far as I have seen, this also affects other functions that work with default arguments.
Can you still reproduce this in 4.0.3 and 4.1-beta3 or later?
Can you still reproduce this in 4.0.3 and 4.1-beta3 or later?
Yes, can still reproduce this issue with Godot 4.2.1-stable (Arch Linux) in combination with Rust/Gdext. Not (yet) tested with GDScript or C++.
Godot version
4.0 (various, including b50acebd48d7e45a1444e553a5ea9878ef87592a)
System information
MBP Silicon
Issue description
I get a crash when I call
commit
on a surface tool from C++. I don't know if this is a problem with Ref, with the bridge or with SurfaceTool itself. It sounds a little similar to https://github.com/godotengine/godot/issues/53191 but seems to be distinct.or
depending on your preferred backtrace style.
Steps to reproduce
In
_ready
on a class inheritingMeshInstance3D
, created withauto ground = memnew(Ground());
and inserted into the tree withparentForScenes->call_deferred("add_child", ground);
It only crashes if I use the return style, rather than passing in a mesh. The following does not crash
Minimal reproduction project
No response