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

xd009642 / llvm-profparser / #240

04 Jul 2026 08:55AM UTC coverage: 73.042%. Remained the same
#240

Pull #80

xd009642
Remove all features from coverage
Pull Request #80: Add in wasm parsing

13 of 14 new or added lines in 1 file covered. (92.86%)

1119 of 1532 relevant lines covered (73.04%)

2165.99 hits per line

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

77.25
/src/coverage/coverage_mapping.rs
1
use crate::coverage::reporting::*;
2
use crate::coverage::*;
3
use crate::instrumentation_profile::types::*;
4
use crate::util::*;
5
use anyhow::{bail, Result};
6
use nom::error::Error as NomError;
7
use object::{Endian, Endianness, Object, ObjectSection, ReadCache, ReadRef, Section};
8
use std::convert::TryInto;
9
use std::error::Error;
10
use std::fmt;
11
use std::fs;
12
use std::io::BufReader;
13
use std::path::{Path, PathBuf};
14
use tracing::{debug, error, trace, warn};
15

16
/// Stores the instrumentation profile and information from the coverage mapping sections in the
17
/// object files in order to construct a coverage report. Inspired, from the LLVM implementation
18
/// with some differences/simplifications due to the fact this only hands instrumentation profiles
19
/// for coverage.
20
///
21
/// So what the LLVM one has that this one doesn't yet:
22
///
23
/// 1. DenseMap<size_t, DenseSet<size_t>> RecordProvenance
24
/// 2. std::vector<FunctionRecord> functions (this is probably taken straight from
25
/// InstrumentationProfile
26
/// 3. DenseMap<size_t, SmallVector<unsigned, 0>> FilenameHash2RecordIndices
27
/// 4. Vec<Pair<String, u64>> FuncHashMismatches
28
#[derive(Debug)]
29
pub struct CoverageMapping<'a> {
30
    profile: &'a InstrumentationProfile,
31
    object_files: &'a [PathBuf],
32
    allow_parsing_failures: bool,
33
    version: u64,
34
}
35

36
#[derive(Copy, Clone, Debug)]
37
pub enum LlvmSection {
38
    CoverageMap,
39
    ProfileNames,
40
    ProfileCounts,
41
    ProfileData,
42
    CoverageFunctions,
43
}
44

45
#[derive(Copy, Clone, Debug)]
46
pub enum SectionReadError {
47
    EmptySection(LlvmSection),
48
    MissingSection(LlvmSection),
49
    InvalidPathList,
50
}
51

52
impl fmt::Display for SectionReadError {
53
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
54
        match self {
×
55
            Self::EmptySection(s) => write!(f, "empty section: {:?}", s),
×
56
            Self::MissingSection(s) => write!(f, "missing section: {:?}", s),
×
57
            Self::InvalidPathList => write!(f, "unable to read path list"),
×
58
        }
59
    }
60
}
61

62
impl Error for SectionReadError {}
63

64
pub fn read_object_file(object: &Path, version: u64) -> Result<CoverageMappingInfo> {
3✔
65
    // I believe vnode sections added by llvm are unnecessary
66

67
    let binary_data = ReadCache::new(BufReader::new(fs::File::open(object)?));
15✔
68
    let object_file = object::File::parse(&binary_data)?;
9✔
69

70
    let prof_counts = object_file
6✔
71
        .section_by_name("__llvm_prf_cnts")
72
        .or(object_file.section_by_name(".lprfc"))
12✔
73
        .and_then(|x| parse_profile_counters(object_file.endianness(), &x).ok());
18✔
74

75
    debug!("Parsed prf_cnts: {:?}", prof_counts);
3✔
76

77
    let prof_data = object_file
6✔
78
        .section_by_name("__llvm_prf_data")
79
        .or(object_file.section_by_name(".lprfd"))
12✔
80
        .and_then(|x| parse_profile_data(object_file.endianness(), &x).ok());
18✔
81

82
    debug!("Parsed prf_data section: {:?}", prof_data);
3✔
83

84
    let cov_fun = object_file
6✔
85
        .section_by_name("__llvm_covfun")
86
        .or(object_file.section_by_name(".lcovfun"))
12✔
87
        .map(|x| parse_coverage_functions(object_file.endianness(), &x))
15✔
88
        .ok_or(SectionReadError::MissingSection(
6✔
89
            LlvmSection::CoverageFunctions,
3✔
90
        ))??;
91

92
    debug!("Parsed covfun section: {:?}", cov_fun);
3✔
93

94
    let cov_map = object_file
6✔
95
        .section_by_name("__llvm_covmap")
96
        .or(object_file.section_by_name(".lcovmap"))
12✔
97
        .map(|x| parse_coverage_mapping(object_file.endianness(), &x, version))
18✔
98
        .ok_or(SectionReadError::MissingSection(LlvmSection::CoverageMap))??;
6✔
99

100
    debug!("Parsed covmap section: {:?}", cov_map);
3✔
101

102
    Ok(CoverageMappingInfo {
3✔
103
        cov_map,
6✔
104
        cov_fun,
6✔
105
        prof_counts,
3✔
106
        prof_data,
3✔
107
    })
108
}
109

110
impl<'a> CoverageMapping<'a> {
111
    pub fn new(
3✔
112
        object_files: &'a [PathBuf],
113
        profile: &'a InstrumentationProfile,
114
        allow_parsing_failures: bool,
115
    ) -> Result<Self> {
116
        let version = match profile.version() {
6✔
117
            Some(v) => v,
6✔
118
            None => bail!("Invalid profile instrumentation, no version number provided"),
×
119
        };
120

121
        Ok(Self {
3✔
122
            profile,
6✔
123
            object_files,
6✔
124
            allow_parsing_failures,
3✔
125
            version,
3✔
126
        })
127
    }
128

129
    /// All counters of type `CounterKind::ProfileInstrumentation` can be used in function regions
130
    /// other than their own (particulary for functions which are only called from one location).
131
    /// This gathers them all to use as a base list of counters.
132
    pub(crate) fn get_simple_counters(&self, func: &FunctionRecordV3) -> FxHashMap<Counter, i64> {
18✔
133
        let mut result = FxHashMap::default();
36✔
134
        result.insert(Counter::default(), 0);
54✔
135
        let record = self.profile.find_record_by_hash(func.header.name_hash);
72✔
136
        if let Some(func_record) = record.as_ref() {
36✔
137
            result.reserve(func_record.record.counts.len());
72✔
138
            for (id, count) in func_record.record.counts.iter().enumerate() {
270✔
139
                result.insert(Counter::instrumentation(id as u64), *count as i64);
312✔
140
            }
141
        }
142
        result
18✔
143
    }
144

145
    pub fn generate_subreport<P>(&self, mut predicate: P) -> Result<CoverageReport>
3✔
146
    where
147
        P: FnMut(&[PathBuf]) -> bool,
148
    {
149
        let mut report = CoverageReport::default();
6✔
150
        //let base_region_ids = info.get_simple_counters(self.profile);
151

152
        for result in self.mapping_info_iter() {
9✔
153
            let info = result?;
6✔
154
            for func in &info.cov_fun {
21✔
155
                let base_region_ids = self.get_simple_counters(func);
72✔
156
                let paths = info.get_files_from_id(func.header.filenames_ref);
72✔
157
                if paths.is_empty() || !predicate(&paths) {
54✔
158
                    continue;
×
159
                }
160

161
                let mut region_ids = base_region_ids.clone();
54✔
162

163
                for region in func.regions.iter().filter(|x| !x.count.is_expression()) {
264✔
164
                    let count = region_ids.get(&region.count).copied().unwrap_or_default();
273✔
165
                    let result = report
117✔
166
                        .files
78✔
167
                        .entry(paths[region.file_id].clone())
156✔
168
                        .or_default();
169
                    result.insert(region.loc.clone(), count as usize);
156✔
170
                }
171

172
                let mut pending_exprs = vec![];
36✔
173

174
                for (expr_index, expr) in func.expressions.iter().enumerate().rev() {
174✔
175
                    let lhs = region_ids.get(&expr.lhs);
240✔
176
                    let rhs = region_ids.get(&expr.rhs);
240✔
177
                    match (lhs, rhs) {
120✔
178
                        (Some(lhs), Some(rhs)) => {
102✔
179
                            let count: i64 = match expr.kind {
153✔
180
                                ExprKind::Subtract => {
181
                                    trace!("Subtracting counts: {} - {}", lhs, rhs);
15✔
182
                                    // TODO these saturating ops need investigation. Potentially
183
                                    // something is wrong.
184
                                    lhs.saturating_sub(*rhs)
45✔
185
                                }
186
                                ExprKind::Add => {
187
                                    trace!("Adding counts: {} + {}", lhs, rhs);
36✔
188
                                    lhs.saturating_add(*rhs)
108✔
189
                                }
190
                            };
191

192
                            let counter = Counter {
193
                                kind: CounterType::Expression(expr.kind),
102✔
194
                                id: expr_index as _,
51✔
195
                            };
196

197
                            region_ids.insert(counter, count);
204✔
198
                            for expr_region in func.regions.iter().filter(|x| {
573✔
199
                                x.count.is_expression() && x.count.id == expr_index as u64
1,134✔
200
                            }) {
201
                                let result = report
81✔
202
                                    .files
54✔
203
                                    .entry(paths[expr_region.file_id].clone())
108✔
204
                                    .or_default();
205
                                result.insert(expr_region.loc.clone(), count as _);
135✔
206
                            }
207
                        }
208
                        _ => {
209
                            let lhs_none = lhs.is_none();
27✔
210
                            let rhs_none = rhs.is_none();
27✔
211
                            // These counters have been optimised out, so just add then in as 0
212
                            if lhs_none && expr.lhs.is_instrumentation() {
21✔
213
                                region_ids.insert(expr.lhs, 0);
×
214
                            }
215
                            if rhs_none && expr.rhs.is_instrumentation() {
15✔
216
                                region_ids.insert(expr.rhs, 0);
×
217
                            }
218
                            pending_exprs.push(Some((expr_index, expr)));
27✔
219
                            continue;
9✔
220
                        }
221
                    }
222
                }
223
                let mut index = 0;
36✔
224
                let mut cleared_expressions = 0;
36✔
225
                let mut tries_left = pending_exprs.len() + 1;
36✔
226
                while pending_exprs.len() != cleared_expressions {
54✔
227
                    assert!(tries_left > 0);
18✔
228
                    if index >= pending_exprs.len() {
18✔
229
                        index = 0;
×
230
                        tries_left -= 1;
×
231
                    }
232
                    let (expr_index, expr) = match pending_exprs[index].as_ref() {
27✔
233
                        Some((idx, expr)) => (idx, expr),
27✔
234
                        None => {
235
                            index += 1;
×
236
                            continue;
×
237
                        }
238
                    };
239
                    let lhs = region_ids.get(&expr.lhs);
36✔
240
                    let rhs = region_ids.get(&expr.rhs);
36✔
241
                    match (lhs, rhs) {
18✔
242
                        (Some(lhs), Some(rhs)) => {
18✔
243
                            let count = match expr.kind {
18✔
244
                                ExprKind::Subtract => lhs - rhs,
×
245
                                ExprKind::Add => lhs + rhs,
9✔
246
                            };
247

248
                            let counter = Counter {
249
                                kind: CounterType::Expression(expr.kind),
18✔
250
                                id: *expr_index as _,
9✔
251
                            };
252

253
                            region_ids.insert(counter, count);
36✔
254
                            for expr_region in func.regions.iter().filter(|x| {
93✔
255
                                x.count.is_expression() && x.count.id == *expr_index as u64
168✔
256
                            }) {
257
                                let result = report
27✔
258
                                    .files
18✔
259
                                    .entry(paths[expr_region.file_id].clone())
36✔
260
                                    .or_default();
261
                                result.insert(expr_region.loc.clone(), count as _);
45✔
262
                            }
263
                            pending_exprs[index] = None;
9✔
264
                            cleared_expressions += 1;
9✔
265
                        }
266
                        _ => {
267
                            index += 1;
×
268
                            continue;
×
269
                        }
270
                    }
271
                }
272
            }
273
        }
274
        Ok(report)
3✔
275
    }
276

277
    pub fn generate_report(&self) -> Result<CoverageReport> {
3✔
278
        self.generate_subreport(|_| true)
6✔
279
    }
280

281
    pub fn mapping_info_iter(&self) -> impl Iterator<Item = Result<CoverageMappingInfo>> + '_ {
3✔
282
        self.object_files
3✔
283
            .iter()
284
            .map(move |object| (object, read_object_file(object, self.version)))
15✔
285
            .skip_while(move |(object, result)| match result {
3✔
286
                Err(ref e) if !self.allow_parsing_failures => {
×
287
                    error!("{} couldn't be interpreted: {}", object.display(), e);
×
288
                    true
×
289
                }
290
                _ => false,
3✔
291
            })
292
            .map(|(_, result)| result)
3✔
293
    }
294
}
295

296
fn parse_coverage_mapping<'data, R: ReadRef<'data>>(
3✔
297
    endian: Endianness,
298
    section: &Section<'data, '_, R>,
299
    version: u64,
300
) -> Result<FxHashMap<u64, Vec<PathBuf>>, SectionReadError> {
301
    if let Ok(mut data) = section.data() {
6✔
302
        let mut result = FxHashMap::default();
6✔
303
        while !data.is_empty() {
9✔
304
            let data_len = data.len();
27✔
305
            // Read the number of affixed function records (now just 0 as not in this header)
306
            debug_assert_eq!(endian.read_i32(data[0..4].try_into().unwrap()), 0);
54✔
307
            let filename_data_len = endian.read_i32(data[4..8].try_into().unwrap());
54✔
308
            // Read the length of the affixed string that contains encoded coverage mapping data (now 0
309
            // as not in this header)
310
            debug_assert_eq!(endian.read_i32(data[8..12].try_into().unwrap()), 0);
54✔
311
            let _format_version = endian.read_i32(data[12..16].try_into().unwrap());
54✔
312

313
            let hash = md5::compute(&data[16..(filename_data_len as usize + 16)]);
27✔
314
            let hash = endian.read_u64(hash.0[..8].try_into().unwrap());
54✔
315

316
            //let bytes = &data[16..(16 + filename_data_len as usize)];
317
            let bytes = &data[16..];
18✔
318
            let (bytes, file_strings) = parse_path_list(bytes, version)
45✔
319
                .map_err(|_: nom::Err<NomError<_>>| SectionReadError::InvalidPathList)?;
9✔
320
            result.insert(hash, file_strings);
36✔
321
            let read_len = data_len - bytes.len();
27✔
322
            let padding = if !bytes.is_empty() && (read_len & 0x07) != 0 {
27✔
323
                8 - (read_len & 0x07)
9✔
324
            } else {
325
                0
×
326
            };
327
            if padding > bytes.len() {
18✔
328
                break;
3✔
329
            }
330
            data = &bytes[padding..];
6✔
331
        }
332
        Ok(result)
3✔
333
    } else {
334
        Err(SectionReadError::EmptySection(LlvmSection::CoverageMap))
×
335
    }
336
}
337

338
// Strictly speaking, parsing __llvm_covfun ought to depend on the version number
339
// observed in the __llvm_covmap headers.
340
//
341
// But in practice there haven’t been any non-additive changes to the covfun format
342
// in a long time, which is why you can get away with parsing them independently,
343
// at least for now.
344
fn parse_coverage_functions<'data, R: ReadRef<'data>>(
3✔
345
    endian: Endianness,
346
    section: &Section<'data, '_, R>,
347
) -> Result<Vec<FunctionRecordV3>, SectionReadError> {
348
    debug!("Parsing coverage functions");
3✔
349
    if let Ok(original_data) = section.data() {
6✔
350
        let mut bytes = original_data;
6✔
351
        let mut res = vec![];
6✔
352
        let section_len = bytes.len();
9✔
353
        while !bytes.is_empty() {
21✔
354
            let name_hash = endian.read_u64(bytes[0..8].try_into().unwrap());
108✔
355
            let data_len = endian.read_u32(bytes[8..12].try_into().unwrap());
108✔
356
            let fn_hash = endian.read_u64(bytes[12..20].try_into().unwrap());
108✔
357
            let filenames_ref = endian.read_u64(bytes[20..28].try_into().unwrap());
108✔
358
            let header = FunctionRecordHeader {
359
                name_hash,
360
                data_len,
361
                fn_hash,
362
                filenames_ref,
363
            };
364
            let _start_len = bytes[28..].len();
54✔
365
            bytes = &bytes[28..];
18✔
366

367
            let (data, id_len) = parse_leb128::<NomError<_>>(bytes).unwrap();
72✔
368
            bytes = data;
18✔
369
            let mut filename_indices = vec![];
36✔
370
            for _ in 0..id_len {
36✔
371
                let (data, id) = parse_leb128::<NomError<_>>(bytes).unwrap(); // Issue
90✔
372
                filename_indices.push(id);
54✔
373
                bytes = data;
18✔
374
            }
375

376
            let (data, expr_len) = parse_leb128::<NomError<_>>(bytes).unwrap();
72✔
377
            let expr_len = expr_len as usize;
36✔
378
            bytes = data;
18✔
379
            let mut exprs = vec![Expression::default(); expr_len];
72✔
380
            for i in 0..expr_len {
138✔
381
                let (data, lhs) = parse_leb128::<NomError<_>>(bytes).unwrap();
300✔
382
                let (data, rhs) = parse_leb128::<NomError<_>>(data).unwrap();
300✔
383
                let lhs = parse_counter(lhs, &mut exprs);
300✔
384
                let rhs = parse_counter(rhs, &mut exprs);
300✔
385
                exprs[i].lhs = lhs;
120✔
386
                exprs[i].rhs = rhs;
60✔
387
                bytes = data;
60✔
388
            }
389

390
            let (data, regions) =
36✔
391
                parse_mapping_regions(bytes, &filename_indices, &mut exprs).unwrap();
72✔
392

393
            res.push(FunctionRecordV3 {
54✔
394
                header,
36✔
395
                regions,
18✔
396
                expressions: exprs,
18✔
397
            });
398

399
            // Todo set couners for expansion regions - counter of expansion region is the counter
400
            // of the first region from the expanded file. This requires multiple passes to
401
            // correctly propagate across all nested regions. N.B. I haven't seen any expansion
402
            // regions in use so may not be an issue!
403

404
            bytes = data;
18✔
405
            let function_len = section_len - bytes.len(); // this should match header
54✔
406

407
            let padding = if function_len < section_len && (function_len & 0x07) != 0 {
51✔
408
                8 - (function_len & 0x07)
12✔
409
            } else {
410
                0
6✔
411
            };
412

413
            if padding > bytes.len() {
36✔
414
                break;
×
415
            }
416
            // Now apply padding, and if hash is 0 move on as it's a dummy otherwise add to result
417
            // And decide what end type will be
418
            bytes = &bytes[padding..];
18✔
419
        }
420
        Ok(res)
3✔
421
    } else {
422
        error!("Can't read data for coverage function section");
×
423
        Err(SectionReadError::EmptySection(
×
424
            LlvmSection::CoverageFunctions,
×
425
        ))
426
    }
427
}
428

429
/// This code is ported from `RawCoverageMappingReader::readMappingRegionsSubArray`
430
fn parse_mapping_regions<'a>(
18✔
431
    mut bytes: &'a [u8],
432
    file_indices: &[u64],
433
    expressions: &mut Vec<Expression>,
434
) -> IResult<&'a [u8], Vec<CounterMappingRegion>> {
435
    let mut mapping = vec![];
36✔
436
    for i in file_indices {
36✔
437
        let (data, regions_len) = parse_leb128(bytes)?;
72✔
438
        bytes = data;
18✔
439
        let mut last_line = 0;
36✔
440
        for _ in 0..regions_len {
18✔
441
            let mut mcdc_params = None;
150✔
442

443
            let mut false_count = Counter::default();
150✔
444
            let mut kind = RegionKind::Code;
150✔
445
            let (data, raw_header) = parse_leb128(bytes)?;
300✔
446
            bytes = data;
75✔
447
            let mut expanded_file_id = 0;
150✔
448
            let mut counter = parse_counter(raw_header, expressions);
300✔
449
            if counter.is_zero() {
150✔
450
                if raw_header & Counter::ENCODING_EXPANSION_REGION_BIT > 0 {
×
451
                    kind = RegionKind::Expansion;
×
452
                    expanded_file_id = raw_header >> Counter::ENCODING_TAG_AND_EXP_REGION_BITS;
×
453
                    if expanded_file_id >= file_indices.len() as u64 {
×
454
                        panic!("expanded_file_id is invalid");
×
455
                    }
456
                } else {
457
                    let shifted_counter = raw_header >> Counter::ENCODING_TAG_AND_EXP_REGION_BITS;
×
458
                    match shifted_counter.try_into() {
×
459
                        Ok(RegionKind::Code) | Ok(RegionKind::Skipped) => {}
×
460
                        Ok(RegionKind::Branch) => {
461
                            kind = RegionKind::Branch;
×
462
                            let (data, c1) = parse_leb128(bytes)?;
×
463
                            let (data, c2) = parse_leb128(data)?;
×
464

465
                            counter = parse_counter(c1, expressions);
×
466
                            false_count = parse_counter(c2, expressions);
×
467
                            bytes = data;
×
468
                        }
469
                        Ok(RegionKind::MCDCBranch) => {
470
                            kind = RegionKind::MCDCBranch;
×
471
                            let (data, c1) = parse_leb128(bytes)?;
×
472
                            let (data, c2) = parse_leb128(data)?;
×
473

474
                            counter = parse_counter(c1, expressions);
×
475
                            false_count = parse_counter(c2, expressions);
×
476

477
                            let (data, id1) = parse_leb128(data)?;
×
478
                            let (data, tid1) = parse_leb128(data)?;
×
479
                            let (data, fid1) = parse_leb128(data)?;
×
480
                            bytes = data;
×
481

482
                            mcdc_params = Some(MCDCParams::Branch(BranchParameters {
×
483
                                id: id1.try_into().unwrap(),
×
484
                                false_cond: fid1.try_into().unwrap(),
×
485
                                true_cond: tid1.try_into().unwrap(),
×
486
                            }));
487
                        }
488
                        Ok(RegionKind::MCDCDecision) => {
489
                            kind = RegionKind::MCDCDecision;
×
490
                            let (data, bidx) = parse_leb128(data)?;
×
491
                            let (data, nc) = parse_leb128(data)?;
×
492
                            bytes = data;
×
493

494
                            mcdc_params = Some(MCDCParams::Decision(DecisionParameters {
×
495
                                bitmap_idx: bidx.try_into().unwrap(),
×
496
                                num_conditions: nc.try_into().unwrap(),
×
497
                            }));
498
                        }
499
                        e => panic!("Malformed: {:?}", e),
×
500
                    }
501
                }
502
            }
503

504
            let (data, delta_line) = parse_leb128(bytes)?;
300✔
505
            let (data, column_start) = parse_leb128(data)?;
300✔
506
            let (data, lines_len) = parse_leb128(data)?;
300✔
507
            let (data, column_end) = parse_leb128(data)?;
300✔
508
            bytes = data;
75✔
509

510
            let (column_start, column_end) = if column_start == 0 && column_end == 0 {
228✔
511
                (1usize, usize::MAX)
×
512
            } else {
513
                (column_start as usize, column_end as usize)
75✔
514
            };
515

516
            let line_start = last_line + delta_line as usize;
150✔
517
            let line_end = line_start + lines_len as usize;
150✔
518
            last_line = line_start;
75✔
519

520
            // Add region working-out-stuff
521
            mapping.push(CounterMappingRegion {
225✔
522
                kind,
150✔
523
                count: counter,
150✔
524
                false_count,
150✔
525
                file_id: *i as usize,
150✔
526
                expanded_file_id: expanded_file_id as _,
150✔
527
                loc: SourceLocation {
75✔
528
                    line_start,
150✔
529
                    line_end,
150✔
530
                    column_start,
75✔
531
                    column_end,
75✔
532
                },
533
                mcdc_params,
75✔
534
            });
535
        }
536
    }
537
    Ok((bytes, mapping))
18✔
538
}
539

540
fn parse_profile_data<'data, R: ReadRef<'data>>(
3✔
541
    endian: Endianness,
542
    section: &Section<'data, '_, R>,
543
) -> Result<Vec<ProfileData>, SectionReadError> {
544
    if let Ok(data) = section.data() {
6✔
545
        let mut bytes = data;
6✔
546
        let mut res = vec![];
6✔
547
        while !bytes.is_empty() {
6✔
548
            // bytes.len() >= 24 {
549
            let name_md5 = endian.read_u64(bytes[..8].try_into().unwrap());
18✔
550
            let structural_hash = endian.read_u64(bytes[8..16].try_into().unwrap());
18✔
551

552
            let _counter_ptr = endian.read_u64(bytes[16..24].try_into().unwrap());
18✔
553
            let counters_location = 24 + 16;
6✔
554
            if bytes.len() <= counters_location {
6✔
555
                bytes = &bytes[counters_location..];
×
NEW
556
                let counters_len = endian.read_u32(bytes[..4].try_into().unwrap());
×
557
                // TODO Might need to get the counter offset and get the list of counters from this?
558
                // And potentially check against the maximum number of counters just to make sure that
559
                // it's not being exceeded?
560
                //
561
                // Also counters_len >= 1 so this should be checked to make sure it's not malformed
562

563
                bytes = &bytes[8..];
×
564

565
                res.push(ProfileData {
×
566
                    name_md5,
×
567
                    structural_hash,
×
568
                    counters_len,
×
569
                });
570
            } else {
571
                bytes = &[];
3✔
572
            }
573
        }
574
        if !bytes.is_empty() {
3✔
575
            warn!("{} bytes left in profile data", bytes.len());
×
576
        }
577
        Ok(res)
3✔
578
    } else {
579
        Err(SectionReadError::EmptySection(LlvmSection::ProfileData))
×
580
    }
581
}
582

583
fn parse_profile_counters<'data, R: ReadRef<'data>>(
3✔
584
    endian: Endianness,
585
    section: &Section<'data, '_, R>,
586
) -> Result<Vec<u64>, SectionReadError> {
587
    if let Ok(data) = section.data() {
6✔
588
        let mut result = vec![];
6✔
589
        for i in (0..data.len()).step_by(8) {
84✔
590
            if data.len() < (i + 8) {
156✔
591
                break;
×
592
            }
593
            result.push(endian.read_u64(data[i..(i + 8)].try_into().unwrap()));
624✔
594
        }
595
        Ok(result)
3✔
596
    } else {
597
        Err(SectionReadError::EmptySection(LlvmSection::ProfileCounts))
×
598
    }
599
}
600

601
/// The equivalent llvm function is `RawCoverageMappingReader::decodeCounter`. This makes it
602
/// stateless as I don't want to be maintaining an expression vector and clearing it and
603
/// repopulating for every function record.
604
fn parse_counter(input: u64, exprs: &mut Vec<Expression>) -> Counter {
195✔
605
    let ty = (Counter::ENCODING_TAG_MASK & input) as u8;
390✔
606
    let id = input >> 2; // For zero we don't actually care about this but we'll still do it
390✔
607
    let kind = match ty {
390✔
608
        0 => CounterType::Zero,
12✔
609
        1 => CounterType::ProfileInstrumentation,
114✔
610
        2 | 3 => {
611
            let expr_kind = if ty == 2 {
138✔
612
                ExprKind::Subtract
12✔
613
            } else {
614
                ExprKind::Add
57✔
615
            };
616
            let id = id as usize;
138✔
617
            if exprs.len() <= id {
138✔
618
                debug!(
×
619
                    "Not enough expressions resizing {}->{}",
620
                    exprs.len(),
×
621
                    id + 1
×
622
                );
623
                exprs.resize(id + 1, Expression::default());
×
624
            }
625
            exprs[id].set_kind(expr_kind);
207✔
626
            CounterType::Expression(expr_kind)
69✔
627
        }
628
        _ => unreachable!(),
629
    };
630
    Counter { kind, id }
631
}
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