Bug #19169
closedKernel#freeze doesn't propagate to singleton class when the singleton class has prepended modules
Description
a = []
singleton = a.singleton_class
a.freeze
p singleton.frozen? # => true
a = []
singleton = a.singleton_class.tap { |klass| klass.prepend(Module.new) }
a.freeze
p singleton.frozen? # => false
It's because of this function:
void
rb_freeze_singleton_class(VALUE x)
{
/* should not propagate to meta-meta-class, and so on */
if (!(RBASIC(x)->flags & FL_SINGLETON)) {
VALUE klass = RBASIC_CLASS(x);
if (klass && (klass = RCLASS_ORIGIN(klass)) != 0 &&
FL_TEST(klass, (FL_SINGLETON|FL_FREEZE)) == FL_SINGLETON) {
OBJ_FREEZE_RAW(klass);
}
}
}
It freezes the origin class of the singleton class, but I'm not sure why.
When there is no prepended modules RCLASS_ORIGIN(klass)
just returns klass
.
It's old code, so maybe the meaning of RCLASS_ORIGIN
has changed?
Practically speaking not a big deal because each time one calls .singleton_class
it copies the frozen bit of the attached object to the singleton class.
Updated by byroot (Jean Boussier) about 2 years ago
I ran it against older rubies, and it seems like it has been behaving like this since prepend
was introduced.
It seem to work on TruffleRuby, but not JRuby.
Updated by alanwu (Alan Wu) about 2 years ago
- Status changed from Open to Closed
Applied in changeset git|bb8afd7265af41a75e8889774aa26f157f146380.
Freeze singleton class, not its origin
Previously, when we froze an object, we froze
RCLASS_ORIGIN(object.singleton_class)
, which didn't freeze
object.singleton_class
when it has some prepended modules.
Origin iclass are internal objects and users can't interact with
them through Kernel#freeze?, Kernel#freeze, or any mutation method
that checks the frozen status. So we shouldn't touch the origin
iclasses when the frozen status should be visible.
[Bug #19169]