From 9dd3c3ab5799e9a1253a5c8ae48e2b99428179eb Mon Sep 17 00:00:00 2001 From: Will Lillis Date: Fri, 30 Jan 2026 02:49:38 -0500 Subject: [PATCH 1/2] fix: don't panic on malformed notification/request --- asm-lsp/handle.rs | 54 +++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/asm-lsp/handle.rs b/asm-lsp/handle.rs index 77c5952a..2d2954ee 100644 --- a/asm-lsp/handle.rs +++ b/asm-lsp/handle.rs @@ -25,6 +25,28 @@ use crate::{ send_empty_resp, text_doc_change_to_ts_edit, }; +// A bug in Neovim can cause client->server RPC messages to be corrupted. If this +// happens, log the error and return instead of panicking. +macro_rules! cast_req { + ($req:expr, $r:ty) => {{ + let Ok(request) = cast_req::<$r>($req) else { + error!("Failed to cast request of type {}", <$r>::METHOD); + return Ok(()); + }; + request + }}; +} + +macro_rules! cast_notif { + ($notif:expr, $r:ty) => {{ + let Ok(notification) = cast_notif::<$r>($notif) else { + error!("Failed to cast notification of type {}", <$r>::METHOD); + return Ok(()); + }; + notification + }}; +} + /// Handles `Request`s from the lsp client /// /// # Errors @@ -46,7 +68,7 @@ pub fn handle_request( let start = std::time::Instant::now(); match req.method.as_str() { HoverRequest::METHOD => { - let (id, params) = cast_req::(req).expect("Failed to cast hover request"); + let (id, params) = cast_req!(req, HoverRequest); handle_hover_request( connection, id, @@ -62,8 +84,7 @@ pub fn handle_request( ); } Completion::METHOD => { - let (id, params) = - cast_req::(req).expect("Failed to cast completion request"); + let (id, params) = cast_req!(req, Completion); handle_completion_request( connection, id, @@ -79,8 +100,7 @@ pub fn handle_request( ); } GotoDefinition::METHOD => { - let (id, params) = - cast_req::(req).expect("Failed to cast completion request"); + let (id, params) = cast_req!(req, GotoDefinition); handle_goto_def_request(connection, id, ¶ms, doc_store)?; info!( "{} request serviced in {}ms", @@ -89,8 +109,7 @@ pub fn handle_request( ); } DocumentSymbolRequest::METHOD => { - let (id, params) = - cast_req::(req).expect("Failed to cast completion request"); + let (id, params) = cast_req!(req, DocumentSymbolRequest); handle_document_symbols_request(connection, id, ¶ms, doc_store)?; info!( "{} request serviced in {}ms", @@ -99,8 +118,7 @@ pub fn handle_request( ); } SignatureHelpRequest::METHOD => { - let (id, params) = - cast_req::(req).expect("Failed to cast completion request"); + let (id, params) = cast_req!(req, SignatureHelpRequest); handle_signature_help_request( connection, id, @@ -116,8 +134,7 @@ pub fn handle_request( ); } References::METHOD => { - let (id, params) = - cast_req::(req).expect("Failed to cast completion request"); + let (id, params) = cast_req!(req, References); handle_references_request(connection, id, ¶ms, doc_store)?; info!( "{} request serviced in {}ms", @@ -126,8 +143,7 @@ pub fn handle_request( ); } DocumentDiagnosticRequest::METHOD => { - let (_id, params) = cast_req::(req) - .expect("Failed to cast completion request"); + let (_id, params) = cast_req!(req, DocumentDiagnosticRequest); let project_config = config.get_config(¶ms.text_document.uri); // Ok to unwrap, this should never be `None` if project_config.opts.as_ref().unwrap().diagnostics.unwrap() { @@ -179,8 +195,7 @@ pub fn handle_notification( let start = std::time::Instant::now(); match notif.method.as_str() { DidOpenTextDocument::METHOD => { - let params = cast_notif::(notif) - .expect("Failed to cast did open text document notification"); + let params = cast_notif!(notif, DidOpenTextDocument); handle_did_open_text_document_notification(¶ms, doc_store); info!( "{} notification serviced in {}ms", @@ -189,8 +204,7 @@ pub fn handle_notification( ); } DidChangeTextDocument::METHOD => { - let params = cast_notif::(notif) - .expect("Failed to cast did change text document notification"); + let params = cast_notif!(notif, DidChangeTextDocument); handle_did_change_text_document_notification(¶ms, doc_store)?; info!( "{} notification serviced in {}ms", @@ -199,8 +213,7 @@ pub fn handle_notification( ); } DidCloseTextDocument::METHOD => { - let params = cast_notif::(notif) - .expect("Failed to cast did close text document notification"); + let params = cast_notif!(notif, DidCloseTextDocument); handle_did_close_text_document_notification(¶ms, doc_store); info!( "{} notification serviced in {}ms", @@ -209,8 +222,7 @@ pub fn handle_notification( ); } DidSaveTextDocument::METHOD => { - let params = cast_notif::(notif) - .expect("Failed to cast did save text document notification"); + let params = cast_notif!(notif, DidSaveTextDocument); let project_config = config.get_config(¶ms.text_document.uri); // Ok to unwrap, this should never be `None` if project_config.opts.as_ref().unwrap().diagnostics.unwrap() { From ad930550424631fe9086475b8631e112e6a57dfc Mon Sep 17 00:00:00 2001 From: Will Lillis Date: Fri, 30 Jan 2026 02:53:30 -0500 Subject: [PATCH 2/2] fix(rust): misc clippy lints --- asm-lsp/config_builder.rs | 2 +- asm-lsp/lsp.rs | 2 +- asm-lsp/parser.rs | 7 ++----- asm-lsp/test.rs | 2 +- asm-lsp/types.rs | 9 +++++---- asm_docs_parsing/src/main.rs | 5 +---- 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/asm-lsp/config_builder.rs b/asm-lsp/config_builder.rs index bc7cff2e..77510aea 100644 --- a/asm-lsp/config_builder.rs +++ b/asm-lsp/config_builder.rs @@ -3,7 +3,7 @@ use std::string::ToString; use std::{env::current_dir, path::PathBuf}; use anyhow::{Result, anyhow}; -use clap::{Args, arg}; +use clap::Args; use dialoguer::{Confirm, FuzzySelect, Input, theme::ColorfulTheme}; use dirs::config_dir; diff --git a/asm-lsp/lsp.rs b/asm-lsp/lsp.rs index a3b65a75..6f21360b 100644 --- a/asm-lsp/lsp.rs +++ b/asm-lsp/lsp.rs @@ -890,7 +890,7 @@ pub fn get_completes( ( *arch_or_asm, CompletionItem { - label: (*name).to_string(), + label: (*name).clone(), kind, documentation: Some(Documentation::MarkupContent(MarkupContent { kind: MarkupKind::Markdown, diff --git a/asm-lsp/parser.rs b/asm-lsp/parser.rs index ec4ecbf2..a5bfeaad 100644 --- a/asm-lsp/parser.rs +++ b/asm-lsp/parser.rs @@ -762,11 +762,8 @@ pub fn populate_6502_instructions(html_conts: &str) -> Result> section_start + start_marker.len() + 1 // + 1 for '\n' }; let mut lines = html_conts[start..].lines().peekable(); - loop { - // opcode id - let Some(name_line) = lines.next() else { - break; - }; + // opcode id + while let Some(name_line) = lines.next() { if name_line.is_empty() { continue; } diff --git a/asm-lsp/test.rs b/asm-lsp/test.rs index 9c445adb..08f4677e 100644 --- a/asm-lsp/test.rs +++ b/asm-lsp/test.rs @@ -679,7 +679,7 @@ mod tests { uri: uri.clone(), language_id: "asm".to_string(), version: 0, - text: source_code.to_string(), + text: source_code.clone(), }, }; let params = serde_json::to_value(did_open_params).unwrap(); diff --git a/asm-lsp/types.rs b/asm-lsp/types.rs index 3d463472..4fd6ed86 100644 --- a/asm-lsp/types.rs +++ b/asm-lsp/types.rs @@ -437,11 +437,12 @@ pub struct AvrTiming { impl Display for AvrTiming { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Timing: ")?; - let mut has_prev = false; - if let Some(ref cycles) = self.avre { + let mut has_prev = if let Some(ref cycles) = self.avre { write!(f, "AVRE: {cycles}")?; - has_prev = true; - } + true + } else { + false + }; if let Some(ref cycles) = self.avrxm { if has_prev { write!(f, " | ")?; diff --git a/asm_docs_parsing/src/main.rs b/asm_docs_parsing/src/main.rs index 1057cdc5..a0d2d6c2 100644 --- a/asm_docs_parsing/src/main.rs +++ b/asm_docs_parsing/src/main.rs @@ -64,10 +64,7 @@ fn run(opts: &SerializeDocs) -> Result<()> { let instrs: Vec; match (path.is_dir(), opts.arch) { (true, Some(arch_in)) => match arch_in { - Arch::ARM => { - instrs = populate_arm_instructions(&opts.input_path)?; - } - Arch::ARM64 => { + Arch::ARM | Arch::ARM64 => { instrs = populate_arm_instructions(&opts.input_path)?; } Arch::RISCV => {