name = "crc32fast"
description = """
ETNA workload for the Rust `crc32fast` crate. Each variant re-introduces
one historical bug-fix into a fresh patched branch and pairs it with a
framework-neutral property, four PBT adapters, and a deterministic
witness test.
"""
language = "rust"
strategies = ["proptest", "quickcheck", "crabcheck", "hegel"]
crate = "crc32fast"
base_commit = "82213eae5758a151dd58a128839038d9c0a559a9"

[[tasks]]
mutations = ["combine_zero_length_identity_724ceb6_1"]

[tasks.source]
repo = "https://github.com/srijs/rust-crc32fast"
commits = ["724ceb6d7f0b24fd2ac2be3461bdcefdae619703"]
commit_subjects = ["cover special case in combine"]
summary = "`combine()` missed a zero-length short-circuit: with `len2 == 0` and a non-zero `crc2`, the main path XOR'd `crc2` into `crc1` and returned `crc1 ^ crc2` instead of `crc1`. The fix adds `if len2 == 0 { return crc1; }` at the top of the function."

[tasks.injection]
kind = "patch"
patch = "patches/combine_zero_length_identity_724ceb6_1.patch"
files = ["src/combine.rs"]
locations = [{ file = "src/combine.rs", line = 24 }]

[tasks.bug]
short_name = "combine_zero_length_identity"
invariant = "combining a `Hasher` with a zero-length `other` (even one whose internal CRC state is non-zero) must leave the receiver's finalized CRC unchanged — `combine(crc1, crc2, 0) == crc1`."
how_triggered = "deleting the `if len2 == 0 { return crc1; }` early-return in `combine::combine` leaves the subsequent `p ^ crc2` path in charge for zero-length inputs. With `p = crc1` and a non-zero `crc2`, the function returns `crc1 ^ crc2` instead of `crc1`. The witness reaches this via `Hasher::new_with_initial(0x1)` → `Hasher::combine`, which forwards the initialized-but-unused hasher's state as `crc2` with `amount == 0`."

[[tasks.tasks]]
property = "CombineZeroLengthIdentity"
witnesses = [
  { test_fn = "witness_combine_zero_length_identity_case_one_zero" },
  { test_fn = "witness_combine_zero_length_identity_case_deadbeef_cafebabe" },
]
