Skip to content

Commit 8e9eb69

Browse files
committed
Freeze singleton class chain
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]
1 parent b479c41 commit 8e9eb69

2 files changed

Lines changed: 54 additions & 10 deletions

File tree

class.c

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2884,16 +2884,18 @@ singleton_class_of(VALUE obj, bool ensure_eigenclass)
28842884
}
28852885

28862886
void
2887-
rb_freeze_singleton_class(VALUE x)
2888-
{
2889-
/* should not propagate to meta-meta-class, and so on */
2890-
if (!RCLASS_SINGLETON_P(x)) {
2891-
VALUE klass = RBASIC_CLASS(x);
2892-
if (klass && // no class when hidden from ObjectSpace
2893-
FL_TEST_RAW(klass, FL_SINGLETON) &&
2894-
!OBJ_FROZEN_RAW(klass)) {
2895-
OBJ_FREEZE(klass);
2896-
}
2887+
rb_freeze_singleton_class(VALUE attached_object)
2888+
{
2889+
VALUE klass;
2890+
2891+
/* Freeze singleton classes of singleton class, as singleton class is frozen, and so on */
2892+
/* In each iteration, check the current object's class pointer is the singleton class of the object. */
2893+
while ((klass = RBASIC_CLASS(attached_object)) &&
2894+
FL_TEST_RAW(klass, FL_SINGLETON) &&
2895+
!OBJ_FROZEN_RAW(klass) &&
2896+
(RCLASS_ATTACHED_OBJECT(klass) == attached_object)) {
2897+
attached_object = klass;
2898+
OBJ_FREEZE(attached_object);
28972899
}
28982900
}
28992901

test/ruby/test_object.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,48 @@ def test_singleton_class_freeze
10251025
assert_predicate(ys, :frozen?, '[Bug #19169]')
10261026
end
10271027

1028+
def test_singleton_class_of_singleton_class_freeze
1029+
x = Object.new
1030+
xs = x.singleton_class
1031+
xxs = xs.singleton_class
1032+
xxxs = xxs.singleton_class
1033+
x.freeze
1034+
assert_predicate(xs, :frozen?, '[Bug #20319]')
1035+
assert_predicate(xxs, :frozen?, '[Bug #20319]')
1036+
assert_predicate(xxxs, :frozen?, '[Bug #20319]')
1037+
1038+
m = Module.new
1039+
y = Object.new
1040+
ys = y.singleton_class
1041+
ys.prepend(Module.new)
1042+
yys = ys.singleton_class
1043+
yys.prepend(Module.new)
1044+
yyys = yys.singleton_class
1045+
yyys.prepend(Module.new)
1046+
y.freeze
1047+
assert_predicate(ys, :frozen?, '[Bug #20319]')
1048+
assert_predicate(yys, :frozen?, '[Bug #20319]')
1049+
assert_predicate(yyys, :frozen?, '[Bug #20319]')
1050+
1051+
c = Class.new
1052+
cs = c.singleton_class
1053+
ccs = cs.singleton_class
1054+
cccs = ccs.singleton_class
1055+
d = Class.new(c)
1056+
ds = d.singleton_class
1057+
dds = ds.singleton_class
1058+
ddds = dds.singleton_class
1059+
d.freeze
1060+
assert_predicate(d, :frozen?, '[Bug #20319]')
1061+
assert_predicate(ds, :frozen?, '[Bug #20319]')
1062+
assert_predicate(dds, :frozen?, '[Bug #20319]')
1063+
assert_predicate(ddds, :frozen?, '[Bug #20319]')
1064+
assert_not_predicate(c, :frozen?, '[Bug #20319]')
1065+
assert_not_predicate(cs, :frozen?, '[Bug #20319]')
1066+
assert_not_predicate(ccs, :frozen?, '[Bug #20319]')
1067+
assert_not_predicate(cccs, :frozen?, '[Bug #20319]')
1068+
end
1069+
10281070
def test_redef_method_missing
10291071
bug5473 = '[ruby-core:40287]'
10301072
['ArgumentError.new("bug5473")', 'ArgumentError, "bug5473"', '"bug5473"'].each do |code|

0 commit comments

Comments
 (0)