{"record":{"id":"41ce450f900abd0f","repo":"pydantic/monty","slug":"deserialize","errorCode":null,"errorMessage":"deserialize","messagePattern":"deserialize","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/monty/src/heap/mod.rs","lineNumber":2540,"sourceCode":"\n    #[test]\n    fn pending_purple_cycle_round_trips_through_serde() {\n        // A snapshot can be taken between any two bytecode instructions, so\n        // entries flagged Purple by `dec_ref` but not yet visited by the\n        // collector must survive serde round-trips. Otherwise a cycle that\n        // becomes garbage just before snapshot would leak permanently after\n        // restore (the post-restore VM would never re-touch it).\n        let mut heap = Heap::new(16, ResourceTracker::default());\n        let id = alloc_self_cycle(&heap);\n        // Drop the caller's external ref so the entry is genuinely\n        // unreachable except via its self-pointer. dec_ref flags Purple.\n        heap.dec_ref(id); // rc 2 → 1\n        assert_eq!(heap.purple_count, 1);\n        assert_eq!(heap.entries.get(id).color.get(), CcColor::Purple);\n\n        // Round-trip through postcard.\n        let bytes = postcard::to_allocvec(&heap).expect(\"serialize\");\n        let mut restored: Heap = postcard::from_bytes(&bytes).expect(\"deserialize\");\n\n        // `purple_count` and the per-entry color must round-trip.\n        assert_eq!(restored.purple_count, 1);\n        assert_eq!(restored.entries.get(id).color.get(), CcColor::Purple);\n\n        // Run the collector on the restored heap; the cycle is unreachable\n        // and must be reclaimed.\n        restored.collect_cycles();\n        assert!(!is_alive(&restored, id));\n        assert_eq!(restored.purple_count, 0);\n    }\n\n    #[test]\n    fn isolated_cycle_with_duplicate_child_refs_is_collected() {\n        // Regression: an unreachable cycle where one element references its\n        // sibling multiple times within its child list. The mark phase must\n        // (a) decrement the sibling's refcount once per edge, and (b) push\n        // the sibling onto the work stack at most once, even when many","sourceCodeStart":2522,"sourceCodeEnd":2558,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty/src/heap/mod.rs#L2522-L2558","documentation":"Panic from `postcard::from_bytes::<Heap>(&bytes).expect(\"deserialize\")` (crates/monty/src/heap/mod.rs:2540), the second half of the same round-trip test. Deserializing failed: either the bytes were produced by an incompatible schema version (heap layout changed between serialize and deserialize paths) or `Heap`'s `Deserialize` impl rejects the encoded data (e.g. variant out of range, invalid enum discriminant, postcard format error).","triggerScenarios":"Deserialize mismatch after adding/reordering `HeapData` or `HeapEntry` fields so the wire layout no longer matches; a hand-written `Deserialize` impl that validates fields and errors on restored state (e.g. refcount or free-list invariants); or corrupted/empty byte input passed to `from_bytes`.","commonSituations":"Mid-refactor states where serialize uses new field layout but the Deserialize impl (or vice versa) is stale; tests that serialize a Heap with entries whose Deserialize path expects an initialized free list or page structure that postcard cannot reconstruct.","solutions":["Print or unwrap_err the postcard::Error to identify the failing field/variant.","Regenerate both Serialize and Deserialize impls together so field order and variants match exactly.","Ensure enum/struct derives use the same representation on both sides (no `#[serde(skip)]` on fields the Deserialize path requires).","If the heap holds non-serializable state, serialize only the durable data and reconstruct transient state (colors, purple_count) in the Deserialize impl or after `from_bytes`."],"exampleFix":"// before\nlet mut restored: Heap = postcard::from_bytes(&bytes).expect(\"deserialize\");\n// after\nlet mut restored: Heap = postcard::from_bytes(&bytes)\n    .unwrap_or_else(|e| panic!(\"deserialize failed: {e:?}\"));","handlingStrategy":"try-catch","validationCode":"let bytes = postcard::to_allocvec(&heap).expect(\"serialize\");\nassert!(!bytes.is_empty(), \"serialized heap must not be empty\");","typeGuard":null,"tryCatchPattern":"match postcard::from_bytes::<Heap>(&bytes) {\n    Ok(restored) => { /* proceed */ }\n    Err(e) => panic!(\"deserialize failed: {e:?}\"),\n}","preventionTips":["Update Serialize and Deserialize impls in lockstep — never one without the other.","Avoid reordering or removing HeapData variants; add new variants only at the end.","Surface the postcard::Error with unwrap_or_else instead of a bare expect so failures are diagnosable."],"tags":["rust","serde","postcard","deserialization","heap"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}