From 5bc8fceca8d47ed1ef9c603c6531a408de36b60c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 9 Aug 2023 11:03:13 -0400 Subject: Fix memory leak in parser for incomplete tokens [Bug #19835] The parser does not free the `tbl` of the `struct vtable` when there are leftover `lvtbl` in the parser. This causes a memory leak. The following script reproduces this issue: ``` 10.times do 100_000.times do Ripper.parse("class Foo") end puts `ps -o rss= -p #{$$}` end ``` --- parse.y | 42 ++++++++++++++++++++++++++++-------------- test/ripper/test_ripper.rb | 7 +++++++ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/parse.y b/parse.y index 77b00c7f80..0909356f33 100644 --- a/parse.y +++ b/parse.y @@ -13229,26 +13229,39 @@ local_push(struct parser_params *p, int toplevel_scope) } static void -local_pop(struct parser_params *p) +local_free(struct parser_params *p, struct local_vars *local) { - struct local_vars *local = p->lvtbl->prev; - if (p->lvtbl->used) { - warn_unused_var(p, p->lvtbl); - vtable_free(p->lvtbl->used); + if (local->used) { + vtable_free(local->used); } + # if WARN_PAST_SCOPE - while (p->lvtbl->past) { - struct vtable *past = p->lvtbl->past; - p->lvtbl->past = past->prev; + while (local->past) { + struct vtable *past = local->past; + local->past = past->prev; vtable_free(past); } # endif - vtable_free(p->lvtbl->args); - vtable_free(p->lvtbl->vars); + + vtable_free(local->args); + vtable_free(local->vars); + + ruby_sized_xfree(local, sizeof(struct local_vars)); +} + +static void +local_pop(struct parser_params *p) +{ + struct local_vars *local = p->lvtbl->prev; + if (p->lvtbl->used) { + warn_unused_var(p, p->lvtbl); + } + + local_free(p, p->lvtbl); + p->lvtbl = local; + CMDARG_POP(); COND_POP(); - ruby_sized_xfree(p->lvtbl, sizeof(*p->lvtbl)); - p->lvtbl = local; } #ifndef RIPPER @@ -13856,11 +13869,12 @@ rb_ruby_parser_free(void *ptr) if (p->tokenbuf) { ruby_sized_xfree(p->tokenbuf, p->toksiz); } + for (local = p->lvtbl; local; local = prev) { - xfree(local->vars); prev = local->prev; - xfree(local); + local_free(p, local); } + { token_info *ptinfo; while ((ptinfo = p->token_info) != 0) { diff --git a/test/ripper/test_ripper.rb b/test/ripper/test_ripper.rb index 2bb307eddf..25ddc381cc 100644 --- a/test/ripper/test_ripper.rb +++ b/test/ripper/test_ripper.rb @@ -154,6 +154,13 @@ end Ripper.parse("") end end; + + # [Bug #19835] + assert_no_memory_leak(%w(-rripper), "", "#{<<~'end;'}", rss: true) + 1_000_000.times do + Ripper.parse("class Foo") + end + end; end class TestInput < self -- cgit v1.2.3