Fix memory leak in complemented method entries

[Bug #19894]

When a copy of a complemented method entry is created, there are two
issues:

1. IMEMO_FL_USER3 is not copied, so the complemented status is not
   copied over.
2. In rb_method_entry_clone we increment both alias_count and
   complemented_count. However, when we free the method entry in
   rb_method_definition_release, we only decrement one of the two
   counters, resulting in the rb_method_definition_t being leaked.

Co-authored-by: Adam Hess <adamhess1991@gmail.com>
This commit is contained in:
Peter Zhu 2023-09-19 20:48:41 -04:00
parent 3c11cdbcfe
commit 96c5a4be7b
3 changed files with 37 additions and 5 deletions

View File

@ -101,8 +101,9 @@ static inline void
METHOD_ENTRY_FLAGS_COPY(rb_method_entry_t *dst, const rb_method_entry_t *src)
{
dst->flags =
(dst->flags & ~(IMEMO_FL_USER0|IMEMO_FL_USER1|IMEMO_FL_USER2)) |
(src->flags & (IMEMO_FL_USER0|IMEMO_FL_USER1|IMEMO_FL_USER2));
(dst->flags & ~(IMEMO_FL_USER0|IMEMO_FL_USER1|IMEMO_FL_USER2
|IMEMO_FL_USER3)) |
(src->flags & (IMEMO_FL_USER0|IMEMO_FL_USER1|IMEMO_FL_USER2|IMEMO_FL_USER3));
}
typedef enum {

View File

@ -3292,6 +3292,35 @@ class TestModule < Test::Unit::TestCase
CODE
end
def test_complemented_method_entry_memory_leak
# [Bug #19894]
assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
code = proc do
$c = Class.new do
def foo; end
end
$m = Module.new do
refine $c do
def foo; end
end
end
Class.new do
using $m
def initialize
o = $c.new
o.method(:foo).unbind
end
end.new
end
1_000.times(&code)
PREP
100_000.times(&code)
CODE
end
private
def assert_top_method_is_private(method)

View File

@ -693,11 +693,13 @@ rb_method_entry_create(ID called_id, VALUE klass, rb_method_visibility_t visi, c
const rb_method_entry_t *
rb_method_entry_clone(const rb_method_entry_t *src_me)
{
rb_method_entry_t *me = rb_method_entry_alloc(src_me->called_id, src_me->owner, src_me->defined_class,
method_definition_addref(src_me->def));
rb_method_entry_t *me = rb_method_entry_alloc(src_me->called_id, src_me->owner, src_me->defined_class, src_me->def);
if (METHOD_ENTRY_COMPLEMENTED(src_me)) {
method_definition_addref_complement(src_me->def);
}
else {
method_definition_addref(src_me->def);
}
METHOD_ENTRY_FLAGS_COPY(me, src_me);
return me;
@ -724,7 +726,7 @@ rb_method_entry_complement_defined_class(const rb_method_entry_t *src_me, ID cal
def = NULL;
}
else {
def = method_definition_addref_complement(def);
method_definition_addref_complement(def);
}
me = rb_method_entry_alloc(called_id, src_me->owner, defined_class, def);
METHOD_ENTRY_FLAGS_COPY(me, src_me);