GCC Code Coverage Report


Directory: ./
File: firmware/libfirmware/util/include/rusefi/efistringutil.h
Date: 2025-10-03 00:57:22
Warnings: 1 unchecked decisions!
Coverage Exec Excl Total
Lines: 76.4% 55 0 72
Functions: 100.0% 10 0 10
Branches: 54.4% 31 0 57
Decisions: 50.0% 19 - 38

Line Branch Decision Exec Source
1 #pragma once
2
3 #include <cstdint>
4 #include <string_view>
5 #include <algorithm>
6
7 namespace rusefi::stringutil {
8 constexpr uint32_t DEFAULT_MAX_EXPECTED_STRING_LENGTH = 100;
9
10 /****************************************************************/
11 // TODO: refactor to constexpr
12 /****************************************************************/
13 bool strEqualCaseInsensitive(const char *str1, const char *str2);
14 bool strEqual(const char *str1, const char *str2);
15 float atoff(const char*);
16 /****************************************************************/
17
18 namespace implementation {
19 // Internal implementation: pointer version with bounded length
20 template <size_t MaxLen>
21 133 constexpr uint32_t efiStrlen_impl(char const* const str) noexcept {
22
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 133 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 133 times.
133 if (str == nullptr) {
23 return 0;
24 }
25
26 133 size_t i{};
27
28
3/4
✓ Branch 0 taken 687 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 554 times.
✓ Branch 3 taken 133 times.
0/1
? Decision couldn't be analyzed.
687 while (i < MaxLen && str[i] != '\0') {
29 554 ++i;
30 }
31
32 133 return static_cast<uint32_t>(i);
33 }
34
35 template <size_t MaxLen>
36 63 constexpr int32_t indexOf_impl(char const* const string, char const ch) noexcept {
37
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 63 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 63 times.
63 if (string == nullptr) {
38 return -1;
39 }
40
41 63 uint32_t i = 0;
42
1/2
✓ Branch 0 taken 345 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 345 times.
✗ Decision 'false' not taken.
345 while (i < MaxLen) {
43 345 char c = string[i];
44
2/2
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 305 times.
2/2
✓ Decision 'true' taken 40 times.
✓ Decision 'false' taken 305 times.
345 if (c == '\0') {
45 40 return -1;
46 }
47
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 282 times.
2/2
✓ Decision 'true' taken 23 times.
✓ Decision 'false' taken 282 times.
305 if (c == ch) {
48 23 return static_cast<int32_t>(i);
49 }
50 282 ++i;
51 }
52
53 return -1;
54 }
55 }
56
57 // Generic version with default max length (for pointers)
58 template <size_t MaxLen>
59 constexpr uint32_t efiStrnlen(char const* const str) noexcept {
60 return implementation::efiStrlen_impl<MaxLen>(str);
61 }
62
63 // Overload for native arrays: deduces size as max
64 template <size_t N>
65 constexpr uint32_t efiStrlen(char const (&arr)[N]) noexcept
66 requires (std::is_array_v<std::remove_cvref_t<decltype(arr)>> && N > 0) {
67 return implementation::efiStrlen_impl<N>(arr);
68 }
69
70 // Counts up to DEFAULT_MAX_EXPECTED_STRING_LENGTH(100) characters in the provided string
71 // Use efiStrnlen<> to raise the limit
72 template <size_t MaxLen = DEFAULT_MAX_EXPECTED_STRING_LENGTH, typename T>
73 133 constexpr uint32_t efiStrlen(T str) noexcept
74 requires (!std::is_array_v<std::remove_cvref_t<T>>) {
75 133 return implementation::efiStrlen_impl<MaxLen>(str);
76 }
77
78 template <size_t MaxLen, size_t N>
79 constexpr int32_t indexOfN(char const (&arr)[N], char const ch) noexcept
80 requires (std::is_array_v<std::remove_cvref_t<decltype(arr)>> && N > 0) {
81 return implementation::indexOf_impl<std::min(MaxLen, N)>(arr, ch);
82 }
83
84 template <size_t MaxLen, typename T>
85 constexpr int32_t indexOfN(T str, char const ch) noexcept
86 requires (!std::is_array_v<std::remove_cvref_t<T>>) {
87 return implementation::indexOf_impl<MaxLen>(str, ch);
88 }
89
90 // Overload for native arrays
91 template <size_t N>
92 constexpr int32_t indexOf(char const (&arr)[N], char const ch) noexcept
93 requires (std::is_array_v<std::remove_cvref_t<decltype(arr)>> && N > 0) {
94 return implementation::indexOf_impl<N>(arr, ch);
95 }
96
97 // Searches up to DEFAULT_MAX_EXPECTED_STRING_LENGTH(100) characters in the provided string
98 // Use indexOfN<> to raise the limit
99 template <typename T>
100 63 constexpr int32_t indexOf(T str, char const ch) noexcept
101 requires (!std::is_array_v<std::remove_cvref_t<T>>) {
102 63 return implementation::indexOf_impl<DEFAULT_MAX_EXPECTED_STRING_LENGTH>(str, ch);
103 }
104
105 namespace tests {
106 constexpr char const* tstr = "hello";
107 static_assert(efiStrlen(tstr) == 5);
108 static_assert(efiStrlen("hello") == 5);
109
110 constexpr char msg[12] = "world!";
111 static_assert(efiStrlen(msg) == 6);
112
113 constexpr char incomplete[12] = {'a', 'b', 'c'}; // not null-terminated
114 static_assert(efiStrlen(incomplete) == 3); // stops at non-null or 12
115
116 constexpr char nullstr[12] = {'\0'};
117 static_assert(efiStrlen(nullstr) == 0);
118
119 static_assert(indexOf("hello", 'e') == 1);
120 static_assert(indexOf("hello", 'x') == -1);
121
122 constexpr char name[] = "example";
123 static_assert(indexOf(name, 'a') == 2);
124
125 constexpr char const* name2 = "example2S";
126 static_assert(indexOf(name2, 'S') == 8);
127
128 constexpr char tst1[] = { ' ', ' ', ' ' };
129 static_assert(indexOf(tst1, 'S') == -1); // <- Ok, we knew only 3 were present
130 static_assert(indexOfN<3>(tst1, 'S') == -1);
131 static_assert(indexOfN<100>(tst1, 'S') == -1); // Array was passed and size is known OK;
132 constexpr char const* const tst2 = static_cast<char const*>(tst1);
133 // SEGFAULT
134 // static_assert(indexOfN<100>(tst2, 'S') == -1); // Array with few chars and no null-term was passed and size is unknown -> SEGFAULT
135 // SEGFAULT
136 static_assert(indexOfN<2>(tst2, 'S') == -1); // <- Ok, we explicitly passed size that is less or equal of real storage size
137 }
138
139 /*
140 From https://en.cppreference.com/w/cpp/string/wide/iswspace
141 Space (0x20, ' ')
142 Form feed (0x0c, '\f')
143 Line feed (0x0a, '\n')
144 Carriage return (0x0d, '\r')
145 Horizontal tab (0x09, '\t')
146 Vertical tab (0x0b, '\v').
147 */
148 37 constexpr bool is_whitespace(char const c) {
149 37 constexpr char matches[] = { ' ', '\f', '\n', '\r', '\t', '\v' };
150
1/1
✓ Branch 1 taken 37 times.
370 return std::any_of(std::begin(matches), std::end(matches), [c](char c0) { return c == c0; });
151 }
152
153 68 constexpr bool is_digit(char const c) {
154
2/4
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
✗ Branch 3 not taken.
68 return c >= '0' && c <= '9';
155 }
156
157 constexpr bool is_sign(char const c) {
158 return c == '+' || c == '-';
159 }
160
161 // "functor" to check for ws absence in string
162 struct no_ws {
163 constexpr explicit no_ws(const char* const str) : data(str) { }
164
165 constexpr explicit operator bool() const {
166 const std::string_view sv{data};
167 return !std::ranges::any_of(sv, is_whitespace);
168 }
169
170 const char* data;
171 };
172
173 constexpr int32_t ATOI_ERROR_CODE = -311223344;
174 constexpr int32_t ATOI_MAGIC_NUMBER_OF_MAX_PRECEEDING_WHITESPACE_CHARS = 5;
175 constexpr int32_t ATOI_MAX_INT32_DIGITS = 10;
176
177 namespace tests {
178 constexpr int32_t ATOI_ERROR_CODE = 10000000;
179 constexpr int32_t ATOI_ERROR_CODE_BAD_STRING = ATOI_ERROR_CODE + 1;
180 constexpr int32_t ATOI_ERROR_CODE_TOO_MUCH_WS = ATOI_ERROR_CODE + 2;
181 constexpr int32_t ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN = ATOI_ERROR_CODE + 3;
182 constexpr int32_t ATOI_ERROR_CODE_NO_DIGITS = ATOI_ERROR_CODE + 4;
183 constexpr int32_t ATOI_ERROR_CODE_TOO_MUCH_DIGITS = ATOI_ERROR_CODE + 5;
184 constexpr int32_t ATOI_ERROR_CODE_OVERFLOW_RANK = ATOI_ERROR_CODE + 6;
185 constexpr int32_t ATOI_ERROR_CODE_OVERFLOW_DIGIT = ATOI_ERROR_CODE + 7;
186 constexpr int32_t ATOI_ERROR_CODE_NO_DATA = ATOI_ERROR_CODE + 8;
187 constexpr int32_t ATOI_ERROR_CODE_TRAILING_NON_WS_CHARS = ATOI_ERROR_CODE + 9;
188 constexpr int32_t ATOI_ERROR_CODE_TRAILING_CHARS = ATOI_ERROR_CODE + 10;
189
190 enum class SafeAtoiErrorMode {
191 Test,
192 Prod
193 };
194 }
195
196 enum class SafeAtoiTrailingCharCheckMode {
197 allowOnlyWhitespace,
198 allowOnlyNull,
199 allowAll
200 };
201
202 // Mostly based on cppreference atoi page.
203 // Intended to mimic the behavior of libc's atoi in a constexpr-safe way:
204 // - Skips leading whitespaces (up to ATOI_MAGIC_NUMBER_OF_MAX_PRECEEDING_WHITESPACE_CHARS)
205 // - Accepts an optional '+' or '-' sign
206 // - Parses digits until the first non-digit
207 // - If onlyAllowWhitespacesAtTheEnd is true, trailing characters must be whitespace or null
208 // Returns ATOI_ERROR_CODE on invalid input, malformed format, or overflow.
209 template<tests::SafeAtoiErrorMode errorMode = tests::SafeAtoiErrorMode::Prod,
210 SafeAtoiTrailingCharCheckMode trailingCharsCheckMode = SafeAtoiTrailingCharCheckMode::allowAll>
211 37 constexpr int32_t safe_atoi(char const* const string) noexcept {
212
2/4
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 37 times.
37 if (string == nullptr || string[0] == '\0') {
213 if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) {
214 return tests::ATOI_ERROR_CODE_BAD_STRING;
215 }
216
217 return ATOI_ERROR_CODE;
218 }
219
220 37 char const* str{ string };
221
222
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 37 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 37 times.
37 while (is_whitespace(*str)) {
223 if ((str - string) < ATOI_MAGIC_NUMBER_OF_MAX_PRECEEDING_WHITESPACE_CHARS) {
224 ++str;
225 } else {
226 if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) {
227 return tests::ATOI_ERROR_CODE_TOO_MUCH_WS;
228 }
229
230 return ATOI_ERROR_CODE;
231 }
232 }
233
234 37 bool const negative{ *str == '-' };
235
236
2/4
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 37 times.
37 if (*str == '+' || *str == '-') {
237 ++str;
238
239 if (!is_digit(*str)) {
240 if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) {
241 return tests::ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN;
242 }
243
244 return ATOI_ERROR_CODE;
245 }
246 }
247
248 37 bool preceding_zero{false};
249
250 37 int32_t digits{};
251
252 // Skip preceding 0s
253
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 37 times.
2/2
✓ Decision 'true' taken 21 times.
✓ Decision 'false' taken 37 times.
58 while (*str == '0') {
254 21 ++str;
255 21 digits = 1;
256 21 preceding_zero = true;
257 }
258
259 37 constexpr int32_t MAX_DIV10 = INT32_MIN / 10; // -214748364
260
261 37 int32_t result{};
262
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 37 times.
2/2
✓ Decision 'true' taken 68 times.
✓ Decision 'false' taken 37 times.
105 while(*str != '\0') {
263
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 68 times.
68 if (!is_digit(*str)) {
264 // Ok finish here
265 if (digits) {
266 break;
267 }
268
269 if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) {
270 return tests::ATOI_ERROR_CODE_NO_DIGITS;
271 }
272
273 // ill-formed string
274 return ATOI_ERROR_CODE;
275 }
276
277 // Too many digits for 32-bit int
278
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 65 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 68 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 68 times.
68 if (digits >= (ATOI_MAX_INT32_DIGITS + (preceding_zero ? 1 : 0))) {
279 if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) {
280 return tests::ATOI_ERROR_CODE_TOO_MUCH_DIGITS;
281 }
282
283 return ATOI_ERROR_CODE;
284 }
285
286 68 int32_t const digit{ *str - '0' };
287
288 // Will overflow
289
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 68 times.
68 if (result < MAX_DIV10) {
290 if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) {
291 return tests::ATOI_ERROR_CODE_OVERFLOW_RANK;
292 }
293
294 return ATOI_ERROR_CODE;
295 }
296
297 68 result *= 10;
298
299 // Will overflow
300
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 68 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 68 times.
68 if (result < (INT32_MIN + digit + (negative ? 0 : 1))) {
301 if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) {
302 return tests::ATOI_ERROR_CODE_OVERFLOW_DIGIT;
303 }
304 return ATOI_ERROR_CODE;
305 }
306
307 68 result -= digit; // calculate in negatives to support INT32_MIN
308 68 ++str;
309 68 ++digits;
310 }
311
312
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 37 times.
37 if (!digits && !preceding_zero) {
313 if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) {
314 return tests::ATOI_ERROR_CODE_NO_DATA;
315 }
316
317 return ATOI_ERROR_CODE;
318 }
319
320 if constexpr (trailingCharsCheckMode != SafeAtoiTrailingCharCheckMode::allowAll) {
321 if constexpr (trailingCharsCheckMode == SafeAtoiTrailingCharCheckMode::allowOnlyWhitespace) {
322 while (*str != '\0' && is_whitespace(*str)) ++str;
323 if (*str != '\0') {
324 if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) {
325 return tests::ATOI_ERROR_CODE_TRAILING_NON_WS_CHARS;
326 }
327
328 return ATOI_ERROR_CODE;
329 }
330 } else if constexpr (trailingCharsCheckMode == SafeAtoiTrailingCharCheckMode::allowOnlyNull) {
331 if (*str != '\0') {
332 if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) {
333 return tests::ATOI_ERROR_CODE_TRAILING_CHARS;
334 }
335
336 return ATOI_ERROR_CODE;
337 }
338 }
339 }
340
341
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
37 return negative ? result : -result;
342 }
343
344 namespace tests {
345 static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>(nullptr) == ATOI_ERROR_CODE_BAD_STRING);
346 static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>("") == ATOI_ERROR_CODE_BAD_STRING);
347 static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>("a123") == ATOI_ERROR_CODE_NO_DIGITS);
348 static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>("-a123") == ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN);
349 static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>("- 123") == ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN);
350 static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>(" + 123") == ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN);
351 static_assert(safe_atoi<SafeAtoiErrorMode::Prod, SafeAtoiTrailingCharCheckMode::allowOnlyNull>("1234567") == 1234567);
352 static_assert(safe_atoi<SafeAtoiErrorMode::Prod, SafeAtoiTrailingCharCheckMode::allowOnlyNull>(" -1234567890") == -1234567890);
353 static_assert(safe_atoi<SafeAtoiErrorMode::Prod, SafeAtoiTrailingCharCheckMode::allowOnlyNull>(" +1234567890") == 1234567890);
354 static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>(" -1234567890 ") == ATOI_ERROR_CODE_TRAILING_CHARS);
355 static_assert(safe_atoi<SafeAtoiErrorMode::Prod, SafeAtoiTrailingCharCheckMode::allowOnlyWhitespace>(" -1234567890 ") == -1234567890);
356 static_assert(safe_atoi<SafeAtoiErrorMode::Test>("+ 42") == ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN);
357 static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowAll>(" ") == ATOI_ERROR_CODE_TOO_MUCH_WS);
358
359 // Too many leading whitespaces
360 static_assert(safe_atoi<SafeAtoiErrorMode::Test>(" 123") == ATOI_ERROR_CODE_TOO_MUCH_WS);
361
362 // No digits after just whitespaces
363 static_assert(safe_atoi<SafeAtoiErrorMode::Test>(" ") == ATOI_ERROR_CODE_NO_DATA);
364
365 // Only a sign, no digits
366 static_assert(safe_atoi<SafeAtoiErrorMode::Test>("+") == ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN);
367 static_assert(safe_atoi<SafeAtoiErrorMode::Test>("-") == ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN);
368
369 // Too many digits (11)
370 static_assert(safe_atoi<SafeAtoiErrorMode::Test>("12345678901") == ATOI_ERROR_CODE_TOO_MUCH_DIGITS);
371 static_assert(safe_atoi<SafeAtoiErrorMode::Test>("111111111111111") == ATOI_ERROR_CODE_TOO_MUCH_DIGITS);
372
373 // INT32_MAX is not representable in negative space (this will overflow)
374 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("2147483647") == 2147483647);
375 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("00000000000000000002147483647") == 2147483647);
376 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("+00000000000000000002147483647") == 2147483647);
377 static_assert(safe_atoi<SafeAtoiErrorMode::Test>("2147483648") == ATOI_ERROR_CODE_OVERFLOW_DIGIT);
378 static_assert(safe_atoi<SafeAtoiErrorMode::Test>("2147483657") == ATOI_ERROR_CODE_OVERFLOW_RANK);
379 static_assert(safe_atoi<SafeAtoiErrorMode::Test>("99999999999999999") == ATOI_ERROR_CODE_OVERFLOW_RANK);
380
381 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("-2147483648") == -2147483648);
382 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("-00000000000000000002147483648") == -2147483648);
383 static_assert(safe_atoi<SafeAtoiErrorMode::Test>("-2147483649") == ATOI_ERROR_CODE_OVERFLOW_DIGIT);
384 static_assert(safe_atoi<SafeAtoiErrorMode::Test>("-2147483658") == ATOI_ERROR_CODE_OVERFLOW_RANK);
385 static_assert(safe_atoi<SafeAtoiErrorMode::Test>("-99999999999999999") == ATOI_ERROR_CODE_OVERFLOW_RANK);
386
387 // Accept +0 and -0
388 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("+0") == 0);
389 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("-0") == 0);
390 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("0") == 0);
391 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>(" 0 ") == 0);
392 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>(" 0") == 0);
393 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("0 ") == 0);
394
395 static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyWhitespace>("123abc") == ATOI_ERROR_CODE_TRAILING_NON_WS_CHARS);
396 static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>("123abc") == ATOI_ERROR_CODE_TRAILING_CHARS);
397 static_assert(safe_atoi<SafeAtoiErrorMode::Prod, SafeAtoiTrailingCharCheckMode::allowAll>("123abc") == 123);
398
399 // Max allowed whitespace (5) - should succeed
400 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>(" 42") == 42);
401 // 6 ws - fail
402 static_assert(safe_atoi<SafeAtoiErrorMode::Prod>(" 42") == stringutil::ATOI_ERROR_CODE);
403 }
404
405 }
406