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

Fix infinite recursion bug in .inherited sigs #1636

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

amomchilov
Copy link
Contributor

Motivation

Fixes #1634

Implementation

The implementation isn't the most elegant, but it's a working starting point.

The main point was to register our new subclass in our lookup table asap, inside of our swizzled .inherited implementation. This puts it in place early enough so that if the .inherited method's sig references the generic class, it will be found (rather than hitting the create_generic_type code path and recursing infinitely).

At that point, our subclass is in an intermediate state. It exists, its superclass is set, but none of our customizations in create_generic_type (e.g. definine .name, __tapioca_override_type, etc.) have been done yet. I'm not sure what the impact of this could be.

I added the "help-wanted" label to solicit feedback on this part.

Tests

Check the commit history. I started with a characterization tests that ensured the test scenario would hit the expected SystemStackError, then replaced it with a test that ensures the fix works. And it does :)

@amomchilov amomchilov added help-wanted We support this change, and welcome community contributions for it. bugfix labels Sep 1, 2023
@amomchilov amomchilov self-assigned this Sep 1, 2023
@amomchilov amomchilov changed the title Bug/generic type registry recursion Fix infinite recursion bug in .inherited sigs Sep 1, 2023
@amomchilov amomchilov force-pushed the bug/GenericTypeRegistry-recursion branch from 8581a3b to 8b9189b Compare September 1, 2023 22:10
@amomchilov amomchilov force-pushed the GenericTypeRegistrySpec branch from 9bfa651 to 9e68657 Compare September 2, 2023 01:39
@amomchilov amomchilov force-pushed the bug/GenericTypeRegistry-recursion branch from 8b9189b to d5b7c98 Compare September 2, 2023 02:21
owner.send(:define_method, :inherited) do |new_subclass|
# Register this new subclass ASAP, to prevent re-entry into the `create_safe_subclass` code-path.
# This can happen if the sig of the original `.inherited` method references the generic type itself.
generic_instances[name] ||= new_subclass
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Admittedly, this is a bit odd.

I would have preferred keeping the simplicity of the "create the thing, then register the thing" as two clean, separate steps, but I don't think that's possible.

Registering the "completed" subclass requires it to first be created, creating it entails running its sigs, which attempts to register it. 🔁

Copy link
Collaborator

Choose a reason for hiding this comment

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

You could separate the process in 2 steps. 1. to register the class and 2. to call the original .inherited. But this looks good enough 👍

Should we update the register_type comment to add a note about recursiveness?

Base automatically changed from GenericTypeRegistrySpec to main September 5, 2023 14:46
@amomchilov amomchilov marked this pull request as ready for review September 5, 2023 14:46
@amomchilov amomchilov requested a review from a team as a code owner September 5, 2023 14:46
Copy link
Member

@vinistock vinistock left a comment

Choose a reason for hiding this comment

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

I don't see alternatives to fix this tbh

@@ -77,6 +77,19 @@ class GenericTypeRegistrySpec < Minitest::Spec
refute_same(result, RaisesInInheritedCallback)
Copy link
Collaborator

Choose a reason for hiding this comment

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

^ There is a typo in the commit message

it "FIXME: breaks from infinite recursion if the sig on .inherited uses the generic type" do
# Our swizzled implementation of the `.inherited` method needs to be carefully implemented to not fall into
# infinite recursion when the sig for the method references the class that it's defined on.
assert_raises(SystemStackError) do
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's try to not commit code that is being changed by a commit in the same PR. You can introduce a test that breaks, and fix it in the next commit, TDD like.

@@ -117,7 +117,7 @@ def create_generic_type(constant, name)
# the generic class `Foo[Bar]` is still a `Foo`. That is:
Copy link
Collaborator

Choose a reason for hiding this comment

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

^ Typo in commit message

owner.send(:define_method, :inherited) do |new_subclass|
# Register this new subclass ASAP, to prevent re-entry into the `create_safe_subclass` code-path.
# This can happen if the sig of the original `.inherited` method references the generic type itself.
generic_instances[name] ||= new_subclass
Copy link
Collaborator

Choose a reason for hiding this comment

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

You could separate the process in 2 steps. 1. to register the class and 2. to call the original .inherited. But this looks good enough 👍

Should we update the register_type comment to add a note about recursiveness?

@andyw8 andyw8 removed their request for review September 11, 2023 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bugfix help-wanted We support this change, and welcome community contributions for it.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Infinite recursion bug when referencing generic class in a sig on its .inherited method
3 participants