summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrii Furmanets <furmanets.andriy@gmail.com>2026-01-16 12:52:16 +0200
committerGitHub <noreply@github.com>2026-01-16 19:52:16 +0900
commitf872901bb263e01768ac64e6d063377c3b633e27 (patch)
tree15d937ce883b9a1c707a241c261842e318a63077
parent16cd9daa39c5488a7ee3f60ebc6f5b3cf1ee50e4 (diff)
MatchData: Avoid large stack allocations in MatchData (GH-15872)
-rw-r--r--re.c9
-rw-r--r--test/ruby/test_regexp.rb24
2 files changed, 31 insertions, 2 deletions
diff --git a/re.c b/re.c
index b2c1909c15..82e9407a0a 100644
--- a/re.c
+++ b/re.c
@@ -1014,6 +1014,7 @@ update_char_offset(VALUE match)
char *s, *p, *q;
rb_encoding *enc;
pair_t *pairs;
+ VALUE pairs_obj = Qnil;
if (rm->char_offset_num_allocated)
return;
@@ -1035,7 +1036,7 @@ update_char_offset(VALUE match)
return;
}
- pairs = ALLOCA_N(pair_t, num_regs*2);
+ pairs = RB_ALLOCV_N(pair_t, pairs_obj, num_regs * 2);
num_pos = 0;
for (i = 0; i < num_regs; i++) {
if (BEG(i) < 0)
@@ -1070,6 +1071,8 @@ update_char_offset(VALUE match)
found = bsearch(&key, pairs, num_pos, sizeof(pair_t), pair_byte_cmp);
rm->char_offset[i].end = found->char_pos;
}
+
+ RB_ALLOCV_END(pairs_obj);
}
static VALUE
@@ -2614,6 +2617,7 @@ match_inspect(VALUE match)
struct re_registers *regs = RMATCH_REGS(match);
int num_regs = regs->num_regs;
struct backref_name_tag *names;
+ VALUE names_obj = Qnil;
VALUE regexp = RMATCH(match)->regexp;
if (regexp == 0) {
@@ -2624,7 +2628,7 @@ match_inspect(VALUE match)
cname, rb_reg_nth_match(0, match));
}
- names = ALLOCA_N(struct backref_name_tag, num_regs);
+ names = RB_ALLOCV_N(struct backref_name_tag, names_obj, num_regs);
MEMZERO(names, struct backref_name_tag, num_regs);
onig_foreach_name(RREGEXP_PTR(regexp),
@@ -2652,6 +2656,7 @@ match_inspect(VALUE match)
}
rb_str_buf_cat2(str, ">");
+ RB_ALLOCV_END(names_obj);
return str;
}
diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb
index 2d7a67dd54..9feababa53 100644
--- a/test/ruby/test_regexp.rb
+++ b/test/ruby/test_regexp.rb
@@ -1669,6 +1669,30 @@ class TestRegexp < Test::Unit::TestCase
assert_equal("hoge fuga", h["body"])
end
+ def test_matchdata_large_capture_groups_stack
+ env = {"RUBY_THREAD_MACHINE_STACK_SIZE" => (256 * 1024).to_s}
+ assert_separately([env], <<~'RUBY')
+ n = 20000
+ require "rbconfig/sizeof"
+ stack = RubyVM::DEFAULT_PARAMS[:thread_machine_stack_size]
+ size = RbConfig::SIZEOF["long"]
+ required = (n + 1) * 4 * size
+ if !stack || stack == 0 || stack >= required
+ omit "thread machine stack size not reduced (#{stack}:#{required})"
+ end
+
+ inspect = Thread.new do
+ str = "\u{3042}" * n
+ m = Regexp.new("(.)" * n).match(str)
+ assert_not_nil(m)
+ assert_equal([n - 1, n], m.offset(n))
+ m.inspect
+ end.value
+
+ assert_include(inspect, "MatchData")
+ RUBY
+ end
+
def test_regexp_popped
EnvUtil.suppress_warning do
assert_nothing_raised { eval("a = 1; /\#{ a }/; a") }