Skip to content

Commit e626da8

Browse files
committed
Don't pin named structs defined in Ruby
[Bug #20311] `rb_define_class_under` assumes it's called from C and that the reference might be held in a C global variable, so it adds the class to the VM root. In the case of `Struct.new('Name')` it's wasteful and make the struct immortal.
1 parent 5d76fe6 commit e626da8

File tree

4 files changed

+36
-9
lines changed

4 files changed

+36
-9
lines changed

class.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,7 @@ rb_define_class_under(VALUE outer, const char *name, VALUE super)
10071007
}
10081008

10091009
VALUE
1010-
rb_define_class_id_under(VALUE outer, ID id, VALUE super)
1010+
rb_define_class_id_under_no_pin(VALUE outer, ID id, VALUE super)
10111011
{
10121012
VALUE klass;
10131013

@@ -1024,8 +1024,6 @@ rb_define_class_id_under(VALUE outer, ID id, VALUE super)
10241024
" (%"PRIsVALUE" is given but was %"PRIsVALUE")",
10251025
outer, rb_id2str(id), RCLASS_SUPER(klass), super);
10261026
}
1027-
/* Class may have been defined in Ruby and not pin-rooted */
1028-
rb_vm_add_root_module(klass);
10291027

10301028
return klass;
10311029
}
@@ -1037,11 +1035,18 @@ rb_define_class_id_under(VALUE outer, ID id, VALUE super)
10371035
rb_set_class_path_string(klass, outer, rb_id2str(id));
10381036
rb_const_set(outer, id, klass);
10391037
rb_class_inherited(super, klass);
1040-
rb_vm_add_root_module(klass);
10411038

10421039
return klass;
10431040
}
10441041

1042+
VALUE
1043+
rb_define_class_id_under(VALUE outer, ID id, VALUE super)
1044+
{
1045+
VALUE klass = rb_define_class_id_under_no_pin(outer, id, super);
1046+
rb_vm_add_root_module(klass);
1047+
return klass;
1048+
}
1049+
10451050
VALUE
10461051
rb_module_s_alloc(VALUE klass)
10471052
{

internal/class.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ void rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE);
175175
void rb_class_detach_subclasses(VALUE);
176176
void rb_class_detach_module_subclasses(VALUE);
177177
void rb_class_remove_from_module_subclasses(VALUE);
178+
VALUE rb_define_class_id_under_no_pin(VALUE outer, ID id, VALUE super);
178179
VALUE rb_obj_methods(int argc, const VALUE *argv, VALUE obj);
179180
VALUE rb_obj_protected_methods(int argc, const VALUE *argv, VALUE obj);
180181
VALUE rb_obj_private_methods(int argc, const VALUE *argv, VALUE obj);

struct.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ new_struct(VALUE name, VALUE super)
273273
rb_warn("redefining constant %"PRIsVALUE"::%"PRIsVALUE, super, name);
274274
rb_mod_remove_const(super, ID2SYM(id));
275275
}
276-
return rb_define_class_id_under(super, id, super);
276+
return rb_define_class_id_under_no_pin(super, id, super);
277277
}
278278

279279
NORETURN(static void invalid_struct_pos(VALUE s, VALUE idx));
@@ -491,8 +491,13 @@ rb_struct_define(const char *name, ...)
491491
ary = struct_make_members_list(ar);
492492
va_end(ar);
493493

494-
if (!name) st = anonymous_struct(rb_cStruct);
495-
else st = new_struct(rb_str_new2(name), rb_cStruct);
494+
if (!name) {
495+
st = anonymous_struct(rb_cStruct);
496+
}
497+
else {
498+
st = new_struct(rb_str_new2(name), rb_cStruct);
499+
rb_vm_add_root_module(st);
500+
}
496501
return setup_struct(st, ary);
497502
}
498503

@@ -506,7 +511,7 @@ rb_struct_define_under(VALUE outer, const char *name, ...)
506511
ary = struct_make_members_list(ar);
507512
va_end(ar);
508513

509-
return setup_struct(rb_define_class_under(outer, name, rb_cStruct), ary);
514+
return setup_struct(rb_define_class_id_under(outer, rb_intern(name), rb_cStruct), ary);
510515
}
511516

512517
/*
@@ -1699,7 +1704,9 @@ rb_data_define(VALUE super, ...)
16991704
ary = struct_make_members_list(ar);
17001705
va_end(ar);
17011706
if (!super) super = rb_cData;
1702-
return setup_data(anonymous_struct(super), ary);
1707+
VALUE klass = setup_data(anonymous_struct(super), ary);
1708+
rb_vm_add_root_module(klass);
1709+
return klass;
17031710
}
17041711

17051712
/*

test/ruby/test_struct.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,20 @@ def test_parameters
534534
assert_equal [[:req, :_]], klass.instance_method(:c=).parameters
535535
end
536536

537+
def test_named_structs_are_not_rooted
538+
# [Bug #20311]
539+
assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
540+
code = proc do
541+
Struct.new("A")
542+
Struct.send(:remove_const, :A)
543+
end
544+
545+
1_000.times(&code)
546+
PREP
547+
300_000.times(&code)
548+
CODE
549+
end
550+
537551
class TopStruct < Test::Unit::TestCase
538552
include TestStruct
539553

0 commit comments

Comments
 (0)