mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
fix(regular_expression): allow oversized decimal escape for Annex B (#26070)
Follow up on #26055. Saturate oversized decimal escapes instead of failing during number parsing. Preserve Annex B fallback in non-Unicode mode while rejecting invalid backreferences in `u|v` modes.
This commit is contained in:
1 parent
fc175b09d3
commit
9b516587ab
2 files changed
+26
-11
No files matched your search
@@ -151,7 +151,6 @@ mod test {
|
||||
("x{9007199254740992}", ""),
|
||||
("x{9007199254740991,9007199254740992}", ""),
|
||||
("x{99999999999999999999999999999999999999999999999999}", ""),
|
||||
(r"\99999999999999999999999999999999999999999999999999", ""),
|
||||
(r"\u{FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF}", "u"),
|
||||
("(?=a", ""),
|
||||
("(?<!a", ""),
|
||||
@@ -292,7 +291,14 @@ mod test {
|
||||
#[test]
|
||||
fn oversized_decimal_backreferences() {
|
||||
let allocator = Allocator::default();
|
||||
let patterns = [r"()\4294967295", r"()\4294967296", r"()\4294967297"];
|
||||
let patterns = [
|
||||
r"()\4294967295", // u32::MAX
|
||||
r"()\4294967296", // u32::MAX + 1, previously wrapped to 0
|
||||
r"()\4294967297", // u32::MAX + 2, previously wrapped to 1
|
||||
r"()\18446744073709551615", // u64::MAX
|
||||
r"()\18446744073709551616", // u64::MAX + 1
|
||||
r"()\999999999999999999999999999999999999", // Arbitrarily large
|
||||
];
|
||||
|
||||
for pattern_text in patterns {
|
||||
for flags_text in ["u", "v"] {
|
||||
|
||||
@@ -470,7 +470,7 @@ impl<'a> PatternParser<'a> {
|
||||
let checkpoint = self.reader.checkpoint();
|
||||
|
||||
// DecimalEscape: \1 means indexed reference
|
||||
if let Some(index) = self.consume_decimal_escape()? {
|
||||
if let Some(index) = self.consume_decimal_escape() {
|
||||
if self.state.unicode_mode {
|
||||
// [SS:EE] AtomEscape :: DecimalEscape
|
||||
// It is a Syntax Error if the CapturingGroupNumber of DecimalEscape is strictly greater than CountLeftCapturingParensWithin(the Pattern containing AtomEscape).
|
||||
@@ -1778,19 +1778,28 @@ impl<'a> PatternParser<'a> {
|
||||
// DecimalEscape ::
|
||||
// NonZeroDigit DecimalDigits[~Sep][opt] [lookahead ∉ DecimalDigit]
|
||||
// ```
|
||||
fn consume_decimal_escape(&mut self) -> Result<Option<u64>> {
|
||||
fn consume_decimal_escape(&mut self) -> Option<u64> {
|
||||
let checkpoint = self.reader.checkpoint();
|
||||
|
||||
if let Some(index) = self.consume_decimal_digits()? {
|
||||
// \0 is CharacterEscape, not DecimalEscape
|
||||
if index != 0 {
|
||||
return Ok(Some(index));
|
||||
}
|
||||
let mut index = 0_u64;
|
||||
while let Some(cp) = self.reader.peek().filter(|&cp| character::is_decimal_digit(cp)) {
|
||||
// `- '0' as u32`: convert code point to digit
|
||||
#[expect(clippy::cast_lossless)]
|
||||
let digit = (cp - '0' as u32) as u64;
|
||||
|
||||
self.reader.rewind(checkpoint);
|
||||
// The exact value is irrelevant once it exceeds the capture count.
|
||||
// Saturate instead of erroring so Annex B can rewind and reinterpret the entire escape.
|
||||
index = index.saturating_mul(10).saturating_add(digit);
|
||||
self.reader.advance();
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
// \0 is CharacterEscape, not DecimalEscape
|
||||
if index != 0 {
|
||||
return Some(index);
|
||||
}
|
||||
|
||||
self.reader.rewind(checkpoint);
|
||||
None
|
||||
}
|
||||
|
||||
// ```
|
||||
|
||||
Reference in new issue
Block a user