LibRegex: Clamp braced quantifier bounds to 2^31 - 1
Browsers clamp braced quantifier bounds above 2^31 - 1 before
checking whether {min,max} is in order. The parser still kept values
up to u32::MAX, so patterns like {2147483648,2147483647} were
rejected even though both bounds should collapse to the same limit.
Clamp parsed braced quantifier bounds to 2^31 - 1 as they are read.
This keeps the existing acceptance of huge exact and open-ended
quantifiers and makes the constructor and regex literal paths agree
with other engines on the out-of-order edge cases.
The RegExp runtime and syntax tests now cover accepted huge
quantifiers, clamped order validation, and huge literal forms. The
reported constructor and literal cases also match other engines.
This commit is contained in:
parent
87b22d0c04
commit
c12647fc37
3 changed files with 46 additions and 6 deletions
|
|
@ -13,6 +13,8 @@
|
|||
//! - <https://tc39.es/ecma262/#sec-parsepattern>
|
||||
use crate::ast::*;
|
||||
|
||||
const MAX_BRACED_QUANTIFIER: u32 = i32::MAX as u32;
|
||||
|
||||
/// Parse a regex pattern string with the given flags.
|
||||
///
|
||||
/// Spec entry point: `ParsePattern`.
|
||||
|
|
@ -447,18 +449,15 @@ impl Parser {
|
|||
/// - `Ok(None)` if no digits were found
|
||||
/// - `Ok(Some(n))` if a valid u32 was parsed
|
||||
///
|
||||
/// On overflow, saturates to `u32::MAX` instead of erroring,
|
||||
/// since the spec allows arbitrarily large quantifier values.
|
||||
/// Clamp braced quantifier bounds to 2^31 - 1 to match browser behavior.
|
||||
fn try_parse_decimal(&mut self) -> Result<Option<u32>, Error> {
|
||||
let start = self.pos;
|
||||
let mut value: u32 = 0;
|
||||
while let Some(ch) = self.peek() {
|
||||
if let Some(digit) = ch.to_digit(10) {
|
||||
self.pos += 1;
|
||||
value = value
|
||||
.checked_mul(10)
|
||||
.and_then(|v| v.checked_add(digit))
|
||||
.unwrap_or(u32::MAX);
|
||||
value = value.saturating_mul(10).saturating_add(digit);
|
||||
value = value.min(MAX_BRACED_QUANTIFIER);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,27 @@ describe("errors", () => {
|
|||
}).toThrowWithMessage(SyntaxError, "RegExp compile error: invalid quantifier");
|
||||
});
|
||||
|
||||
test("large quantifier bounds clamp before order validation", () => {
|
||||
for (const pattern of [
|
||||
"a{2147483648}",
|
||||
"a{2147483648,}",
|
||||
"a{2147483648,2147483647}",
|
||||
"a{2147483648,2147483648}",
|
||||
"a{99999999999999999999999999999999999999999999999999}",
|
||||
]) {
|
||||
expect(() => {
|
||||
new RegExp(pattern);
|
||||
}).not.toThrow();
|
||||
expect(new RegExp(pattern).source).toBe(pattern);
|
||||
}
|
||||
|
||||
for (const pattern of ["a{2147483647,2147483646}", "a{2147483648,2147483646}"]) {
|
||||
expect(() => {
|
||||
new RegExp(pattern);
|
||||
}).toThrowWithMessage(SyntaxError, "RegExp compile error: invalid quantifier");
|
||||
}
|
||||
});
|
||||
|
||||
test("invalid pattern (invalid group name)", () => {
|
||||
expect(() => {
|
||||
RegExp("(?<>a)");
|
||||
|
|
|
|||
|
|
@ -56,6 +56,26 @@ test("mixed surrogate forms in named backreferences are syntax errors", () => {
|
|||
}
|
||||
});
|
||||
|
||||
test("large quantifier bounds clamp before regex literal order validation", () => {
|
||||
for (const source of [
|
||||
"/a{2147483648}/",
|
||||
"/a{2147483648,}/",
|
||||
"/a{2147483648,2147483647}/",
|
||||
"/a{2147483648,2147483648}/",
|
||||
"/a{99999999999999999999999999999999999999999999999999}/",
|
||||
]) {
|
||||
expect(source).toEval();
|
||||
expect(() => eval(source)).not.toThrow();
|
||||
expect(() => new Function(source)).not.toThrow();
|
||||
}
|
||||
|
||||
for (const source of ["/a{2147483647,2147483646}/", "/a{2147483648,2147483646}/"]) {
|
||||
expect(source).not.toEval();
|
||||
expect(() => eval(source)).toThrow(SyntaxError);
|
||||
expect(() => new Function(source)).toThrow(SyntaxError);
|
||||
}
|
||||
});
|
||||
|
||||
test("valid regex literals parse and execute correctly", () => {
|
||||
expect("/foo/g").toEval();
|
||||
expect("/[a-z]+/gims").toEval();
|
||||
|
|
|
|||
Loading…
Reference in a new issue