diff options
| author | nagachika <nagachika@ruby-lang.org> | 2023-08-13 13:35:25 +0900 |
|---|---|---|
| committer | nagachika <nagachika@ruby-lang.org> | 2023-08-13 13:35:25 +0900 |
| commit | 0c908fa681271f13750aa64420203f1a58fa03fe (patch) | |
| tree | c481602fa506844e914b0e95353f11d73ec85386 | |
| parent | 6898389a0f640c4155a7073579f43d1e16893698 (diff) | |
merge revision(s) 0b8f15575a440f85ac686f5b0eae8f8b7c2b72e7: [Backport #19836]
Fix memory leak for incomplete lambdas
[Bug #19836]
The parser does not free the chain of `struct vtable`, which causes
memory leaks.
The following script reproduces this issue:
```
10.times do
100_000.times do
Ripper.parse("-> {")
end
puts `ps -o rss= -p #{$$}`
end
```
---
parse.y | 24 ++++++++++++++----------
test/ripper/test_ripper.rb | 7 +++++++
2 files changed, 21 insertions(+), 10 deletions(-)
| -rw-r--r-- | parse.y | 24 | ||||
| -rw-r--r-- | test/ripper/test_ripper.rb | 29 | ||||
| -rw-r--r-- | version.h | 2 |
3 files changed, 37 insertions, 18 deletions
@@ -13176,22 +13176,26 @@ local_push(struct parser_params *p, int toplevel_scope) } static void -local_free(struct parser_params *p, struct local_vars *local) +vtable_chain_free(struct parser_params *p, struct vtable *table) { - if (local->used) { - vtable_free(local->used); + while (!DVARS_TERMINAL_P(table)) { + struct vtable *cur_table = table; + table = cur_table->prev; + vtable_free(cur_table); } +} + +static void +local_free(struct parser_params *p, struct local_vars *local) +{ + vtable_chain_free(p, local->used); # if WARN_PAST_SCOPE - while (local->past) { - struct vtable *past = local->past; - local->past = past->prev; - vtable_free(past); - } + vtable_chain_free(p, local->past); # endif - vtable_free(local->args); - vtable_free(local->vars); + vtable_chain_free(p, local->args); + vtable_chain_free(p, local->vars); ruby_sized_xfree(local, sizeof(struct local_vars)); } diff --git a/test/ripper/test_ripper.rb b/test/ripper/test_ripper.rb index a9773a89d8..83b43d67fe 100644 --- a/test/ripper/test_ripper.rb +++ b/test/ripper/test_ripper.rb @@ -120,13 +120,6 @@ class TestRipper::Ripper < Test::Unit::TestCase END assert_nil(Ripper.sexp(src), bug12651) end; - - # [Bug #19835] - assert_no_memory_leak(%w(-rripper), "", "#{<<~'end;'}", rss: true) - 1_000_000.times do - Ripper.parse("class Foo") - end - end; end # https://bugs.jruby.org/4176 @@ -148,6 +141,28 @@ end assert_nothing_raised { Ripper.lex src } end + def test_no_memory_leak + assert_no_memory_leak(%w(-rripper), "", "#{<<~'end;'}", rss: true) + 2_000_000.times do + 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; + + # [Bug #19836] + assert_no_memory_leak(%w(-rripper), "", "#{<<~'end;'}", rss: true) + 1_000_000.times do + Ripper.parse("-> {") + end + end; + end + class TestInput < self Input = Struct.new(:lines) do def gets @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 2 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 112 +#define RUBY_PATCHLEVEL 113 #include "ruby/version.h" #include "ruby/internal/abi.h" |
