diff options
author | Jeremy Evans <code@jeremyevans.net> | 2020-04-05 12:10:42 -0700 |
---|---|---|
committer | Jeremy Evans <code@jeremyevans.net> | 2020-05-22 07:36:52 -0700 |
commit | c745a60634260ba2080d35af6fdeaaae86fe5193 (patch) | |
tree | b0b3a34af7c2847940dc294408a763535d1861c5 /class.c | |
parent | ef13558fcd99dc2057cbfc0973f9592f0f8b3071 (diff) | |
download | ruby-c745a60634260ba2080d35af6fdeaaae86fe5193.tar.gz |
Fix origin iclass pointer for modules
If a module has an origin, and that module is included in another
module or class, previously the iclass created for the module had
an origin pointer to the module's origin instead of the iclass's
origin.
Setting the origin pointer correctly requires using a stack, since
the origin iclass is not created until after the iclass itself.
Use a hidden ruby array to implement that stack.
Correctly assigning the origin pointers in the iclass caused a
use-after-free in GC. If a module with an origin is included
in a class, the iclass shares a method table with the module
and the iclass origin shares a method table with module origin.
Mark iclass origin with a flag that notes that even though the
iclass is an origin, it shares a method table, so the method table
should not be garbage collected. The shared method table will be
garbage collected when the module origin is garbage collected.
I've tested that this does not introduce a memory leak.
This also includes a fix for Module#included_modules to skip
iclasses with origins.
Fixes [Bug #16736]
Diffstat (limited to 'class.c')
-rw-r--r-- | class.c | 24 |
1 files changed, 19 insertions, 5 deletions
@@ -837,7 +837,7 @@ rb_include_class_new(VALUE module, VALUE super) RCLASS_M_TBL(OBJ_WB_UNPROTECT(klass)) = RCLASS_M_TBL(OBJ_WB_UNPROTECT(module)); /* TODO: unprotected? */ - RCLASS_SET_ORIGIN(klass, module == RCLASS_ORIGIN(module) ? klass : RCLASS_ORIGIN(module)); + RCLASS_SET_ORIGIN(klass, klass); if (BUILTIN_TYPE(module) == T_ICLASS) { module = RBASIC(module)->klass; } @@ -925,8 +925,9 @@ clear_module_cache_i(ID id, VALUE val, void *data) static int include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super) { - VALUE p, iclass; - int method_changed = 0, constant_changed = 0; + VALUE p, iclass, origin_stack = 0; + int method_changed = 0, constant_changed = 0, add_subclass; + long origin_len; struct rb_id_table *const klass_m_tbl = RCLASS_M_TBL(RCLASS_ORIGIN(klass)); VALUE original_klass = klass; @@ -979,11 +980,24 @@ include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super) iclass = rb_include_class_new(module, super_class); c = RCLASS_SET_SUPER(c, iclass); RCLASS_SET_INCLUDER(iclass, klass); + add_subclass = TRUE; + if (module != RCLASS_ORIGIN(module)) { + if (!origin_stack) origin_stack = rb_ary_tmp_new(2); + VALUE origin[2] = {iclass, RCLASS_ORIGIN(module)}; + rb_ary_cat(origin_stack, origin, 2); + } + else if (origin_stack && (origin_len = RARRAY_LEN(origin_stack)) > 1 && + RARRAY_AREF(origin_stack, origin_len - 1) == module) { + RCLASS_SET_ORIGIN(RARRAY_AREF(origin_stack, (origin_len -= 2)), iclass); + RICLASS_SET_ORIGIN_SHARED_MTBL(iclass); + rb_ary_resize(origin_stack, origin_len); + add_subclass = FALSE; + } { VALUE m = module; if (BUILTIN_TYPE(m) == T_ICLASS) m = RBASIC(m)->klass; - rb_module_add_to_subclasses_list(m, iclass); + if (add_subclass) rb_module_add_to_subclasses_list(m, iclass); } if (FL_TEST(klass, RMODULE_IS_REFINEMENT)) { @@ -1093,7 +1107,7 @@ rb_mod_included_modules(VALUE mod) VALUE origin = RCLASS_ORIGIN(mod); for (p = RCLASS_SUPER(mod); p; p = RCLASS_SUPER(p)) { - if (p != origin && BUILTIN_TYPE(p) == T_ICLASS) { + if (p != origin && RCLASS_ORIGIN(p) == p && BUILTIN_TYPE(p) == T_ICLASS) { VALUE m = RBASIC(p)->klass; if (RB_TYPE_P(m, T_MODULE)) rb_ary_push(ary, m); |