[Bug #20653]
This commit refactors how Onigmo handles timeout. Instead of raising a
timeout error, onig_search will return a ONIGERR_TIMEOUT which the
caller can free memory, and then raise a timeout error.
This fixes a memory leak in String#start_with when the regexp times out.
For example:
regex = Regexp.new("^#{"(a*)" * 10_000}x$", timeout: 0.000001)
str = "a" * 1000000 + "x"
10.times do
100.times do
str.start_with?(regex)
rescue
end
puts `ps -o rss= -p #{$$}`
end
Before:
33216
51936
71152
81728
97152
103248
120384
133392
133520
133616
After:
14912
15376
15824
15824
16128
16128
16144
16144
16160
16160
[Feature #18576]
Since outright renaming `ASCII-8BIT` is deemed to backward incompatible,
the next best thing would be to only change its `#inspect`, particularly
in exception messages.
rb_reg_desc was not safe for GC compaction because it took in the C
string and length but not the backing String object so it get moved
during compaction. This commit changes rb_reg_desc to use the string
from the Regexp object.
The test fails when RGENGC_CHECK_MODE is turned on:
TestRegexp#test_inspect_under_gc_compact_stress [test/ruby/test_regexp.rb:474]:
<"(?-mix:\\/)|"> expected but was
<"/\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00/">.
The test fails when RGENGC_CHECK_MODE is turned on:
TestRegexp#test_match_under_gc_compact_stress:
NoMethodError: undefined method `match' for nil
test_regexp.rb:878:in `block in test_match_under_gc_compact_stress'
The test fails when RGENGC_CHECK_MODE is turned on:
TestRegexp#test_to_s_under_gc_compact_stress = 13.46 s
1) Failure:
TestRegexp#test_to_s_under_gc_compact_stress [test/ruby/test_regexp.rb:81]:
<"(?-mix:abcd\u3042)"> expected but was
<"(?-mix:\u5C78\u3030\u5C78\u3030\u5C78\u3030\u5C78\u3030\u5C78\u3030)">.
* 🐛 Fixes [Bug #20039](https://bugs.ruby-lang.org/issues/20039)
When a Regexp is initialized with another Regexp, we simply copy the
properties from the original. However, the flags on the original were
not being copied correctly. This caused an issue when the original had
multibyte characters and was being compared with an ASCII string.
Without the forced encoding flag (`KCODE_FIXED`) transferred on to the
new Regexp, the comparison would fail. See the included test for an
example.
Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
- Rename regexp.rdoc to exclude from "Pages". This file is for to be
included in the "class Regexp" document, but it also appeared as a
separate page duplicately.
- Fix links on case-sensitive filesystems.
- Fix to use rdoc-ref instead of converted HTML page names.
When matching an incompatible encoding, the Regexp needs to recompile.
If `usecnt == 0`, then we can reuse the `ptr` because nothing else is
using it. This avoids allocating another `regex_t`.
This speeds up matches that switch to incompatible encodings by 15%.
Branch:
```
Regex#match? with different encoding
1.431M (± 1.3%) i/s - 7.264M in 5.076153s
Regex#match? with same encoding
16.858M (± 1.1%) i/s - 85.347M in 5.063279s
```
Base:
```
Regex#match? with different encoding
1.248M (± 2.0%) i/s - 6.342M in 5.083151s
Regex#match? with same encoding
16.377M (± 1.1%) i/s - 82.519M in 5.039504s
```
Script:
```
regex = /foo/
str1 = "日本語"
str2 = "English".force_encoding("ASCII-8BIT")
Benchmark.ips do |x|
x.report("Regex#match? with different encoding") do |times|
i = 0
while i < times
regex.match?(str1)
regex.match?(str2)
i += 1
end
end
x.report("Regex#match? with same encoding") do |times|
i = 0
while i < times
regex.match?(str1)
i += 1
end
end
end
```
Existing strscan releases rely on this C API. It means that the current
Ruby master doesn't work if your Gemfile.lock has strscan unless it's
locked to 3.0.7, which is not released yet.
To fix it, let's not remove the C API we've exposed to users.
rb_reg_onig_match performs preparation, error handling, and cleanup for
matching a regex against a string. This reduces repetitive code and
removes the need for StringScanner to access internal data of regex.
This was broken in ec3542229b. That commit
didn't handle cases where extended mode was turned on/off inside the
regexp. There are two ways to turn extended mode on/off:
```
/(?-x:#y)#z
/x =~ '#y'
/(?-x)#y(?x)#z
/x =~ '#y'
```
These can be nested inside the same regexp:
```
/(?-x:(?x)#x
(?-x)#y)#z
/x =~ '#y'
```
As you can probably imagine, this makes handling these regexps
somewhat complex. Due to the nesting inside portions of regexps,
the unassign_nonascii function needs to be recursive. In
recursive mode, it needs to track both opening and closing
parentheses, similar to how it already tracked opening and
closing brackets for character classes.
When scanning the regexp and coming to `(?` not followed by `#`,
scan for options, and use `x` and `i` to determine whether to
turn on or off extended mode. For `:`, indicting only the
current regexp section should have the extended mode
switched, recurse with the extended mode set or unset. For `)`,
indicating the remainder of the regexp (or current regexp portion
if already recursing) should turn extended mode on or off, just
change the extended mode flag and keep scanning.
While testing this, I noticed that `a`, `d`, and `u` are accepted
as options, in addition to `i`, `m`, and `x`, but I can't see
where those options are documented. I'm not sure whether or not
handling `a`, `d`, and `u` as options is a bug.
Fixes [Bug #19379]
Previously, only certain values of the 3rd argument triggered a
deprecation warning.
First step for fix for bug #18797. Support for the 3rd argument
will be removed after the release of Ruby 3.2.
Fix minor fallout discovered by the tests.
Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>