name = "hashbrown"
description = """
ETNA workload for the Rust `hashbrown` 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 = "hashbrown"
base_commit = "9ca1c583030c6b1a6d02bf0f976a865d8122de93"

[[tasks]]
mutations = ["rehash_panic_length_6cb0eb5_1"]

[tasks.source]
repo = "https://github.com/rust-lang/hashbrown"
commits = ["6cb0eb5c55140e110ca30da1138ca763f6cebc47"]
commit_subjects = ["Fix incorrect length if a hasher panics during rehash"]
summary = "The guard block that ran during a rehash panic only cleaned up `Tag::DELETED` entries when the value type had a `Drop` impl. For tables whose values did not need drop (e.g. `u64`), a caught panic from `BuildHasher::build_hasher` left those tombstones in place and never decremented the items counter, so `len()` overshot the true entry count and iteration yielded phantom duplicates."

[tasks.injection]
kind = "patch"
files = ["src/raw.rs"]
locations = [{ file = "src/raw.rs" }]
patch = "patches/rehash_panic_length_6cb0eb5_1.patch"

[tasks.bug]
short_name = "rehash_panic_length"
invariant = "after catching a panic thrown from `BuildHasher::build_hasher` during an in-place rehash, `HashMap::len()` must equal the number of distinct reachable entries, and iteration must not yield duplicates."
how_triggered = "the unwind guard in `RawTableInner::rehash_in_place` wraps the entire DELETED-tombstone cleanup loop in `if let Some(drop) = drop { ... }`, so maps whose value type has no `Drop` impl (e.g. `u64`) never reset `Tag::DELETED` entries nor decrement `self_.items`; after the caught panic the map's reported length drifts above the number of live entries and iteration yields phantom duplicates."

[[tasks.tasks]]
property = "RehashPanicLength"
witnesses = [
  { test_fn = "witness_rehash_panic_length_case_count_zero" },
]
