diff --git a/regparse.c b/regparse.c index 52f4ea193a8..6943f1bd45e 100644 --- a/regparse.c +++ b/regparse.c @@ -6759,6 +6759,8 @@ parse_subexp(Node** top, OnigToken* tok, int term, } r = parse_branch(&node, tok, term, src, end, env); if (r < 0) { + /* the failed branch is not linked into topnode yet: free both */ + onig_node_free(node); onig_node_free(topnode); return r; } diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb index ae737a15748..fd0532ef3a0 100644 --- a/test/ruby/test_regexp.rb +++ b/test/ruby/test_regexp.rb @@ -1004,6 +1004,23 @@ class TestRegexp < Test::Unit::TestCase assert_equal('foobazquux/foobazquux', result, bug8856) end + def test_alternation_compile_error_no_memory_leak + # A compile error in the 2nd+ branch of a top-level alternation leaked the + # partially parsed branch (parse_subexp freed the alternation list but not + # the node parse_branch had built so far). Regexp#to_s reaches this path + # for every /(?:a|b)c(d)/-shaped regexp. + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~"end;"}", "[Bug #22384]", rss: true) + code = proc do + Regexp.new("a|b(c") rescue nil + /(?:a|b)c(d)/.to_s + end + + 1_000.times(&code) + begin; + 300_000.times(&code) + end; + end + def test_regsub_no_memory_leak assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~"end;"}", rss: true) code = proc do