Files
ladybird/Tests/LibJS/Runtime/syntax/regex-literal-errors.js
Andreas Kling c12647fc37 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.
2026-03-31 15:59:04 +02:00

139 lines
4.6 KiB
JavaScript

test("invalid regex literal pattern is a syntax error", () => {
expect("/[/").not.toEval();
expect("/(/").not.toEval();
expect("/(?/").not.toEval();
expect("/\\p{Invalid}/u").not.toEval();
});
test("duplicate regex flags are a syntax error", () => {
expect("/foo/gg").not.toEval();
expect("/foo/ii").not.toEval();
expect("/foo/gig").not.toEval();
});
test("invalid regex flag is a syntax error", () => {
expect("/foo/x").not.toEval();
expect("/foo/Q").not.toEval();
});
test("regex literal errors in eval", () => {
expect(() => eval("/[/")).toThrow(SyntaxError);
expect(() => eval("/foo/gg")).toThrow(SyntaxError);
expect(() => eval("/foo/x")).toThrow(SyntaxError);
});
test("regex literal errors in new Function()", () => {
expect(() => new Function("/[/")).toThrow(SyntaxError);
expect(() => new Function("/foo/gg")).toThrow(SyntaxError);
expect(() => new Function("/foo/x")).toThrow(SyntaxError);
});
test("mixed surrogate forms in named group names are syntax errors", () => {
for (const source of [
"/(?<a\\uD835\udcf8>.)/",
"/(?<a\ud835\\uDCF8>.)/",
"/(?<a\\uD835\\u{DCF8}>.)/",
"/(?<a\\u{D835}\\uDCF8>.)/",
"/(?<a\\u{D835}\\u{DCF8}>.)/",
]) {
expect(source).not.toEval();
expect(() => eval(source)).toThrow(SyntaxError);
expect(() => new Function(source)).toThrow(SyntaxError);
}
});
test("mixed surrogate forms in named backreferences are syntax errors", () => {
for (const source of [
"/(?<a\\uD835\\uDCF8>.)\\k<a\\uD835\udcf8>/",
"/(?<a\\uD835\\uDCF8>.)\\k<a\ud835\\uDCF8>/",
"/(?<a\\uD835\\uDCF8>.)\\k<a\\uD835\\u{DCF8}>/",
"/(?<a\\uD835\\uDCF8>.)\\k<a\\u{D835}\\uDCF8>/",
"/(?<a\\uD835\\uDCF8>.)\\k<a\\u{D835}\\u{DCF8}>/",
]) {
expect(source).not.toEval();
expect(() => eval(source)).toThrow(SyntaxError);
expect(() => new Function(source)).toThrow(SyntaxError);
}
});
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();
expect("/(?:abc)/u").toEval();
expect("/hello world/").toEval();
expect("/foo/dgimsuy").toEval();
expect("/foo/v").toEval();
});
test("named group names accept literal and escaped surrogate pairs in regex literals", () => {
for (const source of ["/(?<a\ud835\udcf8>.)/", "/(?<a\\uD835\\uDCF8>.)/", "/(?<a\\u{1D4F8}>.)/"]) {
expect(source).toEval();
expect(() => eval(source)).not.toThrow();
expect(() => new Function(source)).not.toThrow();
}
});
test("named backreferences accept literal and escaped surrogate pairs in regex literals", () => {
for (const source of [
"/(?<a\ud835\udcf8>.)\\k<a\ud835\udcf8>/",
"/(?<a\\uD835\\uDCF8>.)\\k<a\\uD835\\uDCF8>/",
"/(?<a\\u{1D4F8}>.)\\k<a\\u{1D4F8}>/",
]) {
expect(source).toEval();
expect(() => eval(source)).not.toThrow();
expect(() => new Function(source)).not.toThrow();
}
});
test("legacy \\k identity escapes remain valid regex literals", () => {
const source = "/\\k<\\uDC00>/";
expect(source).toEval();
expect(() => eval(source)).not.toThrow();
expect(() => new Function(source)).not.toThrow();
});
test("regex literals work inside functions", () => {
function f() {
return /foo/g;
}
expect(f().test("foo")).toBeTrue();
expect(f().test("bar")).toBeFalse();
});
test("regex literals work inside arrow functions", () => {
const f = () => /bar/i;
expect(f().test("BAR")).toBeTrue();
expect(f().test("baz")).toBeFalse();
});
test("multiple regex literals in the same scope", () => {
const a = /abc/;
const b = /def/g;
const c = /ghi/i;
expect(a.test("abc")).toBeTrue();
expect(b.test("def")).toBeTrue();
expect(c.test("GHI")).toBeTrue();
expect(a.test("def")).toBeFalse();
});