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]
This commit is contained in:
Jeremy Evans 2020-04-05 12:10:42 -07:00
parent ef13558fcd
commit c745a60634
Notes: git 2020-05-22 23:37:16 +09:00
4 changed files with 98 additions and 12 deletions

View file

@ -479,6 +479,28 @@ class TestModule < Test::Unit::TestCase
assert_raise(ArgumentError) { Module.new { include } }
end
def test_gc_prepend_chain
assert_separately([], <<-EOS)
10000.times { |i|
m1 = Module.new do
def foo; end
end
m2 = Module.new do
prepend m1
def bar; end
end
m3 = Module.new do
def baz; end
prepend m2
end
Class.new do
prepend m3
end
}
GC.start
EOS
end
def test_include_into_module_already_included
c = Class.new{def foo; [:c] end}
modules = lambda do ||
@ -540,6 +562,16 @@ class TestModule < Test::Unit::TestCase
assert_equal([Comparable, Kernel], String.included_modules - mixins)
end
def test_included_modules_with_prepend
m1 = Module.new
m2 = Module.new
m3 = Module.new
m2.prepend m1
m3.include m2
assert_equal([m1, m2], m3.included_modules)
end
def test_instance_methods
assert_equal([:user, :user2], User.instance_methods(false).sort)
assert_equal([:user, :user2, :mixin].sort, User.instance_methods(true).sort)
@ -2043,6 +2075,33 @@ class TestModule < Test::Unit::TestCase
assert_include(im, mixin, bug8025)
end
def test_prepended_module_with_super_and_alias
bug16736 = '[Bug #16736]'
a = labeled_class("A") do
def m; "A"; end
end
m = labeled_module("M") do
prepend Module.new
def self.included(base)
base.alias_method :base_m, :m
end
def m
super + "M"
end
def m2
base_m
end
end
b = labeled_class("B", a) do
include m
end
assert_equal("AM", b.new.m2, bug16736)
end
def test_prepend_super_in_alias
bug7842 = '[Bug #7842]'