Fix leak in warning of duplicate keys when Ripper#warn jumps

For example, the following code leaks:

    class MyRipper < Ripper
      def initialize(src, &blk)
        super(src)
        @blk = blk
      end

      def warn(msg, *args) = @blk.call(msg)
    end

    $VERBOSE = true
    def call_parse = MyRipper.new("if true\n  end\n") { |msg| return msg }.parse

    10.times do
      500_000.times do
        call_parse
      end

      puts `ps -o rss= -p #{$$}`
    end

Before:

    34832
    51952
    69760
    88048
    105344
    123040
    141152
    159152
    176656
    194272

After:

    18400
    20256
    20272
    20272
    20272
    20304
    20368
    20368
    20368
    20400
This commit is contained in:
Peter Zhu 2024-07-31 14:18:39 -04:00
Родитель c0938fd24c
Коммит ced35800d4
2 изменённых файлов: 33 добавлений и 4 удалений

15
parse.y
Просмотреть файл

@ -543,6 +543,8 @@ struct parser_params {
rb_ast_t *ast;
int node_id;
st_table *warn_duplicate_keys_table;
int max_numparam;
ID it_id;
@ -14701,7 +14703,7 @@ static void
warn_duplicate_keys(struct parser_params *p, NODE *hash)
{
/* See https://bugs.ruby-lang.org/issues/20331 for discussion about what is warned. */
st_table *literal_keys = st_init_table_with_size(&literal_type, RNODE_LIST(hash)->as.nd_alen / 2);
p->warn_duplicate_keys_table = st_init_table_with_size(&literal_type, RNODE_LIST(hash)->as.nd_alen / 2);
while (hash && RNODE_LIST(hash)->nd_next) {
NODE *head = RNODE_LIST(hash)->nd_head;
NODE *value = RNODE_LIST(hash)->nd_next;
@ -14717,16 +14719,17 @@ warn_duplicate_keys(struct parser_params *p, NODE *hash)
if (nd_type_st_key_enable_p(head)) {
key = (st_data_t)head;
if (st_delete(literal_keys, &key, &data)) {
if (st_delete(p->warn_duplicate_keys_table, &key, &data)) {
rb_warn2L(nd_line((NODE *)data),
"key %+"PRIsWARN" is duplicated and overwritten on line %d",
nd_value(p, head), WARN_I(nd_line(head)));
}
st_insert(literal_keys, (st_data_t)key, (st_data_t)hash);
st_insert(p->warn_duplicate_keys_table, (st_data_t)key, (st_data_t)hash);
}
hash = next;
}
st_free_table(literal_keys);
st_free_table(p->warn_duplicate_keys_table);
p->warn_duplicate_keys_table = NULL;
}
static NODE *
@ -15612,6 +15615,10 @@ rb_ruby_parser_free(void *ptr)
rb_ast_free(p->ast);
}
if (p->warn_duplicate_keys_table) {
st_free_table(p->warn_duplicate_keys_table);
}
#ifndef RIPPER
if (p->tokens) {
rb_parser_ary_free(p, p->tokens);

Просмотреть файл

@ -1765,4 +1765,26 @@ class TestRipper::ParserEvents < Test::Unit::TestCase
end
end;
end
def test_return_out_of_warn_no_memory_leak
assert_no_memory_leak(%w(-rripper), "#{<<~'begin;'}", "#{<<~'end;'}", rss: true)
class MyRipper < Ripper
def initialize(src, &blk)
super(src)
@blk = blk
end
def warn(msg, *args) = @blk.call(msg)
end
def call_parse = MyRipper.new("{ a: 1, a: 2 }") { |msg| return msg }.parse
# Check that call_parse does warn
raise "call_parse should warn" unless call_parse
begin;
500_000.times do
call_parse
end
end;
end
end if ripper_test