dte test coverage


Directory: ./
File: src/util/strtonum.c
Date: 2025-09-07 23:01:39
Exec Total Coverage
Lines: 129 129 100.0%
Functions: 15 15 100.0%
Branches: 85 93 91.4%

Line Branch Exec Source
1 #include <errno.h>
2 #include <string.h>
3 #include "strtonum.h"
4 #include "arith.h"
5 #include "ascii.h"
6 #include "debug.h"
7 #include "xmemrchr.h"
8 #include "xstring.h"
9
10 enum {
11 A = 0xA, B = 0xB, C = 0xC,
12 D = 0xD, E = 0xE, F = 0xF,
13 I = HEX_INVALID
14 };
15
16 // Indices are offset by 48 ('0'; 0x30)
17 const uint8_t hex_decode_table[64] = {
18 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, I, I, I, I, I, I, // 0x30 0123456789......
19 I, A, B, C, D, E, F, I, I, I, I, I, I, I, I, I, // 0x40 .ABCDEF.........
20 I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0x50 ................
21 I, A, B, C, D, E, F, I, I, I, I, I, I, I, I, I, // 0x60 .abcdef.........
22 };
23
24 UNITTEST {
25 static_assert((I & 0xF) == 0);
26 BUG_ON(hex_decode_table[sizeof(hex_decode_table) - 1] != I);
27 }
28
29 629 size_t buf_parse_uintmax(const char *str, size_t len, uintmax_t *valp)
30 {
31
4/4
✓ Branch 0 (2→3) taken 614 times.
✓ Branch 1 (2→12) taken 15 times.
✓ Branch 2 (3→4) taken 347 times.
✓ Branch 3 (3→12) taken 267 times.
629 if (unlikely(len == 0 || !ascii_isdigit(str[0]))) {
32 return 0;
33 }
34
35 347 uintmax_t val = str[0] - '0';
36 347 size_t i = 1;
37
38
4/4
✓ Branch 0 (9→10) taken 583 times.
✓ Branch 1 (9→11) taken 246 times.
✓ Branch 2 (10→5) taken 488 times.
✓ Branch 3 (10→11) taken 95 times.
829 while (i < len && ascii_isdigit(str[i])) {
39
2/2
✓ Branch 0 (6→7) taken 482 times.
✓ Branch 1 (6→12) taken 6 times.
488 if (unlikely(umax_multiply_overflows(val, 10, &val))) {
40 return 0;
41 }
42
1/2
✓ Branch 0 (8→9) taken 482 times.
✗ Branch 1 (8→12) not taken.
482 if (unlikely(umax_add_overflows(val, str[i++] - '0', &val))) {
43 return 0;
44 }
45 }
46
47 341 *valp = val;
48 341 return i;
49 }
50
51 31 size_t buf_parse_ulong(const char *str, size_t len, unsigned long *valp)
52 {
53 31 uintmax_t val;
54 31 size_t n = buf_parse_uintmax(str, len, &val);
55
2/2
✓ Branch 0 (3→4) taken 27 times.
✓ Branch 1 (3→5) taken 4 times.
31 if (n == 0 || val > ULONG_MAX) {
56 return 0;
57 }
58 27 *valp = (unsigned long)val;
59 27 return n;
60 }
61
62 2 size_t buf_parse_uint(const char *str, size_t len, unsigned int *valp)
63 {
64 2 uintmax_t val;
65 2 size_t n = buf_parse_uintmax(str, len, &val);
66
2/4
✓ Branch 0 (3→4) taken 2 times.
✗ Branch 1 (3→6) not taken.
✓ Branch 2 (4→5) taken 2 times.
✗ Branch 3 (4→6) not taken.
2 if (n == 0 || val > UINT_MAX) {
67 return 0;
68 }
69 2 *valp = (unsigned int)val;
70 2 return n;
71 }
72
73 40 size_t buf_parse_size(const char *str, size_t len, size_t *valp)
74 {
75 40 uintmax_t val;
76 40 size_t n = buf_parse_uintmax(str, len, &val);
77
2/2
✓ Branch 0 (3→4) taken 31 times.
✓ Branch 1 (3→5) taken 9 times.
40 if (n == 0 || val > SIZE_MAX) {
78 return 0;
79 }
80 31 *valp = (size_t)val;
81 31 return n;
82 }
83
84 63 static size_t buf_parse_long(const char *str, size_t len, long *valp)
85 {
86
1/2
✓ Branch 0 (2→3) taken 63 times.
✗ Branch 1 (2→13) not taken.
63 if (unlikely(len == 0)) {
87 return 0;
88 }
89
90 63 bool negative = false;
91 63 size_t skipped = 0;
92
3/3
✓ Branch 0 (3→4) taken 8 times.
✓ Branch 1 (3→5) taken 9 times.
✓ Branch 2 (3→6) taken 46 times.
63 switch (str[0]) {
93 8 case '-':
94 8 negative = true;
95 // Fallthrough
96 17 case '+':
97 17 skipped = 1;
98 17 str++;
99 17 len--;
100 17 break;
101 }
102
103 63 uintmax_t val;
104 63 size_t n = buf_parse_uintmax(str, len, &val);
105
3/4
✓ Branch 0 (7→8) taken 58 times.
✓ Branch 1 (7→13) taken 5 times.
✓ Branch 2 (8→9) taken 58 times.
✗ Branch 3 (8→13) not taken.
63 if (n == 0 || val > LONG_MAX) {
106 return 0;
107 }
108
109
2/2
✓ Branch 0 (9→10) taken 8 times.
✓ Branch 1 (9→11) taken 50 times.
58 if (negative) {
110 8 *valp = -((long)val);
111 } else {
112 50 *valp = (long)val;
113 }
114
115 58 return n + skipped;
116 }
117
118 64 bool str_to_int(const char *str, int *valp)
119 {
120 64 const size_t len = strlen(str);
121
2/2
✓ Branch 0 (2→3) taken 63 times.
✓ Branch 1 (2→8) taken 1 times.
64 if (unlikely(len == 0)) {
122 return false;
123 }
124 63 long val;
125 63 const size_t n = buf_parse_long(str, len, &val);
126
4/6
✓ Branch 0 (4→5) taken 57 times.
✓ Branch 1 (4→8) taken 6 times.
✓ Branch 2 (5→6) taken 57 times.
✗ Branch 3 (5→8) not taken.
✓ Branch 4 (6→7) taken 57 times.
✗ Branch 5 (6→8) not taken.
63 if (n != len || val < INT_MIN || val > INT_MAX) {
127 return false;
128 }
129 57 *valp = (int)val;
130 57 return true;
131 }
132
133 160 static bool str_to_uintmax(const char *str, uintmax_t *valp)
134 {
135 160 const size_t len = strlen(str);
136
2/2
✓ Branch 0 (2→3) taken 156 times.
✓ Branch 1 (2→6) taken 4 times.
160 if (unlikely(len == 0)) {
137 return false;
138 }
139 156 uintmax_t val;
140 156 const size_t n = buf_parse_uintmax(str, len, &val);
141
2/2
✓ Branch 0 (4→5) taken 136 times.
✓ Branch 1 (4→6) taken 20 times.
156 if (n != len) {
142 return false;
143 }
144 136 *valp = val;
145 136 return true;
146 }
147
148 66 bool str_to_uint(const char *str, unsigned int *valp)
149 {
150 66 uintmax_t x;
151
3/4
✓ Branch 0 (3→4) taken 57 times.
✓ Branch 1 (3→6) taken 9 times.
✓ Branch 2 (4→5) taken 57 times.
✗ Branch 3 (4→6) not taken.
66 if (!str_to_uintmax(str, &x) || x > UINT_MAX) {
152 return false;
153 }
154 57 *valp = (unsigned int)x;
155 57 return true;
156 }
157
158 6 bool str_to_ulong(const char *str, unsigned long *valp)
159 {
160 6 uintmax_t x;
161
2/2
✓ Branch 0 (3→4) taken 5 times.
✓ Branch 1 (3→5) taken 1 times.
6 if (!str_to_uintmax(str, &x) || x > ULONG_MAX) {
162 return false;
163 }
164 5 *valp = (unsigned long)x;
165 5 return true;
166 }
167
168 88 bool str_to_size(const char *str, size_t *valp)
169 {
170 88 uintmax_t x;
171
2/2
✓ Branch 0 (3→4) taken 74 times.
✓ Branch 1 (3→5) taken 14 times.
88 if (!str_to_uintmax(str, &x) || x > SIZE_MAX) {
172 return false;
173 }
174 74 *valp = (size_t)x;
175 74 return true;
176 }
177
178 // Parse line and column number from line[,col] or line[:col]
179 38 bool str_to_xfilepos(const char *str, size_t *linep, size_t *colp)
180 {
181 38 size_t len = strlen(str);
182 38 size_t line, col;
183 38 size_t i = buf_parse_size(str, len, &line);
184
4/4
✓ Branch 0 (3→4) taken 30 times.
✓ Branch 1 (3→14) taken 8 times.
✓ Branch 2 (4→5) taken 25 times.
✓ Branch 3 (4→14) taken 5 times.
38 if (i == 0 || line < 1) {
185 return false;
186 }
187
188 // If an explicit column wasn't specified in `str`, set *colp to 0
189 // (which is NOT a valid column number). Callers should be prepared
190 // to check this and substitute it for something more appropriate,
191 // or otherwise use str_to_filepos() instead.
192
2/2
✓ Branch 0 (5→6) taken 5 times.
✓ Branch 1 (5→7) taken 20 times.
25 if (i == len) {
193 5 col = 0;
194 5 goto out;
195 }
196
197 20 char c = str[i];
198
6/6
✓ Branch 0 (7→8) taken 17 times.
✓ Branch 1 (7→11) taken 3 times.
✓ Branch 2 (9→10) taken 8 times.
✓ Branch 3 (9→11) taken 9 times.
✓ Branch 4 (10→11) taken 2 times.
✓ Branch 5 (10→12) taken 6 times.
20 if ((c != ':' && c != ',') || !str_to_size(str + i + 1, &col) || col < 1) {
199 14 return false;
200 }
201
202 6 out:
203 11 *linep = line;
204 11 *colp = col;
205 11 return true;
206 }
207
208 // This is much like str_to_xfilepos(), except *colp is set to 1 if no
209 // explicit column number is specified in `str`. This is a convenience
210 // to callers that want a valid column number (when omitted) and don't
211 // need to do anything different for e.g. "32" vs. "32:1".
212 32 bool str_to_filepos(const char *str, size_t *linep, size_t *colp)
213 {
214 32 size_t col;
215 32 bool r = str_to_xfilepos(str, linep, &col);
216
2/2
✓ Branch 0 (3→4) taken 8 times.
✓ Branch 1 (3→5) taken 24 times.
32 if (r) {
217 8 *colp = col + !col;
218 }
219 32 return r;
220 }
221
222 // Parse file:line[:col] format (e.g. "dir/filename.ext:12:45") and return
223 // the `file` part if successful, or a zero-length StringView on failure.
224 // Note that `:line` isn't optional.
225 20 StringView parse_file_line_col(const char *str, size_t *linep, size_t *colp)
226 {
227 20 const char *a = strrchr(str, ':');
228
2/2
✓ Branch 0 (2→3) taken 18 times.
✓ Branch 1 (2→4) taken 2 times.
20 const char *b = a ? xmemrchr(str, ':', a - str) : NULL;
229
2/2
✓ Branch 0 (3→4) taken 4 times.
✓ Branch 1 (3→5) taken 14 times.
20 const char *c = b ? b : a;
230
4/4
✓ Branch 0 (5→6) taken 15 times.
✓ Branch 1 (5→9) taken 5 times.
✓ Branch 2 (7→8) taken 4 times.
✓ Branch 3 (7→9) taken 11 times.
20 bool ok = (c && c != str && str_to_filepos(c + 1, linep, colp));
231 4 return string_view(str, ok ? c - str : 0);
232 }
233
234 // Return the number of decimal digits in `x`
235 13 size_t size_str_width(size_t x)
236 {
237 13 size_t width = 0;
238 29 do {
239 29 x /= 10;
240 29 width++;
241
2/2
✓ Branch 0 (3→4) taken 16 times.
✓ Branch 1 (3→5) taken 13 times.
29 } while (x);
242 13 return width;
243 }
244
245 // Convert a string of decimal digits with an optional KiB/MiB/GiB/etc.
246 // suffix to an intmax_t value representing the number of bytes, or a
247 // negated <errno.h> value in the case of errors
248 35 intmax_t parse_filesize(const char *str)
249 {
250 35 uintmax_t x;
251 35 size_t ndigits = buf_parse_uintmax(str, strlen(str), &x);
252
4/4
✓ Branch 0 (3→4) taken 33 times.
✓ Branch 1 (3→5) taken 2 times.
✓ Branch 2 (4→5) taken 1 times.
✓ Branch 3 (4→8) taken 32 times.
35 if (unlikely(ndigits == 0 || x > INTMAX_MAX)) {
253
2/2
✓ Branch 0 (5→6) taken 2 times.
✓ Branch 1 (5→7) taken 1 times.
5 return ascii_isdigit(str[0]) ? -EOVERFLOW : -EINVAL;
254 }
255
256 32 const char *suffix = str + ndigits;
257 32 unsigned int shift;
258
8/8
✓ Branch 0 (8→9) taken 8 times.
✓ Branch 1 (8→10) taken 9 times.
✓ Branch 2 (8→11) taken 1 times.
✓ Branch 3 (8→12) taken 1 times.
✓ Branch 4 (8→13) taken 2 times.
✓ Branch 5 (8→14) taken 4 times.
✓ Branch 6 (8→15) taken 5 times.
✓ Branch 7 (8→18) taken 2 times.
32 switch (suffix[0]) {
259 case 'K': shift = 10; break;
260 8 case 'M': shift = 20; break;
261 9 case 'G': shift = 30; break;
262 1 case 'T': shift = 40; break;
263 1 case 'P': shift = 50; break;
264 2 case 'E': shift = 60; break;
265 4 case '\0': return x;
266 default: return -EINVAL;
267 }
268
269
2/2
✓ Branch 0 (15→16) taken 13 times.
✓ Branch 1 (15→18) taken 13 times.
26 if (unlikely(!streq(suffix + 1, "iB"))) {
270 return -EINVAL;
271 }
272
273 13 uintmax_t val = x << shift;
274
2/2
✓ Branch 0 (16→17) taken 11 times.
✓ Branch 1 (16→18) taken 2 times.
13 if (unlikely(val >> shift != x || x > INTMAX_MAX)) {
275 return -EOVERFLOW;
276 }
277
278 11 return val;
279 }
280