Do not parse past an empty capture in MatchData#integer_at

rb_int_parse_cstr, with an explicit base of 2/8/10/16 and
RB_INT_PARSE_PREFIX set, took the prefix branch when the length was 0:
the `len == 1` guard excluded single-byte input but not empty input, so
it inspected str[0]/str[1] (bytes past the given range) and, on a
0x/0b/0o/0d prefix, ran ADV(2). ADV only bails when `len > 0`, so len
became negative, which the parser reads as a NUL-terminated string and
keeps scanning.

MatchData#integer_at passes the capture's byte range to
rb_int_parse_cstr, so an empty capture parsed the bytes after it:
`/(?<h>[g-z]*)/.match("0xff").integer_at(:h, 16)` returned 255 instead
of nil. Treat length 0 as "no prefix" so the empty-input path falls
through to the existing `if (!len) goto bad`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard Desmarais 2026-11-02 17:41:00 -05:00 committed by Nobuyoshi Nakada
parent d401720c41
commit b6bdc438a0
Notes: git 2026-11-04 07:29:50 +00:00
2 changed files with 10 additions and 1 deletions

View File

@ -4198,7 +4198,7 @@ rb_int_parse_cstr(const char *str, ssize_t len, char **endp, size_t *ndigits,
base = 10;
}
}
else if (len == 1 || !(flags & RB_INT_PARSE_PREFIX)) {
else if (len == 0 || len == 1 || !(flags & RB_INT_PARSE_PREFIX)) {
/* no prefix */
}
else if (base == 2) {

View File

@ -1817,6 +1817,15 @@ class TestRegexp < Test::Unit::TestCase
assert_equal(0x1_0000, /\w+/.match("0x1_0000").integer_at(0, 0))
end
def test_match_integer_at_empty_capture
# An empty capture must not parse bytes after it, even when those bytes
# form a 0x/0b/0o/0d prefix for the requested base. [Bug #NNNNN]
assert_nil(/(?<h>[g-z]*)/.match("0xff").integer_at(:h, 16))
assert_nil(/(?<b>[2-9]*)/.match("0b11").integer_at(:b, 2))
assert_nil(/(?<o>[89]*)/.match("0o17").integer_at(:o, 8))
assert_nil(/x(?<d>[a-z]*)/.match("x0d42").integer_at(:d))
end
def test_regexp_popped
EnvUtil.suppress_warning do
assert_nothing_raised { eval("a = 1; /\#{ a }/; a") }