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}