Skip to content

Regressions from PR #1044 and #1045 #1119

@dsnopek

Description

@dsnopek

These two recently merged PRs have led to a serious regression:

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:

  1. We could change Godot to call virtual functions with the encoding that godot-cpp expects after the PR (ie. passing Object *), and changing Godot's PtrToArgs<> 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?)
  2. We could change how the GDScript VM calls methods so that it encodes Object * as Object ** 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?
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThis has been identified as a bugregression

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions