diff --git a/class.c b/class.c index bcdaa82..885cb80 100644 --- a/class.c +++ b/class.c @@ -2024,6 +2024,13 @@ rb_prepend_module(VALUE klass, VALUE module) /* During lazy sweeping, the entry could be a dead object that * has not yet been swept. */ if (!rb_objspace_garbage_object_p(subclass)) { + /* An origin iclass backfilled by a previous prepend is + * registered in the subclasses list only so that method + * cache invalidation can reach it. The module is inserted + * between the includer's iclass and that origin when the + * includer's iclass is visited, so visiting the origin too + * would insert the module a second time. */ + if (RICLASS_IS_ORIGIN_P(subclass)) continue; if (klass_had_no_origin && klass_origin_m_tbl == RCLASS_M_TBL(subclass)) { // backfill an origin iclass to handle refinements and future prepends rb_id_table_foreach(RCLASS_M_TBL(subclass), clear_module_cache_i, (void *)subclass); diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 10d0e67..adf0220 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -2858,6 +2858,27 @@ def test_prepend_no_duplication assert_equal([m, c], c.ancestors[0, 2], "should never duplicate") end + def test_prepend_to_included_module_multiple_times + m = labeled_module("M") + c = labeled_class("C") {include m} + m0 = labeled_module("M0") + m1 = labeled_module("M1") + m2 = labeled_module("M2") + m.prepend m0 + m.prepend m1 + m.prepend m2 + assert_equal([c, m2, m1, m0, m, Object], c.ancestors[0, 6], "should never duplicate") + + base = Module.new {def m; [:base] end} + h = Module.new {include base} + view = Class.new {include h} + p1 = Module.new {def m; [:p1] + super end} + p2 = Module.new {def m; [:p2] + super end} + h.prepend p1 + h.prepend p2 + assert_equal([:p2, :p1, :base], view.new.m) + end + def test_prepend_in_superclass m = labeled_module("M") c1 = labeled_class("C1")