name = "regex"
language = "rust"
strategies = ["proptest", "quickcheck", "crabcheck", "hegel"]
crate = "regex"
base_commit = "49e1c76759c944750fe56c17563b2d24421ee591"

[[tasks]]
mutations = ["unicode_named_value_negation_c4865a0_1"]

[tasks.source]
repo = "https://github.com/rust-lang/regex"
commits = ["c4865a0c8446a701e10b0fd987f19068f5dcc365"]
commit_subjects = ["syntax: fix negation handling in HIR translation"]
summary = "`hir_ascii_unicode_class` consulted the bare `negated` field on the AST class, which only tracks the `\\p` vs `\\P` sigil and ignores the `!=` operator inside `\\p{name!=value}`; switching to `is_negated()` folds both sources of negation together."

[tasks.injection]
kind = "patch"
files = ["regex-syntax/src/hir/translate.rs"]
locations = [{ file = "regex-syntax/src/hir/translate.rs" }]
patch = "patches/unicode_named_value_negation_c4865a0_1.patch"

[tasks.bug]
short_name = "unicode_named_value_negation"
invariant = "`\\p{gc!=Separator}` must match every character whose General_Category is *not* `Separator` and must not match any character whose General_Category is `Separator`."
how_triggered = "Replaces `ast_class.is_negated()` with the bare `ast_class.negated` field in `hir_ascii_unicode_class`. The bare field only tracks the `\\P` vs `\\p` sigil; it ignores the `!=` operator inside `\\p{name!=value}`. With the mutation, `\\p{gc!=Separator}` is lowered as its *non-negated* variant, so spaces match and letters do not — the inverse of the requested class."

[[tasks.tasks]]
property = "GcNeSeparatorNegation"
witnesses = [
  { test_fn = "witness_gc_ne_separator_negation_case_letters_vs_spaces" },
]

[[tasks]]
mutations = ["captures_get_group_overflow_ab76b86_1"]

[tasks.source]
repo = "https://github.com/rust-lang/regex"
commits = ["ab76b863b4b7bb21608a61e5fee2fc968501791c"]
commit_subjects = ["bug: fix CaptureLocations::get to handle invalid offsets"]
summary = "`CaptureLocations::get(i)` computed `(i*2, i*2 + 1)` without overflow guards; an adversarial `i` near `usize::MAX` wrapped to `0` in release builds and returned the span for the whole-match slot instead of `None`, so the fix uses `checked_mul`/`checked_add`."

[tasks.injection]
kind = "patch"
files = ["regex-automata/src/util/captures.rs"]
locations = [{ file = "regex-automata/src/util/captures.rs" }]
patch = "patches/captures_get_group_overflow_ab76b86_1.patch"

[tasks.bug]
short_name = "captures_get_group_overflow"
invariant = "For any `i >= locs.len()`, `CaptureLocations::get(i)` must return `None`. The doubling used internally to find `(start_slot, end_slot)` must not silently wrap for large `i`."
how_triggered = "Reverts the `index.checked_mul(2)?` / `checked_add(1)?` guards to the plain `(index * 2, index * 2 + 1)` arithmetic. For `index = 1 << 63`, `index * 2` wraps to `0`, and the lookup hands back the span for capture group `0` (the whole-match slot) instead of `None`. Release builds mask the wrap silently; debug builds may panic."

[[tasks.tasks]]
property = "CaptureLocationsGetOverflow"
witnesses = [
  { test_fn = "witness_capture_locations_get_overflow_case_high_bit_index" },
]

[[tasks]]
mutations = ["alternation_literal_from_alt_33898de_1"]

[tasks.source]
repo = "https://github.com/rust-lang/regex"
commits = ["33898de870fc584ce912651590d8715d1178e667"]
commit_subjects = ["syntax: fix bug in new alternation literal analysis"]
summary = "`Properties::from_alt` ANDed `is_alternation_literal()` across alternatives instead of `is_literal()`, letting a child like `(?:z|xx)@` propagate the alternation-literal flag into its parent even though it is a concatenation; OSS-Fuzz #58173 then tripped a downstream literal optimizer."

[tasks.injection]
kind = "patch"
files = ["regex-syntax/src/hir/mod.rs"]
locations = [{ file = "regex-syntax/src/hir/mod.rs" }]
patch = "patches/alternation_literal_from_alt_33898de_1.patch"

[tasks.bug]
short_name = "alternation_literal_from_alt"
invariant = "`Hir::properties().is_alternation_literal()` must be `true` only when the top-level pattern is an alternation whose alternatives are each *single literals*. An alternative like `(?:z|xx)@` is a concatenation and is not a literal, so any alternation containing it must be flagged `false`."
how_triggered = "In the `Properties::from_alt` fold, the mutation replaces `p.is_literal()` with `p.is_alternation_literal()` when ANDing the alternatives' flags. The child `(?:z|xx)@` is itself classified as alternation-literal (because its inner alternation is one), so the outer alternation `(?:z|xx)@|xx` inherits the flag even though it is not literal. The regression surfaces from OSS-Fuzz #58173, where a downstream literal optimizer trusted the flag and panicked."

[[tasks.tasks]]
property = "AlternationLiteralFromAlt"
witnesses = [
  { test_fn = "witness_alternation_literal_from_alt_case_ossfuzz_58173" },
]
