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    /// Syntax error during lexing or parsing.
157    Syntax {
158        /// The syntax error message.
159        msg: String,
160        /// Source location of the syntax error.
161        span: Span,
162    },
163
164    /// Branch relaxation did not converge within the allowed number of passes.
165    RelaxationLimit {
166        /// Maximum number of relaxation passes allowed.
167        max: usize,
168    },
169
170    /// A configurable resource limit was exceeded (defense against DoS).
171    ResourceLimitExceeded {
172        /// Human-readable name of the resource (e.g. "statements", "labels").
173        resource: String,
174        /// The configured limit that was exceeded.
175        limit: usize,
176    },
177
178    /// Multiple errors collected during assembly.
179    Multiple {
180        /// The collected assembly errors.
181        errors: Vec<AsmError>,
182    },
183}
184
185impl fmt::Display for AsmError {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        match self {
188            AsmError::UnknownMnemonic {
189                mnemonic,
190                arch,
191                span,
192            } => {
193                write!(f, "{}: unknown mnemonic '{}' for {}", span, mnemonic, arch)
194            }
195            AsmError::InvalidOperands { detail, span } => {
196                write!(f, "{}: invalid operand combination: {}", span, detail)
197            }
198            AsmError::ImmediateOverflow {
199                value,
200                min,
201                max,
202                span,
203            } => {
204                write!(
205                    f,
206                    "{}: immediate value {} out of range [{}..{}]",
207                    span, value, min, max
208                )
209            }
210            AsmError::UndefinedLabel { label, span } => {
211                write!(f, "{}: undefined label '{}'", span, label)
212            }
213            AsmError::DuplicateLabel {
214                label,
215                span,
216                first_span,
217            } => {
218                write!(
219                    f,
220                    "{}: duplicate label '{}' (first defined at {})",
221                    span, label, first_span
222                )
223            }
224            AsmError::BranchOutOfRange {
225                label,
226                disp,
227                max,
228                span,
229            } => {
230                write!(
231                    f,
232                    "{}: branch target '{}' out of range (displacement={}, max=±{})",
233                    span, label, disp, max
234                )
235            }
236            AsmError::Syntax { msg, span } => {
237                write!(f, "{}: {}", span, msg)
238            }
239            AsmError::RelaxationLimit { max } => {
240                write!(
241                    f,
242                    "assembly exceeded maximum of {} relaxation passes (possible oscillation)",
243                    max
244                )
245            }
246            AsmError::ResourceLimitExceeded { resource, limit } => {
247                write!(
248                    f,
249                    "resource limit exceeded: {} (limit: {})",
250                    resource, limit
251                )
252            }
253            AsmError::Multiple { errors } => {
254                for (i, e) in errors.iter().enumerate() {
255                    if i > 0 {
256                        writeln!(f)?;
257                    }
258                    write!(f, "{}", e)?;
259                }
260                Ok(())
261            }
262        }
263    }
264}
265
266#[cfg(feature = "std")]
267impl std::error::Error for AsmError {}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn span_display() {
275        let span = Span::new(3, 12, 45, 5);
276        assert_eq!(format!("{}", span), "3:12");
277    }
278
279    #[test]
280    fn span_dummy() {
281        let span = Span::dummy();
282        assert_eq!(span.line, 0);
283        assert_eq!(span.col, 0);
284    }
285
286    #[test]
287    fn error_unknown_mnemonic_display() {
288        let err = AsmError::UnknownMnemonic {
289            mnemonic: "foobar".into(),
290            arch: ArchName::X86_64,
291            span: Span::new(3, 12, 0, 6),
292        };
293        assert_eq!(
294            format!("{}", err),
295            "3:12: unknown mnemonic 'foobar' for x86_64"
296        );
297    }
298
299    #[test]
300    fn error_syntax_display() {
301        let err = AsmError::Syntax {
302            msg: "unexpected token '!'".into(),
303            span: Span::new(1, 5, 4, 1),
304        };
305        assert_eq!(format!("{}", err), "1:5: unexpected token '!'");
306    }
307
308    #[test]
309    fn error_undefined_label_display() {
310        let err = AsmError::UndefinedLabel {
311            label: "my_label".into(),
312            span: Span::new(10, 1, 100, 8),
313        };
314        assert_eq!(format!("{}", err), "10:1: undefined label 'my_label'");
315    }
316
317    #[test]
318    fn error_immediate_overflow_display() {
319        let err = AsmError::ImmediateOverflow {
320            value: 256,
321            min: -128,
322            max: 127,
323            span: Span::new(5, 10, 50, 3),
324        };
325        assert_eq!(
326            format!("{}", err),
327            "5:10: immediate value 256 out of range [-128..127]"
328        );
329    }
330
331    #[test]
332    fn error_duplicate_label_display() {
333        let err = AsmError::DuplicateLabel {
334            label: "loop".into(),
335            span: Span::new(20, 1, 200, 4),
336            first_span: Span::new(5, 1, 50, 4),
337        };
338        assert_eq!(
339            format!("{}", err),
340            "20:1: duplicate label 'loop' (first defined at 5:1)"
341        );
342    }
343
344    #[test]
345    fn error_relaxation_limit_display() {
346        let err = AsmError::RelaxationLimit { max: 20 };
347        assert_eq!(
348            format!("{}", err),
349            "assembly exceeded maximum of 20 relaxation passes (possible oscillation)"
350        );
351    }
352
353    #[test]
354    fn error_branch_out_of_range_display() {
355        let err = AsmError::BranchOutOfRange {
356            label: "far_away".into(),
357            disp: 500000,
358            max: 127,
359            span: Span::new(1, 1, 0, 10),
360        };
361        assert_eq!(
362            format!("{}", err),
363            "1:1: branch target 'far_away' out of range (displacement=500000, max=±127)"
364        );
365    }
366
367    #[test]
368    fn error_multiple_display() {
369        let err = AsmError::Multiple {
370            errors: vec![
371                AsmError::Syntax {
372                    msg: "err1".into(),
373                    span: Span::new(1, 1, 0, 1),
374                },
375                AsmError::Syntax {
376                    msg: "err2".into(),
377                    span: Span::new(2, 1, 5, 1),
378                },
379            ],
380        };
381        let s = format!("{}", err);
382        assert!(s.contains("err1"));
383        assert!(s.contains("err2"));
384    }
385
386    #[test]
387    fn error_resource_limit_exceeded_display() {
388        let err = AsmError::ResourceLimitExceeded {
389            resource: "statements".into(),
390            limit: 1_000_000,
391        };
392        assert_eq!(
393            format!("{}", err),
394            "resource limit exceeded: statements (limit: 1000000)"
395        );
396    }
397}