name = "ordered-float"
language = "rust"
strategies = ["proptest", "quickcheck", "crabcheck", "hegel"]
crate = "ordered-float"
base_commit = "8f39636a8bf205b9452847ddde3f3fa7ea7e4bf5"

[[tasks]]
mutations = ["hash_signed_zero_3aa8aa5_1"]

[tasks.source]
repo = "https://github.com/reem/rust-ordered-float"
commits = ["3aa8aa506b3231712958093ee513b37206a474da"]
commit_subjects = ["Hash zero consistently"]
summary = "`OrderedFloat`'s `Hash` impl hashed the raw `to_bits()`, so `+0.0` and `-0.0` — which compare equal — hashed to different bit patterns, violating the Hash/Eq contract; the fix canonicalizes zero by adding `0.0` before hashing."

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

[tasks.bug]
short_name = "hash_signed_zero"
invariant = "If `a == b` then `hash(a) == hash(b)`."
how_triggered = "`SealedTrait::canonical_bits` on `f32`/`f64` drops the `(self + 0.0)` canonicalization and returns `self.to_bits()` directly, so `+0.0` and `-0.0` hash to different bit patterns while still comparing equal."

[[tasks.tasks]]
property = "HashMatchesEq"
witnesses = [
  { test_fn = "witness_hash_matches_eq_case_signed_zero" },
  { test_fn = "witness_hash_matches_eq_notnan_case_signed_zero" },
]

[[tasks]]
mutations = ["notnan_binop_skip_nan_check_8d56a3c_1"]

[tasks.source]
repo = "https://github.com/reem/rust-ordered-float"
commits = ["8d56a3c35bde346cdc91c2736ab0e36ffde85b7f"]
commit_subjects = ["Check result of NotNan addition for NaN"]
summary = "`NotNan`'s arithmetic impls returned `NotNan(self.0 + other.0)` directly, so `INF + (-INF)`, `0.0 * INF`, and `0.0 / 0.0` silently produced a `NotNan` wrapping NaN; the fix re-enters `NotNan::new` on the result so the constructor can panic on NaN."

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

[tasks.bug]
short_name = "notnan_binop_skip_nan_check"
invariant = "Arithmetic on `NotNan<T>` either panics or yields a `NotNan` whose inner value is not NaN."
how_triggered = "`impl_not_nan_binop!` constructs `NotNan(self.0.$method(other.0))` directly instead of routing through `NotNan::new(...).expect(...)`, so `INF + (-INF)`, `0.0 * INF`, and `0.0 / 0.0` silently produce `NotNan(NaN)`."

[[tasks.tasks]]
property = "NotnanBinopNoNan"
witnesses = [
  { test_fn = "witness_notnan_binop_no_nan_case_add_inf_minus_inf" },
  { test_fn = "witness_notnan_binop_no_nan_case_mul_zero_inf" },
  { test_fn = "witness_notnan_binop_no_nan_case_div_zero_zero" },
]

[[tasks]]
mutations = ["partial_cmp_delegates_to_inner_184a381_1"]

[tasks.source]
repo = "https://github.com/reem/rust-ordered-float"
commits = ["184a3816e2702a89b68ceca3cb58c16cfe4fd0c6"]
commit_subjects = ["fix partialord implementation for orderedfloat"]
summary = "`OrderedFloat::partial_cmp` delegated to the inner float's `partial_cmp`, which returns `None` when either operand is NaN; that disagrees with `Ord::cmp`'s total order (`NaN == NaN`) and breaks the `partial_cmp == Some(cmp)` invariant."

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

[tasks.bug]
short_name = "partial_cmp_delegates_to_inner"
invariant = "`a.partial_cmp(&b) == Some(a.cmp(&b))` whenever both traits are implemented."
how_triggered = "`<OrderedFloat<T> as PartialOrd>::partial_cmp` delegates to the inner float's `partial_cmp`, which returns `None` for NaN operands while `Ord::cmp` still produces a total order."

[[tasks.tasks]]
property = "PartialCmpMatchesCmp"
witnesses = [
  { test_fn = "witness_partial_cmp_matches_cmp_case_nan_vs_zero" },
  { test_fn = "witness_partial_cmp_matches_cmp_case_nan_vs_nan" },
]

[[tasks]]
mutations = ["notnan_assign_not_panic_safe_c55cda3_1"]

[tasks.source]
repo = "https://github.com/reem/rust-ordered-float"
commits = ["c55cda301c943270b7eb2b4765bedbcce56edb90"]
commit_subjects = ["Make assignment operators panic safe"]
summary = "`NotNan`'s `AddAssign`/`MulAssign`/… performed the inner op in place and asserted afterwards, so when the result was NaN the panic left the receiver holding NaN — a `catch_unwind` caller could then observe a broken wrapper; the fix computes the result first and only commits once it's known to be non-NaN."

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

[tasks.bug]
short_name = "notnan_assign_not_panic_safe"
invariant = "If a `NotNan` assign operator panics, the receiver still holds a non-NaN value."
how_triggered = "`impl_not_nan_binop!`'s assign impl mutates `self.0` first with the raw inner op and asserts afterwards, so when the assert fires the `NotNan` is already holding NaN and a `catch_unwind` caller observes the broken wrapper."

[[tasks.tasks]]
property = "AssignPanicSafe"
witnesses = [
  { test_fn = "witness_assign_panic_safe_case_add_inf_minus_inf" },
  { test_fn = "witness_assign_panic_safe_case_mul_zero_inf" },
  { test_fn = "witness_assign_panic_safe_case_div_zero_zero" },
]
