-
-
Notifications
You must be signed in to change notification settings - Fork 691
Description
These two recently merged PRs have led to a serious regression:
- Fix crash using Object* as parameter #1044
- Fix crash using Ref<T> as parameter #1045 <-- most discussion about the regressions so far has taken place on this one
I'm making this issue to try and consolidate information about the regressions, hopefully leading to a PR to fix them!
Overview
Both of those PRs aim to fix calling methods defined in GDExtension (godot-cpp) from GDScript. Before merging the PRs, calling any non-virtual method that took Object * or Ref<T> as a parameter would lead to a crash.
However, now calling any virtual method that takes an Object * or Ref<T> as a parameter will lead to a crash!
Both PRs change PtrToArg<>::convert() for their respective types, in the same way: where they previously assumed they were receiving an Object **, the PRs switched to the assumption that they are receiving an Object *.
So, it seems that calling a non-virtual method from GDScript passes an Object * and calling a virtual method passes Object **.
Where does the difference come from?
As @zhehangd points out, in the GDScript VM we have
const void **argptrs = call_args_ptr;
for (int i = 0; i < argc; i++) {
GET_INSTRUCTION_ARG(v, i);
argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);
}
GET_INSTRUCTION_ARG(ret, argc + 1);
VariantInternal::initialize(ret, Variant::m_type);
void *ret_opaque = VariantInternal::OP_GET_##m_type(ret);
method->ptrcall(base_obj, argptrs, ret_opaque);For an object, VariantInternal::get_opaque_pointer() returns a plain Object *. Hence, we're encoding an Object * as Object *.
However, when we look at the macros in gdvirtual.gen.inc (which generate the code that calls virtual methods) we've got:
PtrToArg<m_type1>::EncodeT argval1 = arg1;\
GDExtensionConstTypePtr argptrs[1]={&argval1};\
\
PtrToArg<m_ret>::EncodeT ret;\
_gdvirtual_##m_name(_get_extension_instance(),reinterpret_cast<GDExtensionConstTypePtr*>(argptrs),&ret);\
This is a little trickier to decipher. PtrToArg<Object *>::EncodeT is Object *, so it's making a local variable with the Object *. And then it's putting a pointer to that local variable (so, an Object **) into the argptrs array. That appears to be how we're getting the Object **
Possible solutions
Well, first of all, since PtrToArg<> on the godot-cpp side is supposed to round-trip encode/convert the data from PtrToArg<> on the Godot side, they are now out-of-sync. So, no matter what solution we end up pursuing, we should make sure that PtrToArg<> is doing the same thing in both godot-cpp and Godot again.
Here's some possible solutions:
- We could change Godot to call virtual functions with the encoding that godot-cpp expects after the PR (ie. passing
Object *), and changing Godot'sPtrToArgs<>to match godot-cpp's, as well as altering the macros in gdvirtual.gen.inc. This would be a breaking change to GDExtensions ABI (in a way that we don't have a system to provide compatibility), and may affect how C# works? (I really have no idea how the C# binding works, so that's just a guess. If it doesn't call virtual methods through those same macros, then it wouldn't be affected, but I suspect it does?) - We could change how the GDScript VM calls methods so that it encodes
Object *asObject **and then revert the two godot-cpp PRs. However, since GDScript was able to call all sorts of methods just fine (just not non-virtual ones defined in GDExtension), I'm assuming this would break things in other places? - We could have godot-cpp somehow decode
Object *'s differently depending on whether or not the method being called is a virtual method or not. This is the least disruptive change because it's all in godot-cpp, but I haven't tried to implement it yet, so I'm not sure how messy it would be.
What do you think?
We really need some solution to this before Godot 4.1 is released, because godot-cpp is pretty broken at the moment.