Skip to main content

linker_lang/
error.rs

1//! The error a link reports when it cannot produce an image.
2
3use alloc::string::String;
4use core::fmt;
5
6use crate::object::Width;
7
8/// The reason a [`link`](crate::link) could not be completed.
9///
10/// A link can fail for a handful of distinct reasons: two objects claim the same symbol,
11/// a relocation or entry point names a symbol nobody defines, a relocation's slot does not
12/// fit the section it is in or the address does not fit the slot, or the laid-out image
13/// would run past the address space. Each case points at the object, symbol, or section
14/// involved so the producer can be fixed.
15///
16/// The set is `#[non_exhaustive]`: a future linking feature (a new relocation kind, a new
17/// layout constraint) may add a variant, so a `match` on this type must include a wildcard
18/// arm.
19///
20/// # Examples
21///
22/// ```
23/// use linker_lang::{link, LinkError, Object};
24///
25/// // Two objects both define `main`.
26/// let mut a = Object::new("a");
27/// a.section(".text", [0u8; 1]);
28/// a.define("main", ".text", 0);
29///
30/// let mut b = Object::new("b");
31/// b.section(".text", [0u8; 1]);
32/// b.define("main", ".text", 0);
33///
34/// match link(&[a, b]) {
35///     Err(LinkError::DuplicateSymbol { name }) => assert_eq!(name, "main"),
36///     other => panic!("expected a DuplicateSymbol error, got {other:?}"),
37/// }
38/// ```
39#[derive(Clone, PartialEq, Eq, Debug)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41#[non_exhaustive]
42pub enum LinkError {
43    /// A symbol of this name is defined by more than one object. A symbol's name is its
44    /// identity across the link, so two definitions are ambiguous; rename one or drop the
45    /// duplicate definition.
46    DuplicateSymbol {
47        /// The name defined more than once.
48        name: String,
49    },
50
51    /// A relocation names a `target` symbol that no object defines. The wrapped `object`
52    /// is the one that requested the relocation; define the symbol or remove the reference.
53    UndefinedSymbol {
54        /// The unresolved symbol name.
55        name: String,
56        /// The object whose relocation referenced it.
57        object: String,
58    },
59
60    /// The [entry point](crate::Linker::entry) the linker was configured with is not
61    /// defined by any object. Define the symbol, or link without an entry point.
62    UndefinedEntry {
63        /// The configured entry symbol name.
64        name: String,
65    },
66
67    /// A symbol or relocation names a section the object never created with
68    /// [`Object::section`](crate::Object::section). The name is almost certainly a typo for
69    /// a real section.
70    InvalidSection {
71        /// The object that named the section.
72        object: String,
73        /// The section name that does not exist in that object.
74        section: String,
75    },
76
77    /// A relocation's slot — `offset` plus its [`Width`] — runs past the end of the section
78    /// it patches. The producer recorded an offset the section's bytes do not cover.
79    RelocationOutOfRange {
80        /// The object that requested the relocation.
81        object: String,
82        /// The section the slot is in.
83        section: String,
84        /// The byte offset of the slot within the object's section.
85        offset: u64,
86    },
87
88    /// A relocation resolved to an address that does not fit its [`Width`] — it is negative
89    /// after the addend, or larger than the width can hold. Widen the slot or adjust the
90    /// layout so the address fits.
91    RelocationOverflow {
92        /// The symbol whose resolved address overflowed.
93        target: String,
94        /// The width the address had to fit.
95        width: Width,
96    },
97
98    /// The laid-out image would extend past the end of the 64-bit address space. The base
99    /// address plus the total section size does not fit in a `u64`.
100    LayoutOverflow,
101}
102
103impl fmt::Display for LinkError {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            LinkError::DuplicateSymbol { name } => {
107                write!(f, "symbol `{name}` is defined by more than one object")
108            }
109            LinkError::UndefinedSymbol { name, object } => {
110                write!(
111                    f,
112                    "object `{object}` references symbol `{name}`, which no object defines"
113                )
114            }
115            LinkError::UndefinedEntry { name } => {
116                write!(f, "entry point `{name}` is not defined by any object")
117            }
118            LinkError::InvalidSection { object, section } => {
119                write!(
120                    f,
121                    "object `{object}` names section `{section}`, which it never created"
122                )
123            }
124            LinkError::RelocationOutOfRange {
125                object,
126                section,
127                offset,
128            } => {
129                write!(
130                    f,
131                    "relocation at offset {offset} in section `{section}` of object \
132                     `{object}` runs past the end of the section"
133                )
134            }
135            LinkError::RelocationOverflow { target, width } => {
136                write!(
137                    f,
138                    "the resolved address of `{target}` does not fit in {} bytes",
139                    width.bytes()
140                )
141            }
142            LinkError::LayoutOverflow => {
143                f.write_str("the laid-out image would exceed the 64-bit address space")
144            }
145        }
146    }
147}
148
149impl core::error::Error for LinkError {}
150
151#[cfg(test)]
152#[allow(
153    clippy::unwrap_used,
154    reason = "tests assert on specific outcomes; a wrong outcome should fail the test loudly"
155)]
156mod tests {
157    use super::LinkError;
158    use crate::object::Width;
159    use alloc::string::ToString;
160
161    #[test]
162    fn test_each_variant_renders_a_distinct_message() {
163        let messages = [
164            LinkError::DuplicateSymbol {
165                name: "main".into(),
166            }
167            .to_string(),
168            LinkError::UndefinedSymbol {
169                name: "puts".into(),
170                object: "a".into(),
171            }
172            .to_string(),
173            LinkError::UndefinedEntry {
174                name: "_start".into(),
175            }
176            .to_string(),
177            LinkError::InvalidSection {
178                object: "a".into(),
179                section: ".txet".into(),
180            }
181            .to_string(),
182            LinkError::RelocationOutOfRange {
183                object: "a".into(),
184                section: ".data".into(),
185                offset: 12,
186            }
187            .to_string(),
188            LinkError::RelocationOverflow {
189                target: "big".into(),
190                width: Width::U32,
191            }
192            .to_string(),
193            LinkError::LayoutOverflow.to_string(),
194        ];
195        // No two variants share a message, and each names its subject.
196        for (i, a) in messages.iter().enumerate() {
197            for b in &messages[i + 1..] {
198                assert_ne!(a, b);
199            }
200        }
201        assert!(messages[0].contains("main"));
202        assert!(messages[5].contains("4 bytes"));
203    }
204
205    #[test]
206    fn test_error_is_a_std_error() {
207        fn assert_error<E: core::error::Error>(_: &E) {}
208        assert_error(&LinkError::LayoutOverflow);
209    }
210}