Coverage Report

Created: 2026-02-14 06:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/addr2line-0.24.2/src/line.rs
Line
Count
Source
1
use alloc::boxed::Box;
2
use alloc::string::{String, ToString};
3
use alloc::vec::Vec;
4
use core::cmp::Ordering;
5
use core::mem;
6
use core::num::NonZeroU64;
7
8
use crate::lazy::LazyResult;
9
use crate::{Error, Location};
10
11
pub(crate) struct LazyLines(LazyResult<Lines>);
12
13
impl LazyLines {
14
0
    pub(crate) fn new() -> Self {
15
0
        LazyLines(LazyResult::new())
16
0
    }
17
18
0
    pub(crate) fn borrow<R: gimli::Reader>(
19
0
        &self,
20
0
        dw_unit: gimli::UnitRef<R>,
21
0
        ilnp: &gimli::IncompleteLineProgram<R, R::Offset>,
22
0
    ) -> Result<&Lines, Error> {
23
0
        self.0
24
0
            .borrow_with(|| Lines::parse(dw_unit, ilnp.clone()))
Unexecuted instantiation: <addr2line::line::LazyLines>::borrow::<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>::{closure#0}
Unexecuted instantiation: <addr2line::line::LazyLines>::borrow::<_>::{closure#0}
25
0
            .as_ref()
26
0
            .map_err(Error::clone)
27
0
    }
Unexecuted instantiation: <addr2line::line::LazyLines>::borrow::<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>
Unexecuted instantiation: <addr2line::line::LazyLines>::borrow::<_>
28
}
29
30
struct LineSequence {
31
    start: u64,
32
    end: u64,
33
    rows: Box<[LineRow]>,
34
}
35
36
struct LineRow {
37
    address: u64,
38
    file_index: u64,
39
    line: u32,
40
    column: u32,
41
}
42
43
pub(crate) struct Lines {
44
    files: Box<[String]>,
45
    sequences: Box<[LineSequence]>,
46
}
47
48
impl Lines {
49
0
    fn parse<R: gimli::Reader>(
50
0
        dw_unit: gimli::UnitRef<R>,
51
0
        ilnp: gimli::IncompleteLineProgram<R, R::Offset>,
52
0
    ) -> Result<Self, Error> {
53
0
        let mut sequences = Vec::new();
54
0
        let mut sequence_rows = Vec::<LineRow>::new();
55
0
        let mut rows = ilnp.rows();
56
0
        while let Some((_, row)) = rows.next_row()? {
57
0
            if row.end_sequence() {
58
0
                if let Some(start) = sequence_rows.first().map(|x| x.address) {
59
0
                    let end = row.address();
60
0
                    let mut rows = Vec::new();
61
0
                    mem::swap(&mut rows, &mut sequence_rows);
62
0
                    sequences.push(LineSequence {
63
0
                        start,
64
0
                        end,
65
0
                        rows: rows.into_boxed_slice(),
66
0
                    });
67
0
                }
68
0
                continue;
69
0
            }
70
71
0
            let address = row.address();
72
0
            let file_index = row.file_index();
73
            // Convert line and column to u32 to save a little memory.
74
            // We'll handle the special case of line 0 later,
75
            // and return left edge as column 0 in the public API.
76
0
            let line = row.line().map(NonZeroU64::get).unwrap_or(0) as u32;
77
0
            let column = match row.column() {
78
0
                gimli::ColumnType::LeftEdge => 0,
79
0
                gimli::ColumnType::Column(x) => x.get() as u32,
80
            };
81
82
0
            if let Some(last_row) = sequence_rows.last_mut() {
83
0
                if last_row.address == address {
84
0
                    last_row.file_index = file_index;
85
0
                    last_row.line = line;
86
0
                    last_row.column = column;
87
0
                    continue;
88
0
                }
89
0
            }
90
91
0
            sequence_rows.push(LineRow {
92
0
                address,
93
0
                file_index,
94
0
                line,
95
0
                column,
96
0
            });
97
        }
98
0
        sequences.sort_by_key(|x| x.start);
99
100
0
        let mut files = Vec::new();
101
0
        let header = rows.header();
102
0
        match header.file(0) {
103
0
            Some(file) => files.push(render_file(dw_unit, file, header)?),
104
0
            None => files.push(String::from("")), // DWARF version <= 4 may not have 0th index
105
        }
106
0
        let mut index = 1;
107
0
        while let Some(file) = header.file(index) {
108
0
            files.push(render_file(dw_unit, file, header)?);
109
0
            index += 1;
110
        }
111
112
0
        Ok(Self {
113
0
            files: files.into_boxed_slice(),
114
0
            sequences: sequences.into_boxed_slice(),
115
0
        })
116
0
    }
Unexecuted instantiation: <addr2line::line::Lines>::parse::<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>
Unexecuted instantiation: <addr2line::line::Lines>::parse::<_>
117
118
0
    pub(crate) fn file(&self, index: u64) -> Option<&str> {
119
0
        self.files.get(index as usize).map(String::as_str)
120
0
    }
121
122
0
    pub(crate) fn ranges(&self) -> impl Iterator<Item = gimli::Range> + '_ {
123
0
        self.sequences.iter().map(|sequence| gimli::Range {
124
0
            begin: sequence.start,
125
0
            end: sequence.end,
126
0
        })
Unexecuted instantiation: <addr2line::line::Lines>::ranges::{closure#0}
Unexecuted instantiation: <addr2line::line::Lines>::ranges::{closure#0}
127
0
    }
128
129
0
    fn row_location(&self, row: &LineRow) -> Location<'_> {
130
0
        let file = self.files.get(row.file_index as usize).map(String::as_str);
131
        Location {
132
0
            file,
133
0
            line: if row.line != 0 { Some(row.line) } else { None },
134
            // If row.line is specified then row.column always has meaning.
135
0
            column: if row.line != 0 {
136
0
                Some(row.column)
137
            } else {
138
0
                None
139
            },
140
        }
141
0
    }
142
143
0
    pub(crate) fn find_location(&self, probe: u64) -> Result<Option<Location<'_>>, Error> {
144
0
        let seq_idx = self.sequences.binary_search_by(|sequence| {
145
0
            if probe < sequence.start {
146
0
                Ordering::Greater
147
0
            } else if probe >= sequence.end {
148
0
                Ordering::Less
149
            } else {
150
0
                Ordering::Equal
151
            }
152
0
        });
153
0
        let seq_idx = match seq_idx {
154
0
            Ok(x) => x,
155
0
            Err(_) => return Ok(None),
156
        };
157
0
        let sequence = &self.sequences[seq_idx];
158
159
0
        let idx = sequence
160
0
            .rows
161
0
            .binary_search_by(|row| row.address.cmp(&probe));
162
0
        let idx = match idx {
163
0
            Ok(x) => x,
164
0
            Err(0) => return Ok(None),
165
0
            Err(x) => x - 1,
166
        };
167
0
        Ok(Some(self.row_location(&sequence.rows[idx])))
168
0
    }
169
170
0
    pub(crate) fn find_location_range(
171
0
        &self,
172
0
        probe_low: u64,
173
0
        probe_high: u64,
174
0
    ) -> Result<LineLocationRangeIter<'_>, Error> {
175
        // Find index for probe_low.
176
0
        let seq_idx = self.sequences.binary_search_by(|sequence| {
177
0
            if probe_low < sequence.start {
178
0
                Ordering::Greater
179
0
            } else if probe_low >= sequence.end {
180
0
                Ordering::Less
181
            } else {
182
0
                Ordering::Equal
183
            }
184
0
        });
185
0
        let seq_idx = match seq_idx {
186
0
            Ok(x) => x,
187
0
            Err(x) => x, // probe below sequence, but range could overlap
188
        };
189
190
0
        let row_idx = if let Some(seq) = self.sequences.get(seq_idx) {
191
0
            let idx = seq.rows.binary_search_by(|row| row.address.cmp(&probe_low));
192
0
            match idx {
193
0
                Ok(x) => x,
194
0
                Err(0) => 0, // probe below sequence, but range could overlap
195
0
                Err(x) => x - 1,
196
            }
197
        } else {
198
0
            0
199
        };
200
201
0
        Ok(LineLocationRangeIter {
202
0
            lines: self,
203
0
            seq_idx,
204
0
            row_idx,
205
0
            probe_high,
206
0
        })
207
0
    }
208
}
209
210
pub(crate) struct LineLocationRangeIter<'ctx> {
211
    lines: &'ctx Lines,
212
    seq_idx: usize,
213
    row_idx: usize,
214
    probe_high: u64,
215
}
216
217
impl<'ctx> Iterator for LineLocationRangeIter<'ctx> {
218
    type Item = (u64, u64, Location<'ctx>);
219
220
0
    fn next(&mut self) -> Option<(u64, u64, Location<'ctx>)> {
221
0
        while let Some(seq) = self.lines.sequences.get(self.seq_idx) {
222
0
            if seq.start >= self.probe_high {
223
0
                break;
224
0
            }
225
226
0
            match seq.rows.get(self.row_idx) {
227
0
                Some(row) => {
228
0
                    if row.address >= self.probe_high {
229
0
                        break;
230
0
                    }
231
232
0
                    let nextaddr = seq
233
0
                        .rows
234
0
                        .get(self.row_idx + 1)
235
0
                        .map(|row| row.address)
236
0
                        .unwrap_or(seq.end);
237
238
0
                    let item = (
239
0
                        row.address,
240
0
                        nextaddr - row.address,
241
0
                        self.lines.row_location(row),
242
0
                    );
243
0
                    self.row_idx += 1;
244
245
0
                    return Some(item);
246
                }
247
0
                None => {
248
0
                    self.seq_idx += 1;
249
0
                    self.row_idx = 0;
250
0
                }
251
            }
252
        }
253
0
        None
254
0
    }
255
}
256
257
0
fn render_file<R: gimli::Reader>(
258
0
    dw_unit: gimli::UnitRef<R>,
259
0
    file: &gimli::FileEntry<R, R::Offset>,
260
0
    header: &gimli::LineProgramHeader<R, R::Offset>,
261
0
) -> Result<String, gimli::Error> {
262
0
    let mut path = if let Some(ref comp_dir) = dw_unit.comp_dir {
263
0
        comp_dir.to_string_lossy()?.into_owned()
264
    } else {
265
0
        String::new()
266
    };
267
268
    // The directory index 0 is defined to correspond to the compilation unit directory.
269
0
    if file.directory_index() != 0 {
270
0
        if let Some(directory) = file.directory(header) {
271
0
            path_push(
272
0
                &mut path,
273
0
                dw_unit.attr_string(directory)?.to_string_lossy()?.as_ref(),
274
            );
275
0
        }
276
0
    }
277
278
0
    path_push(
279
0
        &mut path,
280
0
        dw_unit
281
0
            .attr_string(file.path_name())?
282
0
            .to_string_lossy()?
283
0
            .as_ref(),
284
    );
285
286
0
    Ok(path)
287
0
}
Unexecuted instantiation: addr2line::line::render_file::<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>
Unexecuted instantiation: addr2line::line::render_file::<_>
288
289
0
fn path_push(path: &mut String, p: &str) {
290
0
    if has_unix_root(p) || has_windows_root(p) {
291
0
        *path = p.to_string();
292
0
    } else {
293
0
        let dir_separator = if has_windows_root(path.as_str()) {
294
0
            '\\'
295
        } else {
296
0
            '/'
297
        };
298
299
0
        if !path.is_empty() && !path.ends_with(dir_separator) {
300
0
            path.push(dir_separator);
301
0
        }
302
0
        *path += p;
303
    }
304
0
}
305
306
/// Check if the path in the given string has a unix style root
307
0
fn has_unix_root(p: &str) -> bool {
308
0
    p.starts_with('/')
309
0
}
310
311
/// Check if the path in the given string has a windows style root
312
0
fn has_windows_root(p: &str) -> bool {
313
0
    p.starts_with('\\') || p.get(1..3) == Some(":\\")
314
0
}