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

xd009642 / llvm-profparser / #200

19 May 2025 09:36AM UTC coverage: 69.822% (-3.0%) from 72.851%
#200

push

web-flow
Do some speedup stuff again (#49)

* Reserve some map capacity

* Reverse order of expressions list

This favours deeper nodes in the expression tree meaning less unresolved
expressions and less iterations through the entire expression tree.

* Next plan minimising md5 computation!

* Bump MSRV again

3 of 5 new or added lines in 3 files covered. (60.0%)

53 existing lines in 4 files now uncovered.

1180 of 1690 relevant lines covered (69.82%)

3.54 hits per line

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

78.03
/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, Section};
8
use std::convert::TryInto;
9
use std::error::Error;
10
use std::fmt;
11
use std::fs;
12
use std::path::{Path, PathBuf};
13
use tracing::{debug, error, trace, warn};
14

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

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

42
#[derive(Copy, Clone, Debug)]
43
pub enum SectionReadError {
44
    EmptySection(LlvmSection),
45
    MissingSection(LlvmSection),
46
    InvalidPathList,
47
}
48

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

59
impl Error for SectionReadError {}
60

61
pub fn read_object_file(object: &Path, version: u64) -> Result<CoverageMappingInfo> {
1✔
62
    // I believe vnode sections added by llvm are unnecessary
63

64
    let binary_data = fs::read(object)?;
1✔
65
    let object_file = object::File::parse(&*binary_data)?;
2✔
66

67
    let cov_fun = object_file
3✔
68
        .section_by_name("__llvm_covfun")
69
        .or(object_file.section_by_name(".lcovfun"))
1✔
70
        .map(|x| parse_coverage_functions(object_file.endianness(), &x))
2✔
71
        .ok_or(SectionReadError::MissingSection(
1✔
72
            LlvmSection::CoverageFunctions,
1✔
73
        ))??;
74

75
    debug!("Parsed covfun section: {:?}", cov_fun);
2✔
76

77
    let cov_map = object_file
3✔
78
        .section_by_name("__llvm_covmap")
79
        .or(object_file.section_by_name(".lcovmap"))
1✔
80
        .map(|x| parse_coverage_mapping(object_file.endianness(), &x, version))
2✔
81
        .ok_or(SectionReadError::MissingSection(LlvmSection::CoverageMap))??;
1✔
82

83
    debug!("Parsed covmap section: {:?}", cov_map);
2✔
84

85
    let prof_counts = object_file
2✔
86
        .section_by_name("__llvm_prf_cnts")
87
        .or(object_file.section_by_name(".lprfc"))
1✔
88
        .and_then(|x| parse_profile_counters(object_file.endianness(), &x).ok());
2✔
89

90
    debug!("Parsed prf_cnts: {:?}", prof_counts);
2✔
91

92
    let prof_data = object_file
2✔
93
        .section_by_name("__llvm_prf_data")
94
        .or(object_file.section_by_name(".lprfd"))
1✔
95
        .and_then(|x| parse_profile_data(object_file.endianness(), &x).ok());
2✔
96

97
    debug!("Parsed prf_data section: {:?}", prof_data);
2✔
98

99
    Ok(CoverageMappingInfo {
1✔
100
        cov_map,
1✔
101
        cov_fun,
1✔
102
        prof_counts,
1✔
103
        prof_data,
1✔
104
    })
105
}
106

107
impl<'a> CoverageMapping<'a> {
108
    pub fn new(
1✔
109
        object_files: &[PathBuf],
110
        profile: &'a InstrumentationProfile,
111
        allow_parsing_failures: bool,
112
    ) -> Result<Self> {
113
        let mut mapping_info = vec![];
1✔
114
        let version = match profile.version() {
2✔
115
            Some(v) => v,
1✔
116
            None => bail!("Invalid profile instrumentation, no version number provided"),
×
117
        };
118
        for file in object_files {
2✔
119
            match read_object_file(file.as_path(), version) {
2✔
120
                Ok(info) => mapping_info.push(info),
1✔
121
                Err(e) => {
×
122
                    error!("{} couldn't be interpretted: {}", file.display(), e);
×
123
                    if !allow_parsing_failures {
×
124
                        return Err(e);
×
125
                    }
126
                }
127
            };
128
        }
129
        Ok(Self {
1✔
130
            profile,
×
131
            mapping_info,
1✔
132
        })
133
    }
134

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

151
    pub fn generate_subreport<P>(&self, mut predicate: P) -> CoverageReport
1✔
152
    where
153
        P: FnMut(&[PathBuf]) -> bool,
154
    {
155
        let mut report = CoverageReport::default();
1✔
156
        //let base_region_ids = info.get_simple_counters(self.profile);
157
        for info in &self.mapping_info {
2✔
158
            for func in &info.cov_fun {
2✔
159
                let base_region_ids = self.get_simple_counters(func);
1✔
160
                let paths = info.get_files_from_id(func.header.filenames_ref);
1✔
161
                if paths.is_empty() || !predicate(&paths) {
3✔
162
                    continue;
×
163
                }
164

165
                let mut region_ids = base_region_ids.clone();
1✔
166

167
                for region in func.regions.iter().filter(|x| !x.count.is_expression()) {
4✔
168
                    let count = region_ids.get(&region.count).copied().unwrap_or_default();
2✔
169
                    let result = report
1✔
170
                        .files
×
171
                        .entry(paths[region.file_id].clone())
1✔
172
                        .or_default();
173
                    result.insert(region.loc.clone(), count as usize);
1✔
174
                }
175

176
                let mut pending_exprs = vec![];
1✔
177

178
                for (expr_index, expr) in func.expressions.iter().enumerate().rev() {
2✔
179
                    let lhs = region_ids.get(&expr.lhs);
2✔
180
                    let rhs = region_ids.get(&expr.rhs);
1✔
181
                    match (lhs, rhs) {
1✔
182
                        (Some(lhs), Some(rhs)) => {
1✔
183
                            let count: i64 = match expr.kind {
1✔
184
                                ExprKind::Subtract => {
×
185
                                    trace!("Subtracting counts: {} - {}", lhs, rhs);
1✔
186
                                    lhs - rhs
2✔
187
                                }
188
                                ExprKind::Add => {
×
189
                                    trace!("Adding counts: {} + {}", lhs, rhs);
2✔
190
                                    lhs + rhs
2✔
191
                                }
192
                            };
193

194
                            let counter = Counter {
195
                                kind: CounterType::Expression(expr.kind),
1✔
196
                                id: expr_index as _,
1✔
197
                            };
198

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

250
                            let counter = Counter {
251
                                kind: CounterType::Expression(expr.kind),
1✔
252
                                id: *expr_index as _,
1✔
253
                            };
254

255
                            region_ids.insert(counter, count);
2✔
256
                            if let Some(expr_region) = func.regions.iter().find(|x| {
2✔
257
                                x.count.is_expression() && x.count.id == *expr_index as u64
1✔
258
                            }) {
259
                                let result = report
1✔
260
                                    .files
×
261
                                    .entry(paths[expr_region.file_id].clone())
2✔
262
                                    .or_default();
263
                                result.insert(expr_region.loc.clone(), count as _);
1✔
264
                            }
265
                            pending_exprs[index] = None;
2✔
266
                            cleared_expressions += 1;
1✔
267
                        }
268
                        _ => {
×
UNCOV
269
                            index += 1;
×
270
                            continue;
×
271
                        }
272
                    }
273
                }
274
            }
275
        }
276
        report
1✔
277
    }
278

279
    pub fn generate_report(&self) -> CoverageReport {
1✔
280
        self.generate_subreport(|_| true)
3✔
281
    }
282
}
283

284
fn parse_coverage_mapping(
1✔
285
    endian: Endianness,
286
    section: &Section<'_, '_>,
287
    version: u64,
288
) -> Result<FxHashMap<u64, Vec<PathBuf>>, SectionReadError> {
289
    if let Ok(mut data) = section.data() {
2✔
290
        let mut result = FxHashMap::default();
1✔
291
        while !data.is_empty() {
3✔
292
            let data_len = data.len();
1✔
293
            // Read the number of affixed function records (now just 0 as not in this header)
294
            debug_assert_eq!(endian.read_i32_bytes(data[0..4].try_into().unwrap()), 0);
2✔
295
            let filename_data_len = endian.read_i32_bytes(data[4..8].try_into().unwrap());
1✔
296
            // Read the length of the affixed string that contains encoded coverage mapping data (now 0
297
            // as not in this header)
298
            debug_assert_eq!(endian.read_i32_bytes(data[8..12].try_into().unwrap()), 0);
1✔
299
            let _format_version = endian.read_i32_bytes(data[12..16].try_into().unwrap());
1✔
300

301
            let hash = md5::compute(&data[16..(filename_data_len as usize + 16)]);
1✔
302
            let hash = endian.read_u64_bytes(hash.0[..8].try_into().unwrap());
1✔
303

304
            //let bytes = &data[16..(16 + filename_data_len as usize)];
305
            let bytes = &data[16..];
1✔
306
            let (bytes, file_strings) = parse_path_list(bytes, version)
1✔
307
                .map_err(|_: nom::Err<NomError<_>>| SectionReadError::InvalidPathList)?;
×
308
            result.insert(hash, file_strings);
2✔
309
            let read_len = data_len - bytes.len();
1✔
310
            let padding = if !bytes.is_empty() && (read_len & 0x07) != 0 {
3✔
311
                8 - (read_len & 0x07)
1✔
312
            } else {
313
                0
×
314
            };
315
            if padding > bytes.len() {
1✔
316
                break;
317
            }
318
            data = &bytes[padding..];
2✔
319
        }
320
        Ok(result)
1✔
321
    } else {
322
        Err(SectionReadError::EmptySection(LlvmSection::CoverageMap))
×
323
    }
324
}
325

326
fn parse_coverage_functions(
1✔
327
    endian: Endianness,
328
    section: &Section<'_, '_>,
329
) -> Result<Vec<FunctionRecordV3>, SectionReadError> {
330
    debug!("Parsing coverage functions");
1✔
331
    if let Ok(original_data) = section.data() {
3✔
332
        let mut bytes = original_data;
1✔
333
        let mut res = vec![];
1✔
334
        let section_len = bytes.len();
1✔
335
        while !bytes.is_empty() {
2✔
336
            let name_hash = endian.read_u64_bytes(bytes[0..8].try_into().unwrap());
2✔
337
            let data_len = endian.read_u32_bytes(bytes[8..12].try_into().unwrap());
1✔
338
            let fn_hash = endian.read_u64_bytes(bytes[12..20].try_into().unwrap());
1✔
339
            let filenames_ref = endian.read_u64_bytes(bytes[20..28].try_into().unwrap());
1✔
340
            let header = FunctionRecordHeader {
341
                name_hash,
342
                data_len,
343
                fn_hash,
344
                filenames_ref,
345
            };
346
            let _start_len = bytes[28..].len();
1✔
347
            bytes = &bytes[28..];
1✔
348

349
            let (data, id_len) = parse_leb128::<NomError<_>>(bytes).unwrap();
1✔
350
            bytes = data;
1✔
351
            let mut filename_indices = vec![];
1✔
352
            for _ in 0..id_len {
3✔
353
                let (data, id) = parse_leb128::<NomError<_>>(bytes).unwrap(); // Issue
2✔
354
                filename_indices.push(id);
1✔
355
                bytes = data;
1✔
356
            }
357

358
            let (data, expr_len) = parse_leb128::<NomError<_>>(bytes).unwrap();
1✔
359
            let expr_len = expr_len as usize;
1✔
360
            bytes = data;
1✔
361
            let mut exprs = vec![Expression::default(); expr_len];
1✔
362
            for i in 0..expr_len {
3✔
363
                let (data, lhs) = parse_leb128::<NomError<_>>(bytes).unwrap();
2✔
364
                let (data, rhs) = parse_leb128::<NomError<_>>(data).unwrap();
1✔
365
                let lhs = parse_counter(lhs, &mut exprs);
1✔
366
                let rhs = parse_counter(rhs, &mut exprs);
1✔
367
                exprs[i].lhs = lhs;
1✔
368
                exprs[i].rhs = rhs;
1✔
369
                bytes = data;
1✔
370
            }
371

372
            let (data, regions) =
1✔
373
                parse_mapping_regions(bytes, &filename_indices, &mut exprs).unwrap();
374

375
            res.push(FunctionRecordV3 {
1✔
376
                header,
377
                regions,
1✔
378
                expressions: exprs,
1✔
379
            });
380

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

386
            bytes = data;
1✔
387
            let function_len = section_len - bytes.len(); // this should match header
1✔
388

389
            let padding = if function_len < section_len && (function_len & 0x07) != 0 {
3✔
390
                8 - (function_len & 0x07)
2✔
391
            } else {
392
                0
1✔
393
            };
394

395
            if padding > bytes.len() {
1✔
396
                break;
397
            }
398
            // Now apply padding, and if hash is 0 move on as it's a dummy otherwise add to result
399
            // And decide what end type will be
400
            bytes = &bytes[padding..];
2✔
401
        }
402
        Ok(res)
1✔
403
    } else {
404
        error!("Can't read data for coverage function section");
×
405
        Err(SectionReadError::EmptySection(
×
406
            LlvmSection::CoverageFunctions,
×
407
        ))
408
    }
409
}
410

411
/// This code is ported from `RawCoverageMappingReader::readMappingRegionsSubArray`
412
fn parse_mapping_regions<'a>(
1✔
413
    mut bytes: &'a [u8],
414
    file_indices: &[u64],
415
    expressions: &mut Vec<Expression>,
416
) -> IResult<&'a [u8], Vec<CounterMappingRegion>> {
417
    let mut mapping = vec![];
1✔
418
    for i in file_indices {
2✔
419
        let (data, regions_len) = parse_leb128(bytes)?;
2✔
420
        bytes = data;
1✔
421
        let mut last_line = 0;
1✔
422
        for _ in 0..regions_len {
1✔
423
            let mut false_count = Counter::default();
1✔
424
            let mut kind = RegionKind::Code;
1✔
425
            let (data, raw_header) = parse_leb128(bytes)?;
1✔
426
            bytes = data;
1✔
427
            let mut expanded_file_id = 0;
1✔
428
            let mut counter = parse_counter(raw_header, expressions);
1✔
429
            if counter.is_zero() {
1✔
430
                if raw_header & Counter::ENCODING_EXPANSION_REGION_BIT > 0 {
×
431
                    kind = RegionKind::Expansion;
×
432
                    expanded_file_id = raw_header >> Counter::ENCODING_TAG_AND_EXP_REGION_BITS;
×
433
                    if expanded_file_id >= file_indices.len() as u64 {
×
434
                        todo!()
435
                    }
436
                } else {
437
                    let shifted_counter = raw_header >> Counter::ENCODING_TAG_AND_EXP_REGION_BITS;
×
438
                    match shifted_counter.try_into() {
×
439
                        Ok(RegionKind::Code) | Ok(RegionKind::Skipped) => {}
×
440
                        Ok(RegionKind::Branch) => {
×
441
                            kind = RegionKind::Branch;
×
442
                            let (data, c1) = parse_leb128(bytes)?;
×
443
                            let (data, c2) = parse_leb128(data)?;
×
444

445
                            counter = parse_counter(c1, expressions);
×
446
                            false_count = parse_counter(c2, expressions);
×
447
                            bytes = data;
×
448
                        }
449
                        e => panic!("Malformed: {:?}", e),
×
450
                    }
451
                }
452
            }
453

454
            let (data, delta_line) = parse_leb128(bytes)?;
2✔
455
            let (data, column_start) = parse_leb128(data)?;
1✔
456
            let (data, lines_len) = parse_leb128(data)?;
1✔
457
            let (data, column_end) = parse_leb128(data)?;
1✔
458
            bytes = data;
1✔
459

460
            let (column_start, column_end) = if column_start == 0 && column_end == 0 {
3✔
461
                (1usize, usize::MAX)
×
462
            } else {
463
                (column_start as usize, column_end as usize)
1✔
464
            };
465

466
            let line_start = last_line + delta_line as usize;
1✔
467
            let line_end = line_start + lines_len as usize;
2✔
468
            last_line = line_start;
1✔
469

470
            // Add region working-out-stuff
471
            mapping.push(CounterMappingRegion {
2✔
472
                kind,
1✔
473
                count: counter,
1✔
474
                false_count,
1✔
475
                file_id: *i as usize,
1✔
476
                expanded_file_id: expanded_file_id as _,
1✔
477
                loc: SourceLocation {
1✔
478
                    line_start,
×
479
                    line_end,
×
480
                    column_start,
×
481
                    column_end,
×
482
                },
483
            });
484
        }
485
    }
486
    Ok((bytes, mapping))
1✔
487
}
488

489
fn parse_profile_data(
1✔
490
    endian: Endianness,
491
    section: &Section<'_, '_>,
492
) -> Result<Vec<ProfileData>, SectionReadError> {
493
    if let Ok(data) = section.data() {
2✔
494
        let mut bytes = data;
1✔
495
        let mut res = vec![];
1✔
496
        while !bytes.is_empty() {
2✔
497
            // bytes.len() >= 24 {
498
            let name_md5 = endian.read_u64_bytes(bytes[..8].try_into().unwrap());
2✔
499
            let structural_hash = endian.read_u64_bytes(bytes[8..16].try_into().unwrap());
1✔
500

501
            let _counter_ptr = endian.read_u64_bytes(bytes[16..24].try_into().unwrap());
1✔
502
            let counters_location = 24 + 16;
1✔
503
            if bytes.len() <= counters_location {
2✔
504
                bytes = &bytes[counters_location..];
×
505
                let counters_len = endian.read_u32_bytes(bytes[..4].try_into().unwrap());
×
506
                // TODO Might need to get the counter offset and get the list of counters from this?
507
                // And potentially check against the maximum number of counters just to make sure that
508
                // it's not being exceeded?
509
                //
510
                // Also counters_len >= 1 so this should be checked to make sure it's not malformed
511

512
                bytes = &bytes[8..];
×
513

514
                res.push(ProfileData {
×
515
                    name_md5,
516
                    structural_hash,
517
                    counters_len,
518
                });
519
            } else {
520
                bytes = &[];
1✔
521
            }
522
        }
523
        if !bytes.is_empty() {
2✔
524
            warn!("{} bytes left in profile data", bytes.len());
×
525
        }
526
        Ok(res)
1✔
527
    } else {
528
        Err(SectionReadError::EmptySection(LlvmSection::ProfileData))
×
529
    }
530
}
531

532
fn parse_profile_counters(
1✔
533
    endian: Endianness,
534
    section: &Section<'_, '_>,
535
) -> Result<Vec<u64>, SectionReadError> {
536
    if let Ok(data) = section.data() {
2✔
537
        let mut result = vec![];
1✔
538
        for i in (0..data.len()).step_by(8) {
2✔
539
            if data.len() < (i + 8) {
2✔
540
                break;
541
            }
542
            result.push(endian.read_u64_bytes(data[i..(i + 8)].try_into().unwrap()));
1✔
543
        }
544
        Ok(result)
1✔
545
    } else {
546
        Err(SectionReadError::EmptySection(LlvmSection::ProfileCounts))
×
547
    }
548
}
549

550
/// The equivalent llvm function is `RawCoverageMappingReader::decodeCounter`. This makes it
551
/// stateless as I don't want to be maintaining an expression vector and clearing it and
552
/// repopulating for every function record.
553
fn parse_counter(input: u64, exprs: &mut Vec<Expression>) -> Counter {
1✔
554
    let ty = (Counter::ENCODING_TAG_MASK & input) as u8;
2✔
555
    let id = input >> 2; // For zero we don't actually care about this but we'll still do it
2✔
556
    let kind = match ty {
2✔
557
        0 => CounterType::Zero,
1✔
558
        1 => CounterType::ProfileInstrumentation,
1✔
559
        2 | 3 => {
560
            let expr_kind = if ty == 2 {
2✔
561
                ExprKind::Subtract
1✔
562
            } else {
563
                ExprKind::Add
1✔
564
            };
565
            let id = id as usize;
1✔
566
            if exprs.len() <= id {
1✔
567
                debug!(
×
568
                    "Not enough expressions resizing {}->{}",
569
                    exprs.len(),
×
570
                    id + 1
×
571
                );
572
                exprs.resize(id + 1, Expression::default());
×
573
            }
574
            exprs[id].set_kind(expr_kind);
1✔
575
            CounterType::Expression(expr_kind)
1✔
576
        }
577
        _ => unreachable!(),
578
    };
579
    Counter { kind, id }
580
}
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