Skip to main content

asm_rs/
error.rs

1//! Error types and source span tracking for diagnostics.
2
3#[allow(unused_imports)]
4use alloc::format;
5use alloc::string::String;
6#[allow(unused_imports)]
7use alloc::vec;
8use alloc::vec::Vec;
9use core::fmt;
10
11/// Source location for diagnostics.
12///
13/// Tracks the line, column, byte offset, and length of a token or construct
14/// in the original assembly source text.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct Span {
18    /// 1-based line number.
19    pub line: u32,
20    /// 1-based column number (byte offset within line).
21    pub col: u32,
22    /// 0-based byte offset from start of source.
23    pub offset: usize,
24    /// Byte length of the spanned region.
25    pub len: usize,
26}
27
28impl Span {
29    /// Create a new span.
30    #[must_use]
31    pub fn new(line: u32, col: u32, offset: usize, len: usize) -> Self {
32        Self {
33            line,
34            col,
35            offset,
36            len,
37        }
38    }
39
40    /// A dummy span for generated/internal constructs.
41    #[must_use]
42    pub fn dummy() -> Self {
43        Self {
44            line: 0,
45            col: 0,
46            offset: 0,
47            len: 0,
48        }
49    }
50}
51
52impl fmt::Display for Span {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        write!(f, "{}:{}", self.line, self.col)
55    }
56}
57
58/// The architecture for which assembly failed — carried in some error variants.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
61pub enum ArchName {
62    /// 32-bit x86.
63    X86,
64    /// 64-bit x86.
65    X86_64,
66    /// ARM A32.
67    Arm,
68    /// ARM Thumb-2.
69    Thumb,
70    /// ARMv8-A 64-bit.
71    Aarch64,
72    /// RISC-V 32-bit.
73    Rv32,
74    /// RISC-V 64-bit.
75    Rv64,
76}
77
78impl fmt::Display for ArchName {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            ArchName::X86 => write!(f, "x86"),
82            ArchName::X86_64 => write!(f, "x86_64"),
83            ArchName::Arm => write!(f, "ARM"),
84            ArchName::Thumb => write!(f, "Thumb"),
85            ArchName::Aarch64 => write!(f, "AArch64"),
86            ArchName::Rv32 => write!(f, "RV32"),
87            ArchName::Rv64 => write!(f, "RV64"),
88        }
89    }
90}
91
92/// Assembly error with source location and descriptive message.
93#[derive(Debug, Clone, PartialEq, Eq)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95pub enum AsmError {
96    /// Unknown mnemonic for the target architecture.
97    UnknownMnemonic {
98        /// The mnemonic that was not recognized.
99        mnemonic: String,
100        /// The target architecture name.
101        arch: ArchName,
102        /// Source location of the unknown mnemonic.
103        span: Span,
104    },
105
106    /// Invalid operand combination for the instruction.
107    InvalidOperands {
108        /// Description of why the operands are invalid.
109        detail: String,
110        /// Source location of the instruction.
111        span: Span,
112    },
113
114    /// Immediate value exceeds the allowed range.
115    ImmediateOverflow {
116        /// The immediate value that overflowed.
117        value: i128,
118        /// Minimum allowed value.
119        min: i128,
120        /// Maximum allowed value.
121        max: i128,
122        /// Source location of the immediate.
123        span: Span,
124    },
125
126    /// Referenced label was never defined.
127    UndefinedLabel {
128        /// The undefined label name.
129        label: String,
130        /// Source location of the reference.
131        span: Span,
132    },
133
134    /// Label was defined more than once.
135    DuplicateLabel {
136        /// The duplicated label name.
137        label: String,
138        /// Source location of the duplicate definition.
139        span: Span,
140        /// Source location of the first definition.
141        first_span: Span,
142    },
143
144    /// Branch target is out of range even after relaxation.
145    BranchOutOfRange {
146        /// The target label name.
147        label: String,
148        /// The actual displacement to the target.
149        disp: i64,
150        /// Maximum allowed displacement.
151        max: i64,
152        /// Source location of the branch instruction.
153        span: Span,
154    },
155
156    /// A branch target is not aligned to the granularity its encoding can
157    /// represent.
158    ///
159    /// PC-relative branches store their displacement pre-scaled — AArch64
160    /// shifts right by 2, Thumb and RISC-V by 1 — so the low bits of an
161    /// unaligned target have nowhere to go. Truncating them would silently
162    /// branch somewhere other than the label.
163    MisalignedBranchTarget {
164        /// The target label name.
165        label: String,
166        /// The displacement that could not be represented.
167        disp: i64,
168        /// Required alignment, in bytes.
169        alignment: u8,
170        /// Source location of the branch instruction.
171        span: Span,
172    },
173
174    /// Syntax error during lexing or parsing.
175    Syntax {
176        /// The syntax error message.
177        msg: String,
178        /// Source location of the syntax error.
179        span: Span,
180    },
181
182    /// Branch relaxation did not converge within the allowed number of passes.
183    RelaxationLimit {
184        /// Maximum number of relaxation passes allowed.
185        max: usize,
186    },
187
188    /// A configurable resource limit was exceeded (defense against DoS).
189    ResourceLimitExceeded {
190        /// Human-readable name of the resource (e.g. "statements", "labels").
191        resource: String,
192        /// The configured limit that was exceeded.
193        limit: usize,
194    },
195
196    /// Multiple errors collected during assembly.
197    Multiple {
198        /// The collected assembly errors.
199        errors: Vec<AsmError>,
200    },
201}
202
203impl fmt::Display for AsmError {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        match self {
206            AsmError::UnknownMnemonic {
207                mnemonic,
208                arch,
209                span,
210            } => {
211                write!(f, "{}: unknown mnemonic '{}' for {}", span, mnemonic, arch)
212            }
213            AsmError::InvalidOperands { detail, span } => {
214                write!(f, "{}: invalid operand combination: {}", span, detail)
215            }
216            AsmError::ImmediateOverflow {
217                value,
218                min,
219                max,
220                span,
221            } => {
222                write!(
223                    f,
224                    "{}: immediate value {} out of range [{}..{}]",
225                    span, value, min, max
226                )
227            }
228            AsmError::UndefinedLabel { label, span } => {
229                write!(f, "{}: undefined label '{}'", span, label)
230            }
231            AsmError::DuplicateLabel {
232                label,
233                span,
234                first_span,
235            } => {
236                write!(
237                    f,
238                    "{}: duplicate label '{}' (first defined at {})",
239                    span, label, first_span
240                )
241            }
242            AsmError::BranchOutOfRange {
243                label,
244                disp,
245                max,
246                span,
247            } => {
248                write!(
249                    f,
250                    "{}: branch target '{}' out of range (displacement={}, max=±{})",
251                    span, label, disp, max
252                )
253            }
254            AsmError::MisalignedBranchTarget {
255                label,
256                disp,
257                alignment,
258                span,
259            } => {
260                write!(
261                    f,
262                    "{}: branch target '{}' is not {}-byte aligned (displacement={})",
263                    span, label, alignment, disp
264                )
265            }
266            AsmError::Syntax { msg, span } => {
267                write!(f, "{}: {}", span, msg)
268            }
269            AsmError::RelaxationLimit { max } => {
270                write!(
271                    f,
272                    "assembly exceeded maximum of {} relaxation passes (possible oscillation)",
273                    max
274                )
275            }
276            AsmError::ResourceLimitExceeded { resource, limit } => {
277                write!(
278                    f,
279                    "resource limit exceeded: {} (limit: {})",
280                    resource, limit
281                )
282            }
283            AsmError::Multiple { errors } => {
284                for (i, e) in errors.iter().enumerate() {
285                    if i > 0 {
286                        writeln!(f)?;
287                    }
288                    write!(f, "{}", e)?;
289                }
290                Ok(())
291            }
292        }
293    }
294}
295
296#[cfg(feature = "std")]
297impl std::error::Error for AsmError {}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302
303    #[test]
304    fn span_display() {
305        let span = Span::new(3, 12, 45, 5);
306        assert_eq!(format!("{}", span), "3:12");
307    }
308
309    #[test]
310    fn span_dummy() {
311        let span = Span::dummy();
312        assert_eq!(span.line, 0);
313        assert_eq!(span.col, 0);
314    }
315
316    #[test]
317    fn error_unknown_mnemonic_display() {
318        let err = AsmError::UnknownMnemonic {
319            mnemonic: "foobar".into(),
320            arch: ArchName::X86_64,
321            span: Span::new(3, 12, 0, 6),
322        };
323        assert_eq!(
324            format!("{}", err),
325            "3:12: unknown mnemonic 'foobar' for x86_64"
326        );
327    }
328
329    #[test]
330    fn error_syntax_display() {
331        let err = AsmError::Syntax {
332            msg: "unexpected token '!'".into(),
333            span: Span::new(1, 5, 4, 1),
334        };
335        assert_eq!(format!("{}", err), "1:5: unexpected token '!'");
336    }
337
338    #[test]
339    fn error_undefined_label_display() {
340        let err = AsmError::UndefinedLabel {
341            label: "my_label".into(),
342            span: Span::new(10, 1, 100, 8),
343        };
344        assert_eq!(format!("{}", err), "10:1: undefined label 'my_label'");
345    }
346
347    #[test]
348    fn error_immediate_overflow_display() {
349        let err = AsmError::ImmediateOverflow {
350            value: 256,
351            min: -128,
352            max: 127,
353            span: Span::new(5, 10, 50, 3),
354        };
355        assert_eq!(
356            format!("{}", err),
357            "5:10: immediate value 256 out of range [-128..127]"
358        );
359    }
360
361    #[test]
362    fn error_duplicate_label_display() {
363        let err = AsmError::DuplicateLabel {
364            label: "loop".into(),
365            span: Span::new(20, 1, 200, 4),
366            first_span: Span::new(5, 1, 50, 4),
367        };
368        assert_eq!(
369            format!("{}", err),
370            "20:1: duplicate label 'loop' (first defined at 5:1)"
371        );
372    }
373
374    #[test]
375    fn error_relaxation_limit_display() {
376        let err = AsmError::RelaxationLimit { max: 20 };
377        assert_eq!(
378            format!("{}", err),
379            "assembly exceeded maximum of 20 relaxation passes (possible oscillation)"
380        );
381    }
382
383    #[test]
384    fn error_branch_out_of_range_display() {
385        let err = AsmError::BranchOutOfRange {
386            label: "far_away".into(),
387            disp: 500000,
388            max: 127,
389            span: Span::new(1, 1, 0, 10),
390        };
391        assert_eq!(
392            format!("{}", err),
393            "1:1: branch target 'far_away' out of range (displacement=500000, max=±127)"
394        );
395    }
396
397    #[test]
398    fn error_multiple_display() {
399        let err = AsmError::Multiple {
400            errors: vec![
401                AsmError::Syntax {
402                    msg: "err1".into(),
403                    span: Span::new(1, 1, 0, 1),
404                },
405                AsmError::Syntax {
406                    msg: "err2".into(),
407                    span: Span::new(2, 1, 5, 1),
408                },
409            ],
410        };
411        let s = format!("{}", err);
412        assert!(s.contains("err1"));
413        assert!(s.contains("err2"));
414    }
415
416    #[test]
417    fn error_resource_limit_exceeded_display() {
418        let err = AsmError::ResourceLimitExceeded {
419            resource: "statements".into(),
420            limit: 1_000_000,
421        };
422        assert_eq!(
423            format!("{}", err),
424            "resource limit exceeded: statements (limit: 1000000)"
425        );
426    }
427}