GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 97.7% 169 / 0 / 173
Functions: 100.0% 10 / 0 / 10
Branches: 84.5% 196 / 0 / 232

src/internal/manifest_grammar.cpp
Line Branch Exec Source
1 /**
2 * @file manifest_grammar.cpp
3 * @brief Raw-text prepass that closes manifest identity and framing collisions before the INI backend can merge them.
4 * @details CSimpleIniCaseA merges duplicate sections, retains the last duplicate key, and absorbs an unterminated
5 * heredoc through end of file. This pass uses the same whitespace, section, line-break, and heredoc rules.
6 * It rejects lost identities, unclosed heredocs, discarded key lines, and resource-cap excesses before store
7 * allocation. One shared token model prevents a collision from escape through a backend difference.
8 */
9
10 #include "internal/manifest_grammar.hpp"
11
12 #include <limits>
13 #include <string>
14 #include <string_view>
15 #include <unordered_map>
16 #include <unordered_set>
17
18 namespace DetourModKit::manifest::detail
19 {
20 namespace
21 {
22 // The backend's IsSpace: a space, tab, or either newline character. Used for blank-line and trailing-name
23 // trimming exactly as FindEntry does.
24 290323 [[nodiscard]] bool is_space(char c) noexcept
25 {
26
7/8
✓ Branch 2 → 3 taken 286550 times.
✓ Branch 2 → 6 taken 3773 times.
✓ Branch 3 → 4 taken 286550 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 284952 times.
✓ Branch 4 → 6 taken 1598 times.
✓ Branch 5 → 6 taken 71467 times.
✓ Branch 5 → 7 taken 213485 times.
290323 return c == ' ' || c == '\t' || c == '\r' || c == '\n';
27 }
28
29 10761876 [[nodiscard]] bool is_newline(char c) noexcept
30 {
31
4/4
✓ Branch 2 → 3 taken 10760609 times.
✓ Branch 2 → 4 taken 1267 times.
✓ Branch 3 → 4 taken 69947 times.
✓ Branch 3 → 5 taken 10690662 times.
10761876 return c == '\r' || c == '\n';
32 }
33
34 74970 [[nodiscard]] std::string_view rtrim(std::string_view text) noexcept
35 {
36
6/6
✓ Branch 5 → 6 taken 78733 times.
✓ Branch 5 → 10 taken 7 times.
✓ Branch 8 → 9 taken 3770 times.
✓ Branch 8 → 10 taken 74963 times.
✓ Branch 11 → 3 taken 3770 times.
✓ Branch 11 → 12 taken 74970 times.
78740 while (!text.empty() && is_space(text.back()))
37 {
38 3770 text.remove_suffix(1);
39 }
40 74970 return text;
41 }
42
43 71137 [[nodiscard]] std::string to_lower(std::string_view text)
44 {
45
1/2
✓ Branch 4 → 5 taken 71137 times.
✗ Branch 4 → 25 not taken.
71137 std::string out(text);
46
2/2
✓ Branch 22 → 8 taken 890965 times.
✓ Branch 22 → 23 taken 71137 times.
1033239 for (char &c : out)
47 {
48
4/4
✓ Branch 10 → 11 taken 514660 times.
✓ Branch 10 → 13 taken 376305 times.
✓ Branch 11 → 12 taken 405 times.
✓ Branch 11 → 13 taken 514255 times.
890965 if (c >= 'A' && c <= 'Z')
49 {
50 405 c = static_cast<char>(c - 'A' + 'a');
51 }
52 }
53 71137 return out;
54 }
55
56 49 [[nodiscard]] std::unexpected<Error> fail(ErrorCode code, const char *context) noexcept
57 {
58 49 return std::unexpected(Error{code, context});
59 }
60
61 3802 [[nodiscard]] bool consume_bytes(std::size_t bytes, std::size_t limit, std::size_t &total) noexcept
62 {
63
2/2
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 4 taken 3799 times.
3802 if (bytes > limit - total)
64 {
65 3 return false;
66 }
67 3799 total += bytes;
68 3799 return true;
69 }
70
71 1547 [[nodiscard]] std::string_view rung_parent(std::string_view name) noexcept
72 {
73 1547 const std::size_t marker = name.rfind(".rung.");
74
2/2
✓ Branch 3 → 4 taken 1418 times.
✓ Branch 3 → 5 taken 129 times.
1547 if (marker == std::string_view::npos)
75 {
76 1418 return {};
77 }
78 129 const std::string_view tail = name.substr(marker + 6);
79
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 129 times.
129 if (tail.empty())
80 {
81 return {};
82 }
83 129 std::size_t index = 0;
84
2/2
✓ Branch 17 → 11 taken 175 times.
✓ Branch 17 → 18 taken 129 times.
304 for (const char c : tail)
85 {
86
2/4
✓ Branch 11 → 12 taken 175 times.
✗ Branch 11 → 13 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 175 times.
175 if (c < '0' || c > '9')
87 {
88 return {};
89 }
90 175 const std::size_t digit = static_cast<std::size_t>(c - '0');
91 175 constexpr std::size_t MAX_INDEX = std::numeric_limits<std::size_t>::max();
92
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 175 times.
175 if (index > (MAX_INDEX - digit) / 10U)
93 {
94 return {};
95 }
96 175 index = (index * 10U) + digit;
97 }
98 129 return name.substr(0, marker);
99 }
100 } // namespace
101
102 278 Result<void> validate_manifest_grammar(std::string_view text, const GrammarLimits &limits, const char *context)
103 {
104
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 7 taken 276 times.
278 if (text.size() > limits.max_file_bytes)
105 {
106 2 return fail(ErrorCode::SizeTooLarge, context);
107 }
108 // The backend strips a leading UTF-8 BOM before tokenizing, so a BOM-prefixed first line is invisible here yet
109 // parsed by the store: its section or key identities would escape collision detection. The checked serializer
110 // never emits a BOM, so rejection is round-trip safe.
111
2/2
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 12 taken 275 times.
276 if (text.starts_with("\xEF\xBB\xBF"))
112 {
113 1 return fail(ErrorCode::MalformedLine, context);
114 }
115 // The backend's tokenizer is NUL-terminated and silently stops at the first '\0', so every byte after it would
116 // be validated here yet never loaded: a record could vanish without an error. The checked serializer never
117 // emits a NUL, so rejection is round-trip safe.
118
2/2
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 17 taken 274 times.
275 if (text.find('\0') != std::string_view::npos)
119 {
120 1 return fail(ErrorCode::MalformedLine, context);
121 }
122
123 274 std::unordered_set<std::string> seen_sections;
124 274 std::unordered_set<std::string> seen_keys; // reset on each section
125 274 std::unordered_map<std::string, std::size_t> rung_counts;
126 274 std::size_t section_count = 0;
127 274 std::size_t key_count = 0;
128 274 std::size_t record_count = 0;
129 274 std::size_t total_bytes = 0;
130
131 274 const std::size_t size = text.size();
132 274 std::size_t pos = 0;
133
134 71208 const auto line_end_from = [&](std::size_t start) noexcept
135 {
136
5/6
✓ Branch 4 → 5 taken 9859553 times.
✗ Branch 4 → 9 not taken.
✓ Branch 7 → 8 taken 9788345 times.
✓ Branch 7 → 9 taken 71208 times.
✓ Branch 10 → 3 taken 9788345 times.
✓ Branch 10 → 11 taken 71208 times.
9859553 while (start < size && !is_newline(text[start]))
137 {
138 9788345 ++start;
139 }
140 71208 return start;
141 274 };
142 // Consume one line terminator: a lone `\r`, a lone `\n`, or a `\r\n` pair, matching the backend's SkipNewLine.
143 96 const auto skip_newline = [&](std::size_t at) noexcept
144 {
145
5/6
✓ Branch 2 → 3 taken 96 times.
✗ Branch 2 → 6 not taken.
✓ Branch 4 → 5 taken 72 times.
✓ Branch 4 → 6 taken 24 times.
✓ Branch 7 → 8 taken 72 times.
✓ Branch 7 → 15 taken 24 times.
96 if (at < size && text[at] == '\r')
146 {
147 72 ++at;
148
3/6
✓ Branch 8 → 9 taken 72 times.
✗ Branch 8 → 12 not taken.
✓ Branch 10 → 11 taken 72 times.
✗ Branch 10 → 12 not taken.
✓ Branch 13 → 14 taken 72 times.
✗ Branch 13 → 22 not taken.
72 if (at < size && text[at] == '\n')
149 {
150 72 ++at;
151 }
152 }
153
3/6
✓ Branch 15 → 16 taken 24 times.
✗ Branch 15 → 19 not taken.
✓ Branch 17 → 18 taken 24 times.
✗ Branch 17 → 19 not taken.
✓ Branch 20 → 21 taken 24 times.
✗ Branch 20 → 22 not taken.
24 else if (at < size && text[at] == '\n')
154 {
155 24 ++at;
156 }
157 96 return at;
158 274 };
159
160
2/2
✓ Branch 284 → 22 taken 71344 times.
✓ Branch 284 → 285 taken 3 times.
71347 while (pos < size)
161 {
162 // Skip whitespace runs, which folds away leading indentation, blank lines, and every line terminator.
163
6/6
✓ Branch 24 → 25 taken 144212 times.
✓ Branch 24 → 29 taken 197 times.
✓ Branch 27 → 28 taken 73065 times.
✓ Branch 27 → 29 taken 71147 times.
✓ Branch 30 → 23 taken 73065 times.
✓ Branch 30 → 31 taken 71344 times.
144409 while (pos < size && is_space(text[pos]))
164 {
165 73065 ++pos;
166 }
167
2/2
✓ Branch 31 → 32 taken 197 times.
✓ Branch 31 → 33 taken 71147 times.
71344 if (pos >= size)
168 {
169 197 break;
170 }
171
172 // Comment line.
173
5/6
✓ Branch 34 → 35 taken 71147 times.
✗ Branch 34 → 37 not taken.
✓ Branch 36 → 37 taken 1 time.
✓ Branch 36 → 38 taken 71146 times.
✓ Branch 39 → 40 taken 1 time.
✓ Branch 39 → 42 taken 71146 times.
71147 if (text[pos] == ';' || text[pos] == '#')
174 {
175 1 pos = line_end_from(pos);
176 67345 continue;
177 }
178
179 // Section header.
180
2/2
✓ Branch 43 → 44 taken 67375 times.
✓ Branch 43 → 158 taken 3771 times.
71146 if (text[pos] == '[')
181 {
182 67375 ++pos;
183
5/6
✓ Branch 46 → 47 taken 67378 times.
✗ Branch 46 → 51 not taken.
✓ Branch 49 → 50 taken 3 times.
✓ Branch 49 → 51 taken 67375 times.
✓ Branch 52 → 45 taken 3 times.
✓ Branch 52 → 53 taken 67375 times.
67378 while (pos < size && is_space(text[pos]))
184 {
185 3 ++pos;
186 }
187 67375 const std::size_t name_start = pos;
188
7/8
✓ Branch 55 → 56 taken 928391 times.
✗ Branch 55 → 62 not taken.
✓ Branch 57 → 58 taken 861017 times.
✓ Branch 57 → 62 taken 67374 times.
✓ Branch 60 → 61 taken 861016 times.
✓ Branch 60 → 62 taken 1 time.
✓ Branch 63 → 54 taken 861016 times.
✓ Branch 63 → 64 taken 67375 times.
928391 while (pos < size && text[pos] != ']' && !is_newline(text[pos]))
189 {
190 861016 ++pos;
191 }
192
5/6
✓ Branch 64 → 65 taken 67375 times.
✗ Branch 64 → 67 not taken.
✓ Branch 66 → 67 taken 1 time.
✓ Branch 66 → 68 taken 67374 times.
✓ Branch 69 → 70 taken 1 time.
✓ Branch 69 → 73 taken 67374 times.
67375 if (pos >= size || text[pos] != ']')
193 {
194 // No closing bracket. The backend does not discard this line: FindEntry points its section cursor
195 // at the name before testing for `]`, and on the miss it resumes the scan without clearing that
196 // cursor, so the next key line's NUL terminator folds the unterminated name plus an embedded
197 // newline into a section identity this pass never validated. A `sig.`-prefixed record can reach
198 // the store past every collision, prefix, and size check. A canonical manifest never opens a
199 // bracket it does not close, so fail closed.
200 1 return fail(ErrorCode::MalformedLine, context);
201 }
202
1/2
✓ Branch 73 → 74 taken 67374 times.
✗ Branch 73 → 305 not taken.
67374 const std::string_view name = rtrim(text.substr(name_start, pos - name_start));
203 67374 pos = line_end_from(pos);
204
205
1/2
✓ Branch 76 → 77 taken 67374 times.
✗ Branch 76 → 305 not taken.
67374 std::string folded = to_lower(name);
206 // An empty section name (`[]`, `[ ]`) is the backend's implicit default section, which also holds any
207 // keys before the first header; re-opening it would let a key collision split across the `[]` escape
208 // the per-section namespace. A canonical manifest never names it, so reject it outright.
209
2/2
✓ Branch 78 → 79 taken 1 time.
✓ Branch 78 → 82 taken 67373 times.
67374 if (folded.empty())
210 {
211 1 return fail(ErrorCode::MalformedLine, context);
212 }
213 // The `manifest` header and every `sig.` prefix must be canonical lowercase, else the case-sensitive
214 // store would silently drop the section (a lost record). Fail closed instead.
215
9/10
✓ Branch 82 → 83 taken 67373 times.
✗ Branch 82 → 291 not taken.
✓ Branch 83 → 84 taken 273 times.
✓ Branch 83 → 87 taken 67100 times.
✓ Branch 86 → 87 taken 272 times.
✓ Branch 86 → 91 taken 1 time.
✓ Branch 88 → 89 taken 1560 times.
✓ Branch 88 → 92 taken 65812 times.
✓ Branch 93 → 94 taken 2 times.
✓ Branch 93 → 97 taken 67371 times.
136305 if ((folded == "manifest" && name != "manifest") ||
216
2/2
✓ Branch 90 → 91 taken 1 time.
✓ Branch 90 → 92 taken 1559 times.
68932 (folded.starts_with("sig.") && !name.starts_with("sig.")))
217 {
218 2 return fail(ErrorCode::MalformedLine, context);
219 }
220 // The folded, trimmed name is the merge key: two sections reaching it by case, whitespace, or exact
221 // repetition would collapse into one before the trust gate.
222
2/2
✓ Branch 97 → 98 taken 2 times.
✓ Branch 97 → 101 taken 67369 times.
67371 if (section_count >= limits.max_sections)
223 {
224 2 return fail(ErrorCode::SizeTooLarge, context);
225 }
226
4/4
✓ Branch 103 → 104 taken 67358 times.
✓ Branch 103 → 292 taken 11 times.
✓ Branch 104 → 105 taken 8 times.
✓ Branch 104 → 108 taken 67350 times.
67369 if (!seen_sections.insert(std::move(folded)).second)
227 {
228 8 return fail(ErrorCode::ManifestIdentityCollision, context);
229 }
230 67350 ++section_count;
231
232 // Classify record vs rung on the raw name, not the fold: parse() reads `.rung.` case-sensitively and
233 // treats a miscased marker as an ordinary label, so folding here would charge a legitimate record
234 // (e.g. `sig.a.RUNG.0`, label `a.RUNG.0`) against the rung cap the two passes must agree on.
235
2/2
✓ Branch 109 → 110 taken 1547 times.
✓ Branch 109 → 152 taken 65803 times.
67350 if (name.starts_with("sig."))
236 {
237 1547 const std::string_view parent = rung_parent(name);
238
6/6
✓ Branch 112 → 113 taken 129 times.
✓ Branch 112 → 116 taken 1418 times.
✓ Branch 114 → 115 taken 1 time.
✓ Branch 114 → 116 taken 128 times.
✓ Branch 117 → 118 taken 1 time.
✓ Branch 117 → 121 taken 1546 times.
1547 if (!parent.empty() && parent.size() <= 4U)
239 {
240 1 return fail(ErrorCode::MalformedLine, context);
241 }
242
4/6
✓ Branch 122 → 123 taken 1418 times.
✓ Branch 122 → 124 taken 128 times.
✓ Branch 123 → 125 taken 1418 times.
✗ Branch 123 → 302 not taken.
✓ Branch 124 → 125 taken 128 times.
✗ Branch 124 → 302 not taken.
1546 const std::string_view label = parent.empty() ? name.substr(4) : parent.substr(4);
243
2/2
✓ Branch 126 → 127 taken 1 time.
✓ Branch 126 → 130 taken 1545 times.
1546 if (label.size() > limits.max_field_bytes)
244 {
245 1 return fail(ErrorCode::SizeTooLarge, context);
246 }
247
2/2
✓ Branch 131 → 132 taken 1417 times.
✓ Branch 131 → 137 taken 128 times.
1545 if (parent.empty())
248 {
249
2/2
✓ Branch 132 → 133 taken 2 times.
✓ Branch 132 → 136 taken 1415 times.
1417 if (record_count >= limits.max_records)
250 {
251 2 return fail(ErrorCode::SizeTooLarge, context);
252 }
253 1415 ++record_count;
254 }
255 else
256 {
257
2/4
✓ Branch 139 → 140 taken 128 times.
✗ Branch 139 → 295 not taken.
✓ Branch 140 → 141 taken 128 times.
✗ Branch 140 → 293 not taken.
256 auto count_it = rung_counts.try_emplace(std::string(parent), 0).first;
258
2/2
✓ Branch 144 → 145 taken 2 times.
✓ Branch 144 → 148 taken 126 times.
128 if (count_it->second >= limits.max_rungs_per_record)
259 {
260 2 return fail(ErrorCode::SizeTooLarge, context);
261 }
262 126 ++count_it->second;
263 }
264 }
265 67344 seen_keys.clear();
266 67344 key_count = 0;
267 67344 continue;
268
2/2
✓ Branch 155 → 156 taken 19 times.
✓ Branch 155 → 157 taken 67344 times.
67374 }
269
270 // Key line. The key runs to the first `=` or newline.
271 3771 const std::size_t key_start = pos;
272
7/8
✓ Branch 160 → 161 taken 37547 times.
✗ Branch 160 → 167 not taken.
✓ Branch 162 → 163 taken 33780 times.
✓ Branch 162 → 167 taken 3767 times.
✓ Branch 165 → 166 taken 33776 times.
✓ Branch 165 → 167 taken 4 times.
✓ Branch 168 → 159 taken 33776 times.
✓ Branch 168 → 169 taken 3771 times.
37547 while (pos < size && text[pos] != '=' && !is_newline(text[pos]))
273 {
274 33776 ++pos;
275 }
276
5/6
✓ Branch 169 → 170 taken 3771 times.
✗ Branch 169 → 172 not taken.
✓ Branch 171 → 172 taken 4 times.
✓ Branch 171 → 173 taken 3767 times.
✓ Branch 174 → 175 taken 4 times.
✓ Branch 174 → 178 taken 3767 times.
3771 if (pos >= size || text[pos] != '=')
277 {
278 // The backend discards a noncomment line with no `=`. A dropped separator can restore a default value.
279 4 return fail(ErrorCode::MalformedLine, context);
280 }
281
2/2
✓ Branch 178 → 179 taken 4 times.
✓ Branch 178 → 182 taken 3763 times.
3767 if (pos == key_start)
282 {
283 // The backend discards an empty key without entry into heredoc mode.
284 4 return fail(ErrorCode::MalformedLine, context);
285 }
286
1/2
✓ Branch 182 → 183 taken 3763 times.
✗ Branch 182 → 311 not taken.
3763 const std::string_view key = rtrim(text.substr(key_start, pos - key_start));
287 3763 ++pos; // past '='
288
8/10
✓ Branch 186 → 187 taken 7526 times.
✗ Branch 186 → 195 not taken.
✓ Branch 189 → 190 taken 7525 times.
✓ Branch 189 → 195 taken 1 time.
✓ Branch 191 → 192 taken 3762 times.
✓ Branch 191 → 194 taken 3763 times.
✗ Branch 193 → 194 not taken.
✓ Branch 193 → 195 taken 3762 times.
✓ Branch 196 → 185 taken 3763 times.
✓ Branch 196 → 197 taken 3763 times.
7526 while (pos < size && !is_newline(text[pos]) && (text[pos] == ' ' || text[pos] == '\t'))
289 {
290 3763 ++pos;
291 }
292 3763 const std::size_t value_start = pos;
293 3763 const std::size_t value_end = line_end_from(pos);
294
1/2
✓ Branch 198 → 199 taken 3763 times.
✗ Branch 198 → 311 not taken.
3763 const std::string_view value = rtrim(text.substr(value_start, value_end - value_start));
295 3763 pos = value_end;
296
297
1/2
✓ Branch 200 → 201 taken 3763 times.
✗ Branch 200 → 311 not taken.
3763 std::string folded_key = to_lower(key);
298
2/2
✓ Branch 203 → 204 taken 1 time.
✓ Branch 203 → 207 taken 3762 times.
3763 if (key != folded_key)
299 {
300 1 return fail(ErrorCode::MalformedLine, context);
301 }
302
2/2
✓ Branch 207 → 208 taken 2 times.
✓ Branch 207 → 211 taken 3760 times.
3762 if (key_count >= limits.max_keys_per_section)
303 {
304 2 return fail(ErrorCode::SizeTooLarge, context);
305 }
306
4/4
✓ Branch 213 → 214 taken 3742 times.
✓ Branch 213 → 306 taken 18 times.
✓ Branch 214 → 215 taken 1 time.
✓ Branch 214 → 218 taken 3741 times.
3760 if (!seen_keys.insert(std::move(folded_key)).second)
307 {
308 1 return fail(ErrorCode::ManifestIdentityCollision, context);
309 }
310 3741 ++key_count;
311
312
2/2
✓ Branch 219 → 220 taken 28 times.
✓ Branch 219 → 265 taken 3713 times.
3741 if (value.starts_with("<<<"))
313 {
314 // A heredoc runs until a line whose trailing-trimmed form EQUALS the tag (case-sensitive, matching the
315 // case-sensitive store). An unterminated block would absorb every record below it into this value.
316
1/2
✓ Branch 220 → 221 taken 28 times.
✗ Branch 220 → 308 not taken.
28 const std::string_view tag = value.substr(3);
317 // An empty tag terminates differently here and in the backend: the backend's terminator trim never
318 // removes a line's first character, so a whitespace-only line closes the block for this pass but not
319 // for the store, desynchronizing every section below. The checked serializer never emits a tagless
320 // heredoc, so rejection is round-trip safe.
321
2/2
✓ Branch 222 → 223 taken 2 times.
✓ Branch 222 → 226 taken 26 times.
28 if (tag.empty())
322 {
323 2 return fail(ErrorCode::ManifestFramingUnsafe, context);
324 }
325 26 pos = skip_newline(pos);
326 26 std::size_t body_bytes = 0;
327 26 bool has_body_line = false;
328 26 bool closed = false;
329
2/2
✓ Branch 253 → 228 taken 70 times.
✓ Branch 253 → 254 taken 2 times.
72 while (pos < size)
330 {
331 70 const std::size_t body_start = pos;
332 70 const std::size_t body_end = line_end_from(pos);
333
1/2
✓ Branch 229 → 230 taken 70 times.
✗ Branch 229 → 307 not taken.
70 const std::string_view body_line = text.substr(body_start, body_end - body_start);
334 70 pos = skip_newline(body_end);
335
2/2
✓ Branch 233 → 234 taken 23 times.
✓ Branch 233 → 239 taken 47 times.
70 if (rtrim(body_line) == tag)
336 {
337 // A terminator as the first body line is not an empty value in the backend: its
338 // LoadMultiLineText leaves the value cursor on that line and restores the line break it
339 // NUL-tested, so the store loads the tag line plus every byte up to the next NUL the parser
340 // writes. This pass models that content as empty and never charges it against the caps, and
341 // the content can carry a raw `\r` or a leading `<<<` past the shared validator. An empty
342 // value is emitted raw, never as a heredoc, so rejection is round-trip safe. A blank body line
343 // before the terminator is a genuinely empty value in both tokenizers and stays accepted.
344
2/2
✓ Branch 234 → 235 taken 4 times.
✓ Branch 234 → 238 taken 19 times.
23 if (!has_body_line)
345 {
346 4 return fail(ErrorCode::ManifestFramingUnsafe, context);
347 }
348 19 closed = true;
349 19 break;
350 }
351
5/6
✓ Branch 239 → 240 taken 25 times.
✓ Branch 239 → 242 taken 22 times.
✓ Branch 241 → 242 taken 25 times.
✗ Branch 241 → 245 not taken.
✓ Branch 247 → 248 taken 1 time.
✓ Branch 247 → 251 taken 46 times.
94 if ((has_body_line && !consume_bytes(1, limits.max_field_bytes, body_bytes)) ||
352
2/2
✓ Branch 244 → 245 taken 1 time.
✓ Branch 244 → 246 taken 46 times.
47 !consume_bytes(body_line.size(), limits.max_field_bytes, body_bytes))
353 {
354 1 return fail(ErrorCode::SizeTooLarge, context);
355 }
356 46 has_body_line = true;
357 }
358
2/2
✓ Branch 254 → 255 taken 2 times.
✓ Branch 254 → 258 taken 19 times.
21 if (!closed)
359 {
360 2 return fail(ErrorCode::ManifestFramingUnsafe, context);
361 }
362
1/2
✗ Branch 259 → 260 not taken.
✓ Branch 259 → 263 taken 19 times.
19 if (!consume_bytes(body_bytes, limits.max_total_decoded_bytes, total_bytes))
363 {
364 return fail(ErrorCode::SizeTooLarge, context);
365 }
366 }
367 else
368 {
369
2/2
✓ Branch 266 → 267 taken 2 times.
✓ Branch 266 → 270 taken 3711 times.
3713 if (value.size() > limits.max_field_bytes)
370 {
371 2 return fail(ErrorCode::SizeTooLarge, context);
372 }
373
2/2
✓ Branch 272 → 273 taken 2 times.
✓ Branch 272 → 276 taken 3709 times.
3711 if (!consume_bytes(value.size(), limits.max_total_decoded_bytes, total_bytes))
374 {
375 2 return fail(ErrorCode::SizeTooLarge, context);
376 }
377 }
378
2/2
✓ Branch 278 → 279 taken 3728 times.
✓ Branch 278 → 282 taken 17 times.
3763 }
379 200 return {};
380 332 }
381 } // namespace DetourModKit::manifest::detail
382