{"record":{"id":"db66d72f38ba7557","repo":"pydantic/monty","slug":"storecell-entry-is-not-a-cell","errorCode":null,"errorMessage":"StoreCell: entry is not a Cell","messagePattern":"StoreCell: entry is not a Cell","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/monty/src/bytecode/vm/mod.rs","lineNumber":2543,"sourceCode":"        let name_str = match name {\n            Some(id) => self.interns.get_str(id).to_string(),\n            None => \"<free var>\".to_string(),\n        };\n        ExcType::name_error_free_variable(&name_str).into()\n    }\n\n    /// Pops the top of stack and stores it in a closure cell.\n    ///\n    /// The cell `HeapId` is read from the frame's local variable slot on the stack.\n    fn store_cell(&mut self, slot: u16) {\n        let value = self.pop();\n        // The guard will clean up the new value if we panic, or the old value if we swap\n        let this = self;\n        defer_drop_mut!(value, this);\n\n        let cell_id = this.cell_id_from_local(slot);\n        let HeapReadOutput::Cell(mut cell) = this.heap.read(cell_id) else {\n            panic!(\"StoreCell: entry is not a Cell\")\n        };\n        mem::swap(&mut cell.get_mut(this.heap).0, value);\n    }\n\n    /// Unbinds a closure cell: replaces its contents with `Undefined`, so a\n    /// later [`Self::load_cell`] raises the free-variable `NameError` —\n    /// CPython's `DELETE_DEREF` cleanup of a captured `except ... as` target.\n    /// The only emitter stores `None` first, so the cell is never already\n    /// unbound here (no error path, unlike [`Self::delete_global`]).\n    fn delete_cell(&mut self, slot: u16) {\n        let value = Value::Undefined;\n        // the guard drops the cell's previous contents after the swap\n        let this = self;\n        defer_drop_mut!(value, this);\n\n        let cell_id = this.cell_id_from_local(slot);\n        let HeapReadOutput::Cell(mut cell) = this.heap.read(cell_id) else {\n            panic!(\"DeleteCell: entry is not a Cell\")","sourceCodeStart":2525,"sourceCodeEnd":2561,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty/src/bytecode/vm/mod.rs#L2525-L2561","documentation":"`store_cell` swaps a new value into the heap Cell behind a local slot; this panic fires if the heap entry at `cell_id` is not a `Cell`. Cell entries are allocated once per closed-over variable, so a type mismatch means heap corruption or an interpreter bug, not a Python-level failure.","triggerScenarios":"Executing StoreCell (assigning to a free variable or a local captured by a nested function) when the cell HeapId resolves to a non-Cell heap entry; only via an interpreter bug or heap corruption.","commonSituations":"Fuzzing Monty; modifying cell allocation/freevariables handling; patches that free a cell and let the HeapId be reallocated as another type.","solutions":["File a bug with the smallest Python snippet that triggers it","Audit cell creation and `py_dec_ref_ids`/drop paths for premature frees of cell entries","Run `cargo test -p monty --features memory-model-checks` on the relevant test binary to catch the refcount bug"],"exampleFix":"// not applicable — internal interpreter bug","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Internal panic — no user-side catch; isolate and report.\nmatch monty.run(code, limits) {\n    Ok(res) => res,\n    Err(e) => report_bug(code, e),\n}","preventionTips":["Audit cell creation/drop paths for premature frees","Run memory-model-checks on cell tests after lifecycle edits","Report minimal reproducers"],"tags":["panic","internal","heap","closures"],"backgroundTag":"internal-invariant-violation","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}