Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent instantiating classes that aren't exposed #102440

Conversation

dsnopek
Copy link
Contributor

@dsnopek dsnopek commented Feb 4, 2025

Originally from #99534

Currently, if you call ClassDB::can_instantiate("...") for an internal class, it will return true, but most normal ways of instantiating the class in GDScript, GDExtension or C# won't work. However, they still can be created via ClassDB::instantiate("..."). There's also no indication to GDExtension or scripting from ClassDB that these classes can't be instantiated normally because we don't expose ClassDB::class_is_exposed().

For example:

func _ready() -> void:
	# Prints: true
	print(ClassDB.class_exists("AnimationNodeStartState"))

	# Prints: true
	print(ClassDB.can_instantiate("AnimationNodeStartState"))

	# Prints: <AnimationNodeStartState#12345>
	print(ClassDB.instantiate("AnimationNodeStartState"))

	# ERROR!
	AnimationNodeStartState.new()

Do we even want internal classes to be instantiatable via ClassDB::instantiate("...")?

This PR answers no, and makes it so that internal classes return false for ClassDB::can_instantiate() and can't be created via ClassDB::instantiate("...").

I don't think there's anything that depends on the old behavior, but I'm not 100% sure, so treat this one with caution.

@dsnopek dsnopek force-pushed the classdb-cannot-instantiate-unexposed-classes branch from 46d8ca0 to be81b06 Compare February 4, 2025 23:09
@RandomShaper
Copy link
Member

Following you latest comment on the issue, I agree with this approach.

Aside, I'm wondering if we're using two different terms to mean the same across the API and implementation: "internal" and "not exposed". If that's true, we may want to harominze the terminology.

Copy link
Member

@Ivorforce Ivorforce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change seems correct to me. Notably, this also matches bindings_generator.cpp behavior.

Anyone working with internal classes should probably instantiate through the C++ class interface.
The exception (as @dsnopek noted in the meeting) might be serialization, but we aren't aware of internal classes being serialized in this way.

I say we merge this early in the dev cycle, and if somebody or something complains, we may need to back paddle.

Side note, I did search through the code base a bit to find instances of internal classes and make some research, but was surprised to see GDREGISTER_INTERNAL_CLASS isn't used once in the code base. So i didn't actually end up finding any.

akien-mga
akien-mga previously approved these changes Mar 27, 2025
@akien-mga
Copy link
Member

Side note, I did search through the code base a bit to find instances of internal classes and make some research, but was surprised to see GDREGISTER_INTERNAL_CLASS isn't used once in the code base. So i didn't actually end up finding any.

The only usage was removed in #102433 as an alternative way to fix #99534.

I'm fuzzy on why we have this feature in the first place though. It was added by @Daylily-Zeleen in #70329 but there wasn't much justification for why this is useful (no proposal). @dsnopek seemed to think it was a good addition though and I trust your judgement on that, but it might be worth to refresh our memory on the original context to see if this PR does what's expected, and whether this feature should be available for Godot classes if it was intended only for GDExtensions.

@akien-mga akien-mga dismissed their stale review March 27, 2025 15:56

See previous comment, would be good to check the context of the original PR implementing this feature.

@dsnopek
Copy link
Contributor Author

dsnopek commented Mar 27, 2025

I'm fuzzy on why we have this feature in the first place though. It was added by @Daylily-Zeleen in #70329 but there wasn't much justification for why this is useful (no proposal). @dsnopek seemed to think it was a good addition though and I trust your judgement on that, but it might be worth to refresh our memory on the original context to see if this PR does what's expected, and whether this feature should be available for Godot classes if it was intended only for GDExtensions.

So, we've had unexposed classes for a long time, however, they were registered by not explicitly registering them. Basically, if you used GDCLASS() but didn't call GDREGISTER_CLASS(), then the unexposed class would get registered in ClassDB the first time an instance of it was created.

However, this pattern doesn't work as well for GDExtension, where we're registering all classes explicitly. That's why we needed GDREGISTER_INTERNAL_CLASS() for GDExtension. And, so then for API parity between modules and GDExtensions, it made sense to also have GDREGISTER_INTERNAL_CLASS() in Godot too.

Anyway, in short, PR #70329 didn't add the concept of unexposed classes (those existed for far longer), it just added a way to explicitly register them.

This PR isn't really affected by whether or not a class is explicitly registered as unexposed or not. The problem still exists for unexposed classes that aren't explicitly registered (which is basically all the unexposed classes that exist within Godot itself).

@Daylily-Zeleen
Copy link
Contributor

It seems involve multiple PRs/issues, I'm not sure if the response here is accurate.

The initial Initial intention for "internal_class" (register by GDREGISTER_INTERNAL_CLASS()) is to register an unexposed class, but still need to be use through ClassDB.

Like add custom EditorPlugin or ResourceFormatSaver from GDExtension, they need to obtain bindings from ClassDB in order to interact with godot, but not need to expose to users. This is different from godot.

@akien-mga akien-mga merged commit 666edb3 into godotengine:master Mar 28, 2025
19 checks passed
@akien-mga
Copy link
Member

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants