dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 100.0% 163 / 0 / 163
Functions: 100.0% 11 / 0 / 11
Branches: 97.6% 80 / 4 / 86

src/command/parse.c
Line Branch Exec Source
1 #include <stdbool.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include "parse.h"
5 #include "trace.h"
6 #include "util/ascii.h"
7 #include "util/debug.h"
8 #include "util/str-util.h"
9 #include "util/string.h"
10 #include "util/strtonum.h"
11 #include "util/unicode.h"
12 #include "util/xmalloc.h"
13 #include "util/xstring.h"
14
15 1640 static size_t parse_sq(const char *cmd, size_t len, String *buf)
16 {
17 1640 size_t pos = 0;
18 1640 StringView inner = get_delim(cmd, &pos, len, '\'');
19 1640 string_append_buf(buf, inner.data, inner.length);
20 1640 return pos;
21 }
22
23 6 static size_t unicode_escape(const char *str, size_t count, String *buf)
24 {
25 // Note: `u` doesn't need to be initialized here, but `gcc -Og`
26 // gives a spurious -Wmaybe-uninitialized warning if it's not
27 6 unsigned int u = 0;
28 6 static_assert(sizeof(u) >= 4);
29 6 size_t n = buf_parse_hex_uint(str, count, &u);
30
3/4
✓ Branch 3 → 4 taken 4 times.
✓ Branch 3 → 6 taken 2 times.
✓ Branch 4 → 5 taken 4 times.
✗ Branch 4 → 6 not taken.
6 if (likely(n > 0 && u_is_unicode(u))) {
31 4 string_append_codepoint(buf, u);
32 }
33 6 return n;
34 }
35
36 44 static size_t hex_escape(const char *str, size_t count, String *buf)
37 {
38 44 unsigned int x = 0;
39 44 size_t n = buf_parse_hex_uint(str, count, &x);
40
2/2
✓ Branch 3 → 4 taken 39 times.
✓ Branch 3 → 5 taken 5 times.
44 if (likely(n == 2)) {
41 39 string_append_byte(buf, x);
42 }
43 44 return n;
44 }
45
46 692 static size_t parse_dq(const char *cmd, size_t len, String *buf)
47 {
48 692 size_t pos = 0;
49
2/2
✓ Branch 24 → 3 taken 4746 times.
✓ Branch 24 → 25 taken 2 times.
4748 while (pos < len) {
50 4746 unsigned char ch = cmd[pos++];
51
2/2
✓ Branch 3 → 4 taken 4056 times.
✓ Branch 3 → 25 taken 690 times.
4746 if (ch == '"') {
52 break;
53 }
54
2/2
✓ Branch 4 → 5 taken 707 times.
✓ Branch 4 → 22 taken 3349 times.
4056 if (ch == '\\' && pos < len) {
55 707 ch = cmd[pos++];
56
13/13
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 1 time.
✓ Branch 5 → 8 taken 1 time.
✓ Branch 5 → 9 taken 3 times.
✓ Branch 5 → 10 taken 401 times.
✓ Branch 5 → 11 taken 9 times.
✓ Branch 5 → 12 taken 126 times.
✓ Branch 5 → 13 taken 1 time.
✓ Branch 5 → 14 taken 44 times.
✓ Branch 5 → 16 taken 4 times.
✓ Branch 5 → 18 taken 2 times.
✓ Branch 5 → 20 taken 5 times.
✓ Branch 5 → 22 taken 109 times.
707 switch (ch) {
57 1 case 'a': ch = '\a'; break;
58 1 case 'b': ch = '\b'; break;
59 1 case 'e': ch = '\033'; break;
60 3 case 'f': ch = '\f'; break;
61 401 case 'n': ch = '\n'; break;
62 9 case 'r': ch = '\r'; break;
63 126 case 't': ch = '\t'; break;
64 1 case 'v': ch = '\v'; break;
65 case '\\':
66 case '"':
67 break;
68 44 case 'x':
69 44 pos += hex_escape(cmd + pos, MIN(2, len - pos), buf);
70 44 continue;
71 4 case 'u':
72 4 pos += unicode_escape(cmd + pos, MIN(4, len - pos), buf);
73 4 continue;
74 2 case 'U':
75 2 pos += unicode_escape(cmd + pos, MIN(8, len - pos), buf);
76 2 continue;
77 5 default:
78 5 string_append_byte(buf, '\\');
79 5 break;
80 }
81 }
82 4006 string_append_byte(buf, ch);
83 }
84
85 692 return pos;
86 }
87
88 27 static size_t expand_var (
89 const CommandRunner *runner,
90 const char *cmd,
91 size_t len,
92 String *buf
93 ) {
94
4/4
✓ Branch 2 → 3 taken 26 times.
✓ Branch 2 → 9 taken 1 time.
✓ Branch 3 → 4 taken 25 times.
✓ Branch 3 → 9 taken 1 time.
27 if (!runner->expand_variable || len == 0) {
95 return len;
96 }
97
98 25 char *name = xstrcut(cmd, len);
99 25 char *value = runner->expand_variable(runner->e, name);
100 25 free(name);
101
2/2
✓ Branch 6 → 7 taken 16 times.
✓ Branch 6 → 9 taken 9 times.
25 if (value) {
102 16 string_append_cstring(buf, value);
103 16 free(value);
104 }
105
106 return len;
107 }
108
109 // Handles bracketed variables, e.g. ${varname}
110 3 static size_t parse_bracketed_var (
111 const CommandRunner *runner,
112 const char *cmd,
113 size_t len,
114 String *buf
115 ) {
116 3 const char *end = memchr(cmd + 1, '}', len - 1);
117
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 5 taken 2 times.
3 if (!end) {
118 1 LOG_WARNING("no end delimiter for bracketed variable: $%.*s", (int)len, cmd);
119 1 return len; // Consume without appending
120 }
121
122 2 size_t var_len = (size_t)(end - cmd) - 1;
123 2 TRACE_CMD("expanding variable: ${%.*s}", (int)var_len, cmd + 1);
124 2 return expand_var(runner, cmd + 1, var_len, buf) + STRLEN("{}");
125 }
126
127 31 static size_t parse_var (
128 const CommandRunner *runner,
129 const char *cmd,
130 size_t len,
131 String *buf
132 ) {
133
1/2
✓ Branch 2 → 3 taken 31 times.
✗ Branch 2 → 4 not taken.
31 char ch = len ? cmd[0] : 0;
134
2/2
✓ Branch 4 → 5 taken 6 times.
✓ Branch 4 → 7 taken 25 times.
31 if (!is_alpha_or_underscore(ch)) {
135 6 bool bracketed = (ch == '{');
136
2/2
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 9 taken 3 times.
6 return bracketed ? parse_bracketed_var(runner, cmd, len, buf) : 0;
137 }
138
139 25 AsciiCharType type_mask = ASCII_ALNUM | ASCII_UNDERSCORE;
140 25 size_t var_len = ascii_type_prefix_length(cmd, len, type_mask);
141 25 TRACE_CMD("expanding variable: $%.*s", (int)var_len, cmd);
142 25 return expand_var(runner, cmd, var_len, buf);
143 }
144
145 // Parse a single dterc(5) argument from `cmd`, stopping when an unquoted
146 // whitespace or semicolon character is found or when all `len` bytes have
147 // been processed without encountering such a character. Escape sequences
148 // and $variables are expanded during processing and the fully expanded
149 // result is returned as a malloc'd string.
150 33525 char *parse_command_arg(const CommandRunner *runner, const char *cmd, size_t len)
151 {
152 33525 const StringView *home = runner->home_dir;
153 33525 bool expand_ts = (runner->flags & CMDRUNNER_EXPAND_TILDE_SLASH);
154
4/4
✓ Branch 2 → 3 taken 32593 times.
✓ Branch 2 → 6 taken 932 times.
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 32590 times.
33525 bool tilde_slash = expand_ts && len >= 2 && mem_equal(cmd, "~/", 2);
155 33525 String buf = string_new(len + 1 + (tilde_slash ? home->length : 0));
156 33525 size_t pos = 0;
157
158
2/2
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 21 taken 33522 times.
33525 if (tilde_slash) {
159 3 string_append_strview(&buf, *home);
160 3 pos += 1; // Skip past '~' and leave '/' to be handled below
161 }
162
163
2/2
✓ Branch 24 → 9 taken 184577 times.
✓ Branch 24 → 25 taken 33519 times.
218096 while (pos < len) {
164 184577 char ch = cmd[pos++];
165
6/6
✓ Branch 9 → 10 taken 5 times.
✓ Branch 9 → 11 taken 1640 times.
✓ Branch 9 → 13 taken 692 times.
✓ Branch 9 → 15 taken 31 times.
✓ Branch 9 → 17 taken 239 times.
✓ Branch 9 → 20 taken 181970 times.
184577 switch (ch) {
166 5 case '\t':
167 case '\n':
168 case '\r':
169 case ' ':
170 case ';':
171 5 goto end;
172 1640 case '\'':
173 1640 pos += parse_sq(cmd + pos, len - pos, &buf);
174 1640 break;
175 692 case '"':
176 692 pos += parse_dq(cmd + pos, len - pos, &buf);
177 692 break;
178 31 case '$':
179 31 pos += parse_var(runner, cmd + pos, len - pos, &buf);
180 31 break;
181 239 case '\\':
182
2/2
✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 19 taken 238 times.
239 if (unlikely(pos == len)) {
183 1 goto end;
184 }
185 238 ch = cmd[pos++];
186 // Fallthrough
187 182208 default:
188 182208 string_append_byte(&buf, ch);
189 182208 break;
190 }
191 }
192
193 33519 end:
194 33525 return string_steal_cstring(&buf);
195 }
196
197 33475 size_t find_end(const char *cmd, size_t pos, CommandParseError *err)
198 {
199 218060 while (1) {
200
5/5
✓ Branch 5 → 5 taken 182052 times.
✓ Branch 5 → 6 taken 1630 times.
✓ Branch 5 → 11 taken 681 times.
✓ Branch 5 → 20 taken 236 times.
✓ Branch 5 → 23 taken 33461 times.
218060 switch (cmd[pos++]) {
201 28265 case '\'':
202 54900 while (1) {
203
2/2
✓ Branch 6 → 7 taken 1625 times.
✓ Branch 6 → 8 taken 26640 times.
28265 if (cmd[pos] == '\'') {
204 1625 pos++;
205 1625 break;
206 }
207
2/2
✓ Branch 8 → 9 taken 5 times.
✓ Branch 8 → 10 taken 26635 times.
26640 if (unlikely(cmd[pos] == '\0')) {
208 5 *err = CMDERR_UNCLOSED_SQUOTE;
209 5 return 0;
210 }
211 26635 pos++;
212 }
213 1625 break;
214 1362 case '"':
215 4762 while (1) {
216
2/2
✓ Branch 12 → 13 taken 674 times.
✓ Branch 12 → 14 taken 4088 times.
4762 if (cmd[pos] == '"') {
217 674 pos++;
218 674 break;
219 }
220
2/2
✓ Branch 14 → 15 taken 5 times.
✓ Branch 14 → 16 taken 4083 times.
4088 if (unlikely(cmd[pos] == '\0')) {
221 5 *err = CMDERR_UNCLOSED_DQUOTE;
222 5 return 0;
223 }
224
2/2
✓ Branch 16 → 12 taken 3400 times.
✓ Branch 16 → 17 taken 683 times.
4083 if (cmd[pos++] == '\\') {
225
2/2
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 19 taken 681 times.
683 if (unlikely(cmd[pos] == '\0')) {
226 2 *err = CMDERR_UNEXPECTED_EOF;
227 2 return 0;
228 }
229 681 pos++;
230 }
231 }
232 674 break;
233 236 case '\\':
234
2/2
✓ Branch 20 → 21 taken 2 times.
✓ Branch 20 → 22 taken 234 times.
236 if (unlikely(cmd[pos] == '\0')) {
235 2 *err = CMDERR_UNEXPECTED_EOF;
236 2 return 0;
237 }
238 234 pos++;
239 234 break;
240 33461 case '\0':
241 case '\t':
242 case '\n':
243 case '\r':
244 case ' ':
245 case ';':
246 33461 *err = CMDERR_NONE;
247 33461 return pos - 1;
248 }
249 }
250
251 BUG("Unexpected break of outer loop");
252 }
253
254 // Note: `array` must be freed, regardless of the return value
255 12120 CommandParseError parse_commands (
256 const CommandRunner *runner,
257 PointerArray *array,
258 const char *cmd
259 ) {
260 12120 for (size_t pos = 0; true; ) {
261
2/2
✓ Branch 5 → 3 taken 22650 times.
✓ Branch 5 → 6 taken 45463 times.
68113 while (ascii_isspace(cmd[pos])) {
262 22650 pos++;
263 }
264
265
2/2
✓ Branch 6 → 7 taken 33357 times.
✓ Branch 6 → 16 taken 12106 times.
45463 if (cmd[pos] == '\0') {
266 break;
267 }
268
269
2/2
✓ Branch 7 → 8 taken 105 times.
✓ Branch 7 → 10 taken 33252 times.
33357 if (cmd[pos] == ';') {
270 105 ptr_array_append(array, NULL);
271 105 pos++;
272 105 continue;
273 }
274
275 33252 CommandParseError err;
276 33252 size_t end = find_end(cmd, pos, &err);
277
2/2
✓ Branch 11 → 12 taken 14 times.
✓ Branch 11 → 13 taken 33238 times.
33252 if (err != CMDERR_NONE) {
278 14 return err;
279 }
280
281 33238 ptr_array_append(array, parse_command_arg(runner, cmd + pos, end - pos));
282 33238 pos = end;
283 }
284
285 12106 ptr_array_append(array, NULL);
286 12106 return CMDERR_NONE;
287 }
288
289 6 const char *command_parse_error_to_string(CommandParseError err)
290 {
291 6 static const char error_strings[][16] = {
292 [CMDERR_UNCLOSED_SQUOTE] = "unclosed '",
293 [CMDERR_UNCLOSED_DQUOTE] = "unclosed \"",
294 [CMDERR_UNEXPECTED_EOF] = "unexpected EOF",
295 };
296
297 6 BUG_ON(err <= CMDERR_NONE);
298 6 BUG_ON(err >= ARRAYLEN(error_strings));
299 6 return error_strings[err];
300 }
301