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

xd009642 / llvm-profparser / #223

02 Nov 2025 11:11AM UTC coverage: 69.021% (-0.5%) from 69.555%
#223

push

web-flow
Add a better error message (#66)

7 of 25 new or added lines in 2 files covered. (28.0%)

1241 of 1798 relevant lines covered (69.02%)

3.23 hits per line

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

82.61
/src/hash_table.rs
1
use crate::instrumentation_profile::{types::*, ParseResult};
2
use indexmap::IndexMap;
3
use nom::{
4
    error::{ErrorKind, ParseError, VerboseError, VerboseErrorKind},
5
    number::complete::*,
6
};
7
use std::borrow::Cow;
8
use std::mem::size_of;
9
use tracing::debug;
10

11
#[derive(Copy, Clone, Debug)]
12
struct KeyDataLen {
13
    key_len: u64,
14
    data_len: u64,
15
}
16

17
#[derive(Clone, Debug)]
18
pub(crate) struct HashTable(pub IndexMap<(u64, String), InstrProfRecord>);
19

20
fn read_key_data_len(input: &[u8]) -> ParseResult<'_, KeyDataLen> {
2✔
21
    let (bytes, key_len) = le_u64(input)?;
2✔
22
    let (bytes, data_len) = le_u64(bytes)?;
2✔
23
    let res = KeyDataLen { key_len, data_len };
2✔
24
    Ok((bytes, res))
2✔
25
}
26

27
fn read_key(input: &[u8], key_len: usize) -> ParseResult<'_, Cow<'_, str>> {
2✔
28
    if key_len > input.len() {
2✔
29
        Err(nom::Err::Failure(VerboseError::from_error_kind(
×
30
            &input[input.len()..],
×
31
            ErrorKind::Eof,
32
        )))
33
    } else {
34
        let res = String::from_utf8_lossy(&input[..key_len]);
2✔
35
        Ok((&input[key_len..], res))
4✔
36
    }
37
}
38

39
fn read_value(
2✔
40
    version: u64,
41
    mut input: &[u8],
42
    data_len: usize,
43
) -> ParseResult<'_, (u64, InstrProfRecord)> {
44
    if data_len % 8 != 0 {
2✔
45
        // Element is corrupted, it should be aligned
46
        let errors = vec![(
×
47
            input,
×
48
            VerboseErrorKind::Context("table data length is not 8 byte aligned"),
×
49
        )];
50
        return Err(nom::Err::Failure(VerboseError { errors }));
×
51
    }
52
    if input.len() < data_len {
2✔
NEW
53
        tracing::trace!("{} < {}", input.len(), data_len);
×
54
        return Err(nom::Err::Failure(VerboseError::from_error_kind(
×
55
            &input[input.len()..],
×
56
            ErrorKind::Eof,
57
        )));
58
    }
59
    let mut result = vec![];
2✔
60
    let end_len = input.len() - data_len;
4✔
61
    tracing::trace!("input: {} end: {}", input.len(), end_len);
4✔
62

63
    let expected_end = &input[data_len..];
4✔
64
    let mut last_hash = 0;
2✔
65

66
    while input.len() > end_len {
4✔
67
        let mut counts = vec![];
2✔
68
        let (bytes, hash) = le_u64(input)?;
4✔
69
        last_hash = hash;
2✔
70
        if bytes.len() <= end_len {
2✔
71
            break;
72
        }
73
        // This is only available for versions > v1. But as rust won't be going backwards to legacy
74
        // versions it's a safe assumption.
75
        tracing::trace!("1. le_u64: {}", bytes.len());
4✔
76
        let (bytes, counts_len) = le_u64(bytes)?;
4✔
77
        tracing::trace!("Counts len: {}", counts_len);
2✔
78
        if bytes.len() <= end_len {
2✔
79
            break;
80
        }
81
        input = bytes;
2✔
82
        if size_of::<u64>().saturating_mul(counts_len as usize) > input.len() {
2✔
NEW
83
            let errors = vec![(
×
NEW
84
                input,
×
NEW
85
                VerboseErrorKind::Context("hash_table value count length exceeds length of input"),
×
86
            )];
NEW
87
            return Err(nom::Err::Failure(VerboseError { errors }));
×
88
        }
89
        for _ in 0..counts_len {
4✔
90
            // tracing::trace!("2. le_u64: {}", input.len());
91
            let (bytes, count) = le_u64(input)?;
4✔
92
            input = bytes;
2✔
93
            counts.push(count);
2✔
94
        }
95
        result.push((hash, InstrProfRecord { counts, data: None }));
2✔
96
        if input.len() <= end_len {
2✔
97
            break;
98
        }
99

100
        // If the version is > v2 then there can also be value profiling data so lets try and parse
101
        // that now
102
        tracing::trace!("3. le_u32: {}", input.len());
2✔
103
        let (bytes, total_size) = le_u32(input)?;
4✔
104
        if bytes.len() <= end_len {
2✔
105
            break;
106
        }
107
        let (bytes, num_value_kinds) = le_u32(bytes)?;
2✔
108
        // Here it's just less than because we don't need to read anything else so if it's equal to
109
        // we're good
110
        if bytes.len() < end_len {
2✔
111
            break;
112
        }
113
        input = bytes;
2✔
114
        let value_prof_data = ValueProfData {
115
            total_size,
116
            num_value_kinds,
117
        };
118
        if value_prof_data.num_value_kinds > 0 && version > 2 {
2✔
119
            // If we actually want to change data in future get result.last_mut() and change it
120
            // there
121
            break;
122
        }
123
    }
124
    if result.is_empty() {
4✔
125
        result.push((last_hash, InstrProfRecord::default()));
×
126
    }
127
    input = expected_end;
2✔
128
    assert_eq!(result.len(), 1);
4✔
129
    Ok((input, result.remove(0)))
2✔
130
}
131

132
impl HashTable {
133
    fn new() -> Self {
2✔
134
        Self(IndexMap::new())
2✔
135
    }
136

137
    /// buckets is the data the hash table buckets start at - the start of the `HashTable` in memory.
138
    /// hash. offset shows the offset from the base address to the start of the `HashTable` as this
139
    /// will be used to correct any offsets
140
    pub(crate) fn parse<'a>(
2✔
141
        version: u64,
142
        input: &'a [u8],
143
        _offset: usize,
144
        bucket_start: usize,
145
    ) -> ParseResult<'a, Self> {
146
        let (bytes, num_buckets) = le_u64(&input[bucket_start..])?;
2✔
147
        debug!("Number of hashtable buckets: {}", num_buckets);
2✔
148
        let (_bytes, mut num_entries) = le_u64(bytes)?;
2✔
149
        debug!("Number of entries: {}", num_entries);
2✔
150
        let mut payload = input;
2✔
151
        let mut result = Self::new();
2✔
152
        //TODO is this change right?
153
        while num_entries > 0 {
4✔
154
            let (bytes, entries) = result.parse_bucket(version, payload, num_entries)?;
4✔
155
            payload = bytes;
2✔
156
            num_entries = entries;
2✔
157
        }
158
        Ok((payload, result))
2✔
159
    }
160

161
    fn parse_bucket<'a>(
2✔
162
        &mut self,
163
        version: u64,
164
        input: &'a [u8],
165
        mut num_entries: u64,
166
    ) -> ParseResult<'a, u64> {
167
        let (bytes, num_items_in_bucket) = le_u16(input)?;
2✔
168
        debug!(
2✔
169
            "Number of items in bucket: {}, input remaining: {}",
NEW
170
            num_items_in_bucket,
×
NEW
171
            input.len()
×
172
        );
173
        let mut remaining = bytes;
2✔
174
        for _i in 0..num_items_in_bucket {
4✔
175
            let (bytes, _hash) = le_u64(remaining)?;
2✔
176
            debug!("Hash(?): {}", _hash);
2✔
177
            let (bytes, lens) = read_key_data_len(bytes)?;
2✔
178
            let (bytes, key) = read_key(bytes, lens.key_len as usize)?;
2✔
179
            debug!("lengths: {:?} and key: {}", lens, key);
4✔
180
            let (bytes, (hash, value)) = read_value(version, bytes, lens.data_len as usize)?;
4✔
181
            debug!("hash: {}, value: {:?}", hash, value);
4✔
182
            self.0.insert((hash, key.to_string()), value);
4✔
183
            assert!(num_entries > 0);
2✔
184
            num_entries -= 1;
2✔
185

186
            remaining = bytes;
2✔
187
        }
188
        Ok((remaining, num_entries))
2✔
189
    }
190
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc