{"record":{"id":"be40370d70f82b4f","repo":"rust-lang/rust-analyzer","slug":"syntax-annotation-id-overflow","errorCode":null,"errorMessage":"syntax annotation id overflow","messagePattern":"syntax annotation id overflow","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/syntax/src/syntax_editor.rs","lineNumber":276,"sourceCode":"\n        self.new_root\n            .descendants()\n            .find(|it| it.kind() == kind && it.text_range().start() - new_root_start == old_start)\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]\n#[repr(transparent)]\npub struct SyntaxAnnotation(NonZeroU32);\n\nimpl Default for SyntaxAnnotation {\n    fn default() -> Self {\n        static COUNTER: AtomicU32 = AtomicU32::new(1);\n\n        // Only consistency within a thread matters, as SyntaxElements are !Send\n        let id = COUNTER.fetch_add(1, Ordering::Relaxed);\n\n        Self(NonZeroU32::new(id).expect(\"syntax annotation id overflow\"))\n    }\n}\n\n/// Position describing where to insert elements\n#[derive(Debug)]\npub struct Position {\n    repr: PositionRepr,\n}\n\nimpl Position {\n    pub(crate) fn parent(&self) -> SyntaxNode {\n        self.place().0\n    }\n\n    pub(crate) fn place(&self) -> (SyntaxNode, usize) {\n        match &self.repr {\n            PositionRepr::FirstChild(parent) => (parent.clone(), 0),\n            PositionRepr::After(child) => (child.parent().unwrap(), child.index() + 1),","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/rust-lang/rust-analyzer/blob/e8f7e90aa3e7b26aa9a000200f606c1078da99ec/crates/syntax/src/syntax_editor.rs#L258-L294","documentation":"The syntax editor hands each annotation a unique nonzero u32 id from a per-process `AtomicU32` counter. When the counter reaches `u32::MAX`, the next increment yields 0, which `NonZeroU32::new` rejects, so the code panics with 'syntax annotation id overflow'. It only fires after ~4.29 billion annotations have been created, which the authors deem impossible in practice.","triggerScenarios":"Creating more than `u32::MAX - 1` syntax annotations in one process lifetime, i.e. repeatedly running `SyntaxEditor`-based rewrites (each annotation allocation bumps the counter) over an extremely long-lived process such as an IDE/language-server session.","commonSituations":"An editor or LSP process kept alive for months continuously re-parsing and rewriting files with annotation-heavy edits; a runaway loop that creates editors/annotations without bound.","solutions":["Restart the long-running process so the counter resets (the counter is process-global)","Fix the unbounded loop that is creating annotations/editors in a hot path","Reduce annotation churn by reusing one editor per batch of edits instead of creating editors per edit","Patch to use AtomicU64 or wrap-with-reuse if you genuinely need more than 2^32 annotation ids"],"exampleFix":"null","handlingStrategy":"retry","validationCode":"// No pre-call check exists; only mitigate structurally:\n// bound the number of SyntaxEditor annotations per process (e.g. a counter guard).\nlet annotations_created = AtomicU64::new(0);\nif annotations_created.fetch_add(1, Ordering::Relaxed) > u32::MAX as u64 {\n    // fail fast / restart process instead of panicking deep in NonZeroU32\n}","typeGuard":null,"tryCatchPattern":"// This is a panic, not a Result; catch at a process/task boundary.\nlet result = std::panic::catch_unwind(AssertUnwindSafe(|| run_edit_batch()));\nif result.is_err() { restart_worker(); }","preventionTips":["Do not create SyntaxEditors/annotations in unbounded loops","Recycle a single editor per batch of edits","Restart long-running IDE processes periodically or on memory/counter pressure"],"tags":["rust","overflow","panic","long-running-process","syntax-editor"],"backgroundTag":"integer-overflow","analyzedSha":"e8f7e90aa3e7b26aa9a000200f606c1078da99ec","analyzedAt":"2026-09-03T21:08:06.959Z","contentChangedAt":"2026-09-03T21:08:06.959Z","schemaVersion":2},"datasetVersion":"2026-09-11T07:07:21.782Z"}