• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

xd009642 / tarpaulin / #483

10 May 2024 10:16PM UTC coverage: 73.61% (-0.6%) from 74.182%
#483

push

xd009642
Release 0.30.0

89 of 102 new or added lines in 7 files covered. (87.25%)

7 existing lines in 6 files now uncovered.

2569 of 3490 relevant lines covered (73.61%)

143183.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.4
/src/test_loader.rs
1
use crate::config::{types::TraceEngine, Config};
2
use crate::path_utils::{fix_unc_path, is_coverable_file_path};
3
use crate::source_analysis::*;
4
use crate::traces::*;
5
use gimli::*;
6
use object::{read::ObjectSection, Object};
7
use rustc_demangle::demangle;
8
use std::collections::{HashMap, HashSet};
9
use std::fs::File;
10
use std::io;
11
use std::path::{Path, PathBuf};
12
use tracing::{debug, error, trace, warn};
13

14
/// Describes a function as `low_pc`, `high_pc` and bool representing `is_test`.
15
type FuncDesc = (u64, u64, FunctionType, Option<String>);
16

17
#[derive(Debug, Clone, Copy, PartialEq)]
18
enum FunctionType {
19
    Generated,
20
    Test,
21
    Standard,
22
}
23

24
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
25
pub enum LineType {
26
    /// Generated test main. Shouldn't be traced.
27
    TestMain,
28
    /// Entry of function known to be a test
29
    TestEntry(u64),
30
    /// Entry of function. May or may not be test
31
    FunctionEntry(u64),
32
    /// Standard statement
33
    Statement,
34
    /// Condition
35
    Condition,
36
    /// Unknown type
37
    Unknown,
38
    /// Unused meta-code
39
    UnusedGeneric,
40
}
41

42
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
43
struct SourceLocation {
44
    pub path: PathBuf,
45
    pub line: u64,
46
}
47

48
impl From<(PathBuf, usize)> for SourceLocation {
49
    fn from(other: (PathBuf, usize)) -> Self {
1,130✔
50
        Self {
51
            path: other.0,
1,130✔
52
            line: other.1 as u64,
1,130✔
53
        }
54
    }
55
}
56

57
#[derive(Debug, Clone)]
58
pub struct TracerData {
59
    /// Currently used to find generated __test::main and remove from coverage,
60
    /// may have uses in future for finding conditions etc
61
    pub trace_type: LineType,
62
    /// Start address of the line
63
    pub address: Option<u64>,
64
    /// Length of the instruction
65
    pub length: u64,
66
    /// Function name
67
    pub fn_name: Option<String>,
68
}
69

70
fn generate_func_desc<R, Offset>(
3,597,460✔
71
    die: &DebuggingInformationEntry<R, Offset>,
72
    debug_str: &DebugStr<R>,
73
) -> Result<FuncDesc>
74
where
75
    R: Reader<Offset = Offset>,
76
    Offset: ReaderOffset,
77
{
78
    let mut func_type = FunctionType::Standard;
3,597,460✔
79
    let low = die.attr_value(DW_AT_low_pc)?;
7,194,920✔
80
    let high = die.attr_value(DW_AT_high_pc)?;
3,597,460✔
81
    let linkage = die.attr_value(DW_AT_linkage_name)?;
3,597,460✔
82
    let fn_name = die.attr_value(DW_AT_name)?;
3,597,460✔
83

84
    let fn_name: Option<String> = match fn_name {
6,985,426✔
85
        Some(AttributeValue::DebugStrRef(offset)) => debug_str
3,387,966✔
86
            .get_str(offset)
3,387,966✔
87
            .and_then(|r| r.to_string().map(|s| s.to_string()))
13,551,864✔
88
            .ok()
89
            .map(|r| demangle(r.as_ref()).to_string()),
3,387,966✔
90
        _ => None,
209,494✔
91
    };
92

93
    // Low is a program counter address so stored in an Addr
94
    let low = match low {
4,143,974✔
95
        Some(AttributeValue::Addr(x)) => x,
546,514✔
96
        _ => 0u64,
3,050,946✔
97
    };
98
    // High is an offset from the base pc, therefore is u64 data.
99
    let high = match high {
4,143,974✔
100
        Some(AttributeValue::Udata(x)) => x,
546,514✔
101
        _ => 0u64,
3,050,946✔
102
    };
103
    if let Some(AttributeValue::DebugStrRef(offset)) = linkage {
3,336,080✔
104
        let name = debug_str
3,336,080✔
105
            .get_str(offset)
3,336,080✔
106
            .and_then(|r| r.to_string().map(|s| s.to_string()))
16,680,400✔
107
            .unwrap_or_else(|_| "".into());
6,672,160✔
108
        let name = demangle(name.as_ref()).to_string();
3,336,080✔
109
        // Simplest test is whether it's in tests namespace.
110
        // Rust guidelines recommend all tests are in a tests module.
111
        func_type = if name.contains("tests::") {
3,336,080✔
112
            FunctionType::Test
8,772✔
113
        } else if name.contains("__test::main") {
3,327,308✔
114
            FunctionType::Generated
×
115
        } else {
116
            FunctionType::Standard
3,327,308✔
117
        };
118
    }
119
    Ok((low, high, func_type, fn_name))
3,597,460✔
120
}
121

122
/// Finds all function entry points and returns a vector
123
/// This will identify definite tests, but may be prone to false negatives.
124
fn get_entry_points<R, Offset>(
4,808✔
125
    debug_info: &UnitHeader<R, Offset>,
126
    debug_abbrev: &Abbreviations,
127
    debug_str: &DebugStr<R>,
128
) -> Vec<FuncDesc>
129
where
130
    R: Reader<Offset = Offset>,
131
    Offset: ReaderOffset,
132
{
133
    let mut result: Vec<FuncDesc> = Vec::new();
4,808✔
134
    let mut cursor = debug_info.entries(debug_abbrev);
4,808✔
135
    // skip compilation unit root.
136
    let _ = cursor.next_entry();
4,808✔
137
    while let Ok(Some((_, node))) = cursor.next_dfs() {
37,857,744✔
138
        // Function DIE
139
        if node.tag() == DW_TAG_subprogram {
18,926,468✔
140
            if let Ok(fd) = generate_func_desc(node, debug_str) {
10,792,380✔
141
                result.push(fd);
3,597,460✔
142
            }
143
        }
144
    }
145
    result
4,808✔
146
}
147

148
fn get_addresses_from_program<R, Offset>(
4,808✔
149
    prog: IncompleteLineProgram<R>,
150
    debug_strs: &DebugStr<R>,
151
    entries: &[(u64, LineType, &Option<String>)],
152
    config: &Config,
153
    result: &mut HashMap<SourceLocation, Vec<TracerData>>,
154
) -> Result<()>
155
where
156
    R: Reader<Offset = Offset>,
157
    Offset: ReaderOffset,
158
{
159
    let project = config.root();
4,808✔
160
    let get_string = |x: R| x.to_string().map(|y| y.to_string()).ok();
69,022,268✔
161
    let (cprog, seq) = prog.sequences()?;
9,616✔
162
    for s in seq {
1,097,836✔
163
        let mut sm = cprog.resume_from(&s);
×
164
        while let Ok(Some((header, &ln_row))) = sm.next_row() {
32,710,320✔
165
            if ln_row.end_sequence() {
16,355,160✔
166
                break;
546,514✔
167
            }
168
            // If this row isn't useful move on
169
            if !ln_row.is_stmt() || ln_row.line().is_none() {
27,312,150✔
170
                continue;
4,305,490✔
171
            }
172
            if let Some(file) = ln_row.file(header) {
11,503,156✔
173
                let mut path = project.clone();
×
174
                if let Some(dir) = file.directory(header) {
11,502,664✔
175
                    if let Some(temp) = dir.string_value(debug_strs).and_then(get_string) {
11,502,664✔
176
                        path.push(temp);
×
177
                    }
178
                }
179
                if let Ok(p) = path.canonicalize() {
11,943,410✔
180
                    path = fix_unc_path(&p);
×
181
                }
182
                let file = file.path_name();
11,503,156✔
183
                let line = ln_row.line().unwrap();
11,503,156✔
184
                if let Some(file) = file.string_value(debug_strs).and_then(get_string) {
11,503,156✔
185
                    path.push(file);
×
186
                    if !path.is_file() {
×
187
                        // Not really a source file!
188
                        continue;
11,272,838✔
189
                    }
190
                    if is_coverable_file_path(&path, &project, &config.target_dir()) {
230,318✔
191
                        let address = ln_row.address();
36,086✔
192
                        let (desc, fn_name) = entries
36,086✔
193
                            .iter()
194
                            .filter(|&&(addr, _, _)| addr == address)
20,530,564✔
195
                            .map(|&(_, t, fn_name)| (t, fn_name.clone()))
3,776✔
196
                            .next()
197
                            .unwrap_or((LineType::Unknown, None));
×
198
                        let loc = SourceLocation {
199
                            path,
200
                            line: line.into(),
×
201
                        };
202
                        if desc != LineType::TestMain {
36,086✔
203
                            let trace = TracerData {
204
                                address: Some(address),
36,086✔
205
                                trace_type: desc,
206
                                length: 1,
207
                                fn_name,
208
                            };
209
                            let tracerdata = result.entry(loc).or_default();
36,086✔
210
                            tracerdata.push(trace);
36,086✔
211
                        }
212
                    }
213
                }
214
            }
215
        }
216
    }
217
    Ok(())
4,808✔
218
}
219

220
fn get_line_addresses<'data>(
188✔
221
    endian: RunTimeEndian,
222
    obj: &'data impl object::read::Object<'data>,
223
    analysis: &HashMap<PathBuf, LineAnalysis>,
224
    config: &Config,
225
) -> Result<TraceMap> {
226
    let project = config.root();
188✔
227
    let io_err = |e| {
188✔
228
        error!("IO error parsing section: {e}");
×
229
        Error::Io
×
230
    };
231
    trace!("Reading object sections");
376✔
232
    let mut result = TraceMap::new();
188✔
233
    trace!("Reading .debug_info");
376✔
234
    let debug_info = obj.section_by_name(".debug_info").ok_or(Error::Io)?;
376✔
235
    let debug_info = DebugInfo::new(debug_info.data().map_err(io_err)?, endian);
188✔
236
    trace!("Reading .debug_abbrev");
188✔
237
    let debug_abbrev = obj.section_by_name(".debug_abbrev").ok_or(Error::Io)?;
376✔
238
    let debug_abbrev = DebugAbbrev::new(debug_abbrev.data().map_err(io_err)?, endian);
188✔
239
    trace!("Reading .debug_str");
188✔
240
    let debug_strings = obj.section_by_name(".debug_str").ok_or(Error::Io)?;
376✔
241
    let debug_strings = DebugStr::new(debug_strings.data().map_err(io_err)?, endian);
188✔
242
    trace!("Reading .debug_line");
188✔
243
    let debug_line = obj.section_by_name(".debug_line").ok_or(Error::Io)?;
376✔
244
    let debug_line = DebugLine::new(debug_line.data().map_err(io_err)?, endian);
188✔
245

246
    trace!("Reading .text");
188✔
247
    let base_addr = obj.section_by_name(".text").ok_or(Error::Io)?;
376✔
248

249
    trace!("Reading DebugInfo units");
188✔
250
    let mut iter = debug_info.units();
188✔
251
    while let Ok(Some(cu)) = iter.next() {
9,804✔
252
        let addr_size = cu.address_size();
4,808✔
253
        let abbr = match cu.abbreviations(&debug_abbrev) {
9,616✔
254
            Ok(a) => a,
×
255
            _ => continue,
×
256
        };
257
        let entry_points = get_entry_points(&cu, &abbr, &debug_strings);
×
258
        let entries = entry_points
×
259
            .iter()
260
            .map(|(a, b, c, fn_name)| match c {
3,597,460✔
261
                FunctionType::Test => (*a, LineType::TestEntry(*b), fn_name),
8,772✔
262
                FunctionType::Standard => (*a, LineType::FunctionEntry(*b), fn_name),
3,588,688✔
263
                FunctionType::Generated => (*a, LineType::TestMain, fn_name),
×
264
            })
265
            .collect::<Vec<_>>();
266

267
        if let Ok(Some((_, root))) = cu.entries(&abbr).next_dfs() {
4,808✔
268
            let offset = match root.attr_value(DW_AT_stmt_list) {
9,616✔
269
                Ok(Some(AttributeValue::DebugLineRef(o))) => o,
4,808✔
270
                _ => continue,
×
271
            };
272
            let prog = debug_line.program(offset, addr_size, None, None)?; // Here?
9,616✔
273
            let mut temp_map: HashMap<SourceLocation, Vec<TracerData>> = HashMap::new();
×
274

275
            if let Err(e) =
×
276
                get_addresses_from_program(prog, &debug_strings, &entries, config, &mut temp_map)
×
277
            {
278
                debug!("Potential issue reading test addresses {}", e);
×
279
            } else {
280
                // Deduplicate addresses
281
                for v in temp_map.values_mut() {
43,216✔
282
                    v.dedup_by_key(|x| x.address);
72,172✔
283
                }
284
                let temp_map = temp_map
4,808✔
285
                    .into_iter()
286
                    .filter(|(ref k, _)| {
24,012✔
287
                        config.include_tests() || !k.path.starts_with(project.join("tests"))
28,286✔
288
                    })
289
                    .filter(|(ref k, _)| !(config.exclude_path(&k.path)))
28,708✔
290
                    .filter(|(ref k, _)| {
23,900✔
291
                        !analysis.should_ignore(k.path.as_ref(), &(k.line as usize))
19,092✔
292
                    })
293
                    .map(|(k, v)| {
5,938✔
294
                        let ret = analysis.normalise(k.path.as_ref(), k.line as usize);
1,130✔
295
                        let k_n = SourceLocation::from(ret);
1,130✔
296
                        (k_n, v)
1,130✔
297
                    })
298
                    .collect::<HashMap<SourceLocation, Vec<TracerData>>>();
299

300
                let mut tracemap = TraceMap::new();
4,808✔
301
                for (k, val) in temp_map.iter().filter(|(k, _)| k.line != 0) {
11,876✔
302
                    let rpath = config.strip_base_dir(&k.path);
1,130✔
303
                    let mut address = HashSet::new();
1,130✔
304
                    let mut fn_name = None;
1,130✔
305
                    for v in val.iter() {
3,178✔
306
                        if let Some(a) = v.address {
4,096✔
307
                            if a < base_addr.address()
×
308
                                && a >= (base_addr.address() + base_addr.size())
×
309
                            {
310
                                continue;
×
311
                            }
312
                            address.insert(a);
2,048✔
313
                            trace!(
2,048✔
314
                                "Adding trace at address 0x{:x} in {}:{}",
2,048✔
315
                                a,
2,048✔
316
                                rpath.display(),
2,048✔
317
                                k.line
×
318
                            );
319
                        }
320
                        if fn_name.is_none() && v.fn_name.is_some() {
4,064✔
321
                            fn_name = v.fn_name.clone();
326✔
322
                        }
323
                    }
324
                    if address.is_empty() {
1,130✔
325
                        trace!(
×
UNCOV
326
                            "Adding trace with no address at {}:{}",
×
327
                            rpath.display(),
×
328
                            k.line
×
329
                        );
330
                    }
331
                    tracemap.add_trace(&k.path, Trace::new(k.line, address, 1, fn_name));
1,130✔
332
                }
333
                result.merge(&tracemap);
4,808✔
334
            }
335
        }
336
    }
337

338
    for (file, line_analysis) in analysis.iter() {
572✔
339
        if config.exclude_path(file) {
384✔
340
            continue;
×
341
        }
342
        for line in &line_analysis.cover {
412✔
343
            let line = *line as u64;
×
344
            if !result.contains_location(file, line) && !line_analysis.should_ignore(line as usize)
×
345
            {
346
                let rpath = config.strip_base_dir(file);
×
347
                trace!(
×
UNCOV
348
                    "Adding trace for potentially uncoverable line in {}:{}",
×
349
                    rpath.display(),
×
350
                    line
×
351
                );
352
                result.add_trace(file, Trace::new_stub(line));
×
353
            }
354
        }
355
    }
356
    Ok(result)
188✔
357
}
358

359
#[cfg(ptrace_supported)]
360
fn open_symbols_file(test: &Path) -> io::Result<File> {
188✔
361
    File::open(test)
188✔
362
}
363

364
/*
365
#[cfg(target_os = "macos")]
366
fn open_symbols_file(test: &Path) -> io::Result<File> {
367
    let d_sym = test.with_extension("dSYM");
368
    File::open(&d_sym)
369
}
370
*/
371

372
#[cfg(not(ptrace_supported))]
373
fn open_symbols_file(_test: &Path) -> io::Result<File> {
374
    Err(io::Error::new(
375
        io::ErrorKind::Other,
376
        "Symbol files aren't read on non-ptrace systems",
377
    ))
378
}
379

380
pub fn generate_tracemap(
188✔
381
    test: &Path,
382
    analysis: &HashMap<PathBuf, LineAnalysis>,
383
    config: &Config,
384
) -> io::Result<TraceMap> {
385
    trace!("Generating traces for {}", test.display());
376✔
386
    let file = match open_symbols_file(test) {
376✔
387
        Ok(s) => object::read::ReadCache::new(s),
388
        Err(e) if config.engine() != TraceEngine::Llvm => return Err(e),
×
389
        _ => {
390
            return Ok(TraceMap::new());
×
391
        }
392
    };
393
    let obj = object::File::parse(&file)
188✔
394
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Unable to parse binary"))?;
×
395
    let endian = if obj.is_little_endian() {
188✔
396
        RunTimeEndian::Little
188✔
397
    } else {
398
        RunTimeEndian::Big
×
399
    };
400
    get_line_addresses(endian, &obj, analysis, config)
401
        .map_err(|e| {
×
402
            // They may be running with a stripped binary or doing something weird
403
            error!("Error parsing debug information from binary: {}", e);
×
404
            warn!("Stripping symbol information can prevent tarpaulin from working. If you want to do this pass `--engine=llvm`");
×
405
            io::Error::new(io::ErrorKind::InvalidData, "Error while parsing binary or DWARF info.")
×
406
        })
407
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc