Skip to content

Commit 7571ad4

Browse files
authored
[Bug #20650] Fix memory leak in Regexp capture group when timeout (ruby#11244)
Fix memory leak in Regexp capture group when timeout [Bug #20650] The capture group allocates memory that is leaked when it times out. For example: re = Regexp.new("^#{"(a*)" * 10_000}x$", timeout: 0.000001) str = "a" * 1000000 + "x" 10.times do 100.times do re =~ str rescue Regexp::TimeoutError end puts `ps -o rss= -p #{$$}` end Before: 34688 56416 78288 100368 120784 140704 161904 183568 204320 224800 After: 16288 16288 16880 16896 16912 16928 16944 17184 17184 17200
1 parent 4667f8e commit 7571ad4

File tree

3 files changed

+66
-24
lines changed

3 files changed

+66
-24
lines changed

include/ruby/onigmo.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,7 @@ ONIG_EXTERN const OnigSyntaxType* OnigDefaultSyntax;
636636
#define ONIGERR_PARSE_DEPTH_LIMIT_OVER -16
637637
#define ONIGERR_DEFAULT_ENCODING_IS_NOT_SET -21
638638
#define ONIGERR_SPECIFIED_ENCODING_CANT_CONVERT_TO_WIDE_CHAR -22
639+
#define ONIGERR_TIMEOUT -23
639640
/* general error */
640641
#define ONIGERR_INVALID_ARGUMENT -30
641642
/* syntax error */

regexec.c

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4220,7 +4220,7 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,
42204220
xfree(xmalloc_base);
42214221
if (stk_base != stk_alloc || IS_NOT_NULL(msa->stack_p))
42224222
xfree(stk_base);
4223-
HANDLE_REG_TIMEOUT_IN_MATCH_AT;
4223+
return ONIGERR_TIMEOUT;
42244224
}
42254225

42264226

@@ -5212,44 +5212,64 @@ onig_search_gpos(regex_t* reg, const UChar* str, const UChar* end,
52125212
# ifdef USE_FIND_LONGEST_SEARCH_ALL_OF_RANGE
52135213
# define MATCH_AND_RETURN_CHECK(upper_range) \
52145214
r = match_at(reg, str, end, (upper_range), s, prev, &msa); \
5215-
if (r != ONIG_MISMATCH) {\
5216-
if (r >= 0) {\
5217-
if (! IS_FIND_LONGEST(reg->options)) {\
5218-
goto match;\
5215+
switch (r) { \
5216+
case ONIG_MISMATCH: \
5217+
break; \
5218+
case ONIGERR_TIMEOUT: \
5219+
goto timeout; \
5220+
default: \
5221+
if (r >= 0) { \
5222+
if (! IS_FIND_LONGEST(reg->options)) { \
5223+
goto match; \
5224+
}\
52195225
}\
5220-
}\
5221-
else goto finish; /* error */ \
5226+
else goto finish; /* error */ \
52225227
}
52235228
# else
52245229
# define MATCH_AND_RETURN_CHECK(upper_range) \
52255230
r = match_at(reg, str, end, (upper_range), s, prev, &msa); \
5226-
if (r != ONIG_MISMATCH) {\
5227-
if (r >= 0) {\
5228-
goto match;\
5229-
}\
5230-
else goto finish; /* error */ \
5231+
switch (r) { \
5232+
case ONIG_MISMATCH: \
5233+
break; \
5234+
case ONIGERR_TIMEOUT: \
5235+
goto timeout; \
5236+
default: \
5237+
if (r >= 0) { \
5238+
goto match; \
5239+
}\
5240+
else goto finish; /* error */ \
52315241
}
52325242
# endif /* USE_FIND_LONGEST_SEARCH_ALL_OF_RANGE */
52335243
#else
52345244
# ifdef USE_FIND_LONGEST_SEARCH_ALL_OF_RANGE
52355245
# define MATCH_AND_RETURN_CHECK(none) \
52365246
r = match_at(reg, str, end, s, prev, &msa);\
5237-
if (r != ONIG_MISMATCH) {\
5238-
if (r >= 0) {\
5239-
if (! IS_FIND_LONGEST(reg->options)) {\
5240-
goto match;\
5241-
}\
5242-
}\
5243-
else goto finish; /* error */ \
5247+
switch (r) { \
5248+
case ONIG_MISMATCH: \
5249+
break; \
5250+
case ONIGERR_TIMEOUT: \
5251+
goto timeout; \
5252+
default: \
5253+
if (r >= 0) { \
5254+
if (! IS_FIND_LONGEST(reg->options)) { \
5255+
goto match; \
5256+
} \
5257+
} \
5258+
else goto finish; /* error */ \
52445259
}
52455260
# else
52465261
# define MATCH_AND_RETURN_CHECK(none) \
52475262
r = match_at(reg, str, end, s, prev, &msa);\
5248-
if (r != ONIG_MISMATCH) {\
5249-
if (r >= 0) {\
5250-
goto match;\
5251-
}\
5252-
else goto finish; /* error */ \
5263+
switch (r) { \
5264+
case ONIG_MISMATCH: \
5265+
break; \
5266+
case ONIGERR_TIMEOUT: \
5267+
goto timeout; \
5268+
default: \
5269+
if (r >= 0) { \
5270+
goto match; \
5271+
} \
5272+
else goto finish; /* error */ \
52535273
}
52545274
# endif /* USE_FIND_LONGEST_SEARCH_ALL_OF_RANGE */
52555275
#endif /* USE_MATCH_RANGE_MUST_BE_INSIDE_OF_SPECIFIED_RANGE */
@@ -5552,6 +5572,11 @@ onig_search_gpos(regex_t* reg, const UChar* str, const UChar* end,
55525572
match:
55535573
MATCH_ARG_FREE(msa);
55545574
return s - str;
5575+
5576+
timeout:
5577+
MATCH_ARG_FREE(msa);
5578+
onig_region_free(region, false);
5579+
HANDLE_REG_TIMEOUT_IN_MATCH_AT;
55555580
}
55565581

55575582
extern OnigPosition

test/ruby/test_regexp.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1895,6 +1895,22 @@ def test_timeout_corner_cases
18951895
end;
18961896
end
18971897

1898+
def test_timeout_memory_leak
1899+
assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", "[Bug #20650]", timeout: 100, rss: true)
1900+
regex = Regexp.new("^#{"(a*)" * 10_000}x$", timeout: 0.000001)
1901+
str = "a" * 1_000_000 + "x"
1902+
1903+
code = proc do
1904+
regex =~ str
1905+
rescue
1906+
end
1907+
1908+
10.times(&code)
1909+
begin;
1910+
1_000.times(&code)
1911+
end;
1912+
end
1913+
18981914
def test_match_cache_exponential
18991915
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
19001916
timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }

0 commit comments

Comments
 (0)