godot-cpp icon indicating copy to clipboard operation
godot-cpp copied to clipboard

Unable to validate freed Object

Open ashtonmeuser opened this issue 1 year ago • 4 comments

Godot version

4.3

godot-cpp version

4.3 (e298f430b55bdbd9cf3eae9e041354c26d2e9c79)

System information

macOS Sonoma 14.6.1

Issue description

With the removal of UtilityFunctions::is_instance_valid() (https://github.com/godotengine/godot-cpp/pull/1513), it is impossible to validate an Object passed to a GDExtension method as a Variant.

Spawned from this comment. Using ObjectDB::get_instance() as suggested here and here isn't a perfect replacement. Starting with a Variant that represents a freed Object, we're unable to get an object ID in order to use ObjectDB::get_instance(). After ensuring a Variant is of type Object, an operator Object*() call causes a crash.

Previously, UtilityFunctions::is_instance_valid() could be used to validate an object. It's possible to use variant.stringify() == "<Freed Object>" as a hacky workaround although obviously not ideal.

Steps to reproduce

Add the following method to a GDExtension class.

// Must validate supplied object
godot_error MyLib::dummy(const Variant v) {
  // Side effects for other Variant types would necessitate accepting a Variant rather than Object*
  if (v.get_type() != Variant::OBJECT) return ERR_CANT_CREATE;
  // if (v.stringify() == "<Freed Object>") return ERR_CANT_CREATE; // Hacky solution
  auto o = v.operator Object*(); // Crashes here if object was previously freed
  if (o == NULL) return ERR_CANT_CREATE;
  auto id = o->get_instance_id();
  auto x = ObjectDB::get_instance(id);
  if (x == NULL) return ERR_CANT_CREATE;
  return OK;
}

Use the GDExtension function via GDScript as follows.

var o = Object.new()
var lib = MyLib.new()
assert(lib.dummy(o) == OK) # Expect object to be valid
o.free()
assert(lib.dummy(o) != OK) # Expect object to be invalid

Minimal reproduction project

N/A

ashtonmeuser avatar Sep 16 '24 19:09 ashtonmeuser

Thanks!

Ack, yeah, in the case you give here (a bound function that takes a Variant argument), I think we are, in fact, missing something. :-(

The problem is that Variant::operator ObjectID() is implemented using Variant::operator Object*(), which will crash if the object is already freed. We need a way to get the ObjectID directly, which I don't think we actually have at the moment. (It would also would be nice to implement Variant::get_validated_object() in godot-cpp, that provides a nice API.)

I'm not sure how we can add that without adding something new in Godot, and so, this may need to be a Godot 4.4 thing. And if that's the case, then I think we'll probably need to revert the removal of is_instance_valid() until Godot 4.4 :-/

This is unfortunate, because I don't think it's very common to accept Variant arguments and check their validity. I think the "trap" is much more common, ie where folks accept and hold on to an Object *, and then later call is_instance_valid() on it, thinking that will actually check if the instance is valid (rather than crashing if the instance is invalid, which is what it'll actually do).

dsnopek avatar Sep 16 '24 20:09 dsnopek

@dsnopek Agreed that something like Variant::get_validated_object() would be useful. Also agree that while is_instance_valid() is a bit of a footgun, it's better than lacking an equivalent tool in v4.3.

A more realistic example than I provided above (and the case that revealed this issue to me) is a method that accepts a higher-order data structure e.g. Dictionary that predicate a Variant but is expected to contain an Object.

ashtonmeuser avatar Sep 16 '24 21:09 ashtonmeuser

I just posted PR https://github.com/godotengine/godot-cpp/pull/1592 which reverts the removal of is_instance_valid(), targeting Godot 4.3 and earlier.

And also PR https://github.com/godotengine/godot-cpp/pull/1591 which adds what we need to support this use-case without is_instance_valid() that we can use in Godot 4.4+

dsnopek avatar Sep 17 '24 15:09 dsnopek

I also ran into this. I tried to use Variant::get_validated_object to match core, and it was not available.

aaronfranke avatar Sep 18 '24 16:09 aaronfranke

This should be fixed now since https://github.com/godotengine/godot-cpp/pull/1591 and https://github.com/godotengine/godot-cpp/pull/1592 have been merged

dsnopek avatar Jan 21 '25 17:01 dsnopek