My knowleged in rust is pretty minimal. So I used claude and codex to help write the patch. I've reviewed everything and reproduced it locally, but flagging it so you know.
What's wrong
In CssRuleList::minify, the dedup HashMap<StyleRuleKey, usize> has a broken Hash/Eq invariant: the hash is precomputed once at insert time, but PartialEq re-reads the live rule from the Vec. When merge_style_rules mutates a rule in place, its content changes, but the hash stored in the map doesn't. Future lookups end up in the wrong bucket and miss valid duplicates.
How it surfaced for us
We hit this as build non-determinism in rspack build using LigthningCSS minimzer. Byte-identical CSS input produced different minified output across fresh process invocations at ~1–2.5%. StyleRule::hash_key() uses ahash::AHasher::default() (random per-process seed), so whether the stale hash collides with a later rule's fresh hash varies between processes.
A constant seed would hide that variance but wouldn't fix the underlying invariant violation — there's also a fully deterministic missed-dedup case, shown below.
Repro
Open in playground.
.a { border-top-left-radius: 16px }
.a { border-top-right-radius: 16px }
.a { border-bottom-left-radius: 16px }
.a { border-bottom-right-radius: 16px }
.x { color: red }
.a { border-radius: 16px }
Expected:
.x{color:red}.a{border-radius:16px}
Actual:
.a{border-radius:16px}.x{color:red}.a{border-radius:16px}
The four longhands merge into rules[0] and collapse to a border-radius shorthand. The trailing .a { border-radius: 16px } should dedup against it but doesn't, because rules[0] is still indexed under the original border-top-left-radius hash bucket.
What's wrong
In
CssRuleList::minify, the dedupHashMap<StyleRuleKey, usize>has a brokenHash/Eqinvariant: the hash is precomputed once at insert time, butPartialEqre-reads the live rule from theVec. Whenmerge_style_rulesmutates a rule in place, its content changes, but the hash stored in the map doesn't. Future lookups end up in the wrong bucket and miss valid duplicates.How it surfaced for us
We hit this as build non-determinism in rspack build using LigthningCSS minimzer. Byte-identical CSS input produced different minified output across fresh process invocations at ~1–2.5%.
StyleRule::hash_key()usesahash::AHasher::default()(random per-process seed), so whether the stale hash collides with a later rule's fresh hash varies between processes.A constant seed would hide that variance but wouldn't fix the underlying invariant violation — there's also a fully deterministic missed-dedup case, shown below.
Repro
Open in playground.
Expected:
Actual:
The four longhands merge into
rules[0]and collapse to aborder-radiusshorthand. The trailing.a { border-radius: 16px }should dedup against it but doesn't, becauserules[0]is still indexed under the originalborder-top-left-radiushash bucket.