Skip to content

Freeze singleton class chain#10245

Merged
jeremyevans merged 1 commit intoruby:masterfrom
jeremyevans:freeze-singleton-class-chain-20319
Feb 24, 2026
Merged

Freeze singleton class chain#10245
jeremyevans merged 1 commit intoruby:masterfrom
jeremyevans:freeze-singleton-class-chain-20319

Conversation

@jeremyevans
Copy link
Copy Markdown
Contributor

@jeremyevans jeremyevans commented Mar 14, 2024

The following code:

x = Object.new
sc1 = x.singleton_class
sc2 = sc1.singleton_class
x.freeze

Would freeze sc1 but not sc2, even though sc1 would be frozen.

Not freezing the entire singleton class chain is expected, because at a certain point, the singleton class is shared. However, you can walk the the attached object chain for each singleton class, and only freeze if it results in the original object. This way, only non-shared singleton classes are frozen.

Fixes [Bug #20319]

@jeremyevans jeremyevans force-pushed the freeze-singleton-class-chain-20319 branch from f4f8f51 to ff2b943 Compare March 14, 2024 01:48
@jeremyevans jeremyevans requested a review from nobu April 11, 2024 19:55
@eregon
Copy link
Copy Markdown
Member

eregon commented Apr 18, 2024

Not freezing the entire singleton class chain is expected, because at a certain point, the singleton class is shared. However, you can walk the the attached object chain for each singleton class, and only freeze if it results in the original object. This way, only non-shared singleton classes are frozen.

Right, by "shared singleton class" that's actually only internal CRuby state, i.e., before the singleton class of that class has been needed (e.g. by calling .singleton_class). It's basically a case of "doesn't have a singleton class yet, so instead the superclass is stored in the RBasic->klass field).
Maybe there is even a macro in CRuby to check that more explicitly than RCLASS_ATTACHED_OBJECT.

I think it this is a good way to fix the inconsistency shown in https://bugs.ruby-lang.org/issues/20319

The following code:

```ruby
x = Object.new
sc1 = x.singleton_class
sc2 = sc1.singleton_class
x.freeze
```

Would freeze sc1 but not sc2, even though sc1 would be frozen.

Handle this by walking the class pointer chain for the object.
If the class is a singleton class, and it isn't frozen, and the
attached object for the singleton class is the object, the
singleton class should be frozen, and we move to the next iteration.

Fixes [Bug #20319]
@jeremyevans jeremyevans force-pushed the freeze-singleton-class-chain-20319 branch from ff2b943 to ab8c431 Compare February 8, 2026 16:27
@jeremyevans
Copy link
Copy Markdown
Contributor Author

I came up with a revised algorithm for this, which is O(N) instead of O(N^2), where N is the depth of the singleton class chain. Instead of going all the way down the chain back to the original frozen object in every level, we only need to check at each level that the object's class pointer is the singleton class for the current object, by checking the attached object for that class matches.

Previously, I wasn't sure about whether the code was correct in all cases. I'm now fairly confident that it is. I've expanded the tests as well.

@nobu when you have time, could you please rereview?

@jeremyevans jeremyevans merged commit 8e9eb69 into ruby:master Feb 24, 2026
90 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants