dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 100.0% 162 / 0 / 162
Functions: 100.0% 12 / 0 / 12
Branches: 96.5% 82 / 4 / 89

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/strtonum.h"
10 #include "util/unicode.h"
11 #include "util/xmalloc.h"
12
13 1668 static size_t parse_sq(StringView cmd, String *buf)
14 {
15 1668 size_t pos = 0;
16 1668 string_append_strview(buf, get_delim(cmd.data, &pos, cmd.length, '\''));
17 1668 return pos;
18 }
19
20 6 static size_t unicode_escape(StringView hexstr, String *buf)
21 {
22 // Note: `u` doesn't need to be initialized here, but `gcc -Og`
23 // gives a spurious -Wmaybe-uninitialized warning if it's not
24 6 unsigned int u = 0;
25 6 static_assert(sizeof(u) >= 4);
26 6 size_t n = buf_parse_hex_uint(hexstr, &u);
27
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))) {
28 4 string_append_codepoint(buf, u);
29 }
30 6 return n;
31 }
32
33 44 static size_t hex_escape(StringView hexstr, String *buf)
34 {
35 44 unsigned int x = 0;
36 44 size_t n = buf_parse_hex_uint(hexstr, &x);
37
2/2
✓ Branch 3 → 4 taken 39 times.
✓ Branch 3 → 5 taken 5 times.
44 if (likely(n == 2)) {
38 39 string_append_byte(buf, x);
39 }
40 44 return n;
41 }
42
43 50 static StringView slice(StringView cmd, size_t pos, size_t maxlen)
44 {
45 50 return string_view(cmd.data + pos, MIN(maxlen, cmd.length - pos));
46 }
47
48 721 static size_t parse_dq(StringView cmd, String *buf)
49 {
50 721 size_t pos = 0;
51
2/2
✓ Branch 25 → 3 taken 5413 times.
✓ Branch 25 → 26 taken 2 times.
5415 while (pos < cmd.length) {
52 5413 unsigned char ch = cmd.data[pos++];
53
2/2
✓ Branch 3 → 4 taken 4694 times.
✓ Branch 3 → 26 taken 719 times.
5413 if (ch == '"') {
54 break;
55 }
56
3/4
✓ Branch 4 → 5 taken 725 times.
✓ Branch 4 → 23 taken 3969 times.
✓ Branch 5 → 6 taken 725 times.
✗ Branch 5 → 23 not taken.
4694 if (ch == '\\' && pos < cmd.length) {
57 725 ch = cmd.data[pos++];
58
12/12
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 1 time.
✓ Branch 6 → 9 taken 1 time.
✓ Branch 6 → 10 taken 3 times.
✓ Branch 6 → 11 taken 416 times.
✓ Branch 6 → 12 taken 9 times.
✓ Branch 6 → 13 taken 125 times.
✓ Branch 6 → 14 taken 1 time.
✓ Branch 6 → 15 taken 44 times.
✓ Branch 6 → 17 taken 6 times.
✓ Branch 6 → 21 taken 5 times.
✓ Branch 6 → 23 taken 113 times.
725 switch (ch) {
59 1 case 'a': ch = '\a'; break;
60 1 case 'b': ch = '\b'; break;
61 1 case 'e': ch = '\033'; break;
62 3 case 'f': ch = '\f'; break;
63 416 case 'n': ch = '\n'; break;
64 9 case 'r': ch = '\r'; break;
65 125 case 't': ch = '\t'; break;
66 1 case 'v': ch = '\v'; break;
67 case '\\':
68 case '"':
69 break;
70 44 case 'x':
71 44 pos += hex_escape(slice(cmd, pos, 2), buf);
72 44 continue;
73 6 case 'u':
74 case 'U':
75
2/2
✓ Branch 17 → 18 taken 4 times.
✓ Branch 17 → 19 taken 2 times.
10 pos += unicode_escape(slice(cmd, pos, ch == 'U' ? 8 : 4), buf);
76 6 continue;
77 5 default:
78 5 string_append_byte(buf, '\\');
79 5 break;
80 }
81 }
82 4644 string_append_byte(buf, ch);
83 }
84
85 721 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 StringView cmd,
130 String *buf
131 ) {
132
1/2
✓ Branch 2 → 3 taken 31 times.
✗ Branch 2 → 4 not taken.
31 char ch = cmd.length ? cmd.data[0] : 0;
133
2/2
✓ Branch 4 → 5 taken 6 times.
✓ Branch 4 → 7 taken 25 times.
31 if (!is_alpha_or_underscore(ch)) {
134 6 bool bracketed = (ch == '{');
135
2/2
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 9 taken 3 times.
6 return bracketed ? parse_bracketed_var(runner, cmd.data, cmd.length, buf) : 0;
136 }
137
138 25 AsciiCharType type_mask = ASCII_ALNUM | ASCII_UNDERSCORE;
139 25 size_t var_len = ascii_type_prefix_length(cmd.data, cmd.length, type_mask);
140 25 TRACE_CMD("expanding variable: $%.*s", (int)var_len, cmd.data);
141 25 return expand_var(runner, cmd.data, var_len, buf);
142 }
143
144 // Parse a single dterc(5) argument from `cmd`, stopping when an unquoted
145 // whitespace or semicolon character is found or when all `len` bytes have
146 // been processed without encountering such a character. Escape sequences
147 // and $variables are expanded during processing and the fully expanded
148 // result is returned as a malloc'd string.
149 33790 String parse_command_arg(const CommandRunner *runner, StringView cmd)
150 {
151 33790 const StringView *home = runner->home_dir;
152 33790 bool expand_ts = (runner->flags & CMDRUNNER_EXPAND_TILDE_SLASH);
153
4/4
✓ Branch 2 → 3 taken 33702 times.
✓ Branch 2 → 6 taken 88 times.
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 33699 times.
33790 bool tilde_slash = expand_ts && strview_has_prefix(cmd, "~/");
154 33790 String buf = string_new(cmd.length + 1 + (tilde_slash ? home->length : 0));
155 33790 size_t pos = 0;
156
157
2/2
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 25 taken 33787 times.
33790 if (tilde_slash) {
158 3 string_append_strview(&buf, *home);
159 3 pos += 1; // Skip past '~' and leave '/' to be handled below
160 }
161
162
2/2
✓ Branch 28 → 10 taken 185547 times.
✓ Branch 28 → 29 taken 33784 times.
219331 while (pos < cmd.length) {
163 185547 char ch = cmd.data[pos++];
164
6/6
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 12 taken 1668 times.
✓ Branch 10 → 15 taken 721 times.
✓ Branch 10 → 18 taken 31 times.
✓ Branch 10 → 21 taken 239 times.
✓ Branch 10 → 24 taken 182883 times.
185547 switch (ch) {
165 5 case '\t':
166 case '\n':
167 case '\r':
168 case ' ':
169 case ';':
170 5 goto end;
171 1668 case '\'':
172 1668 pos += parse_sq(strview_from_slice(cmd.data, pos, cmd.length), &buf);
173 1668 break;
174 721 case '"':
175 721 pos += parse_dq(strview_from_slice(cmd.data, pos, cmd.length), &buf);
176 721 break;
177 31 case '$':
178 31 pos += parse_var(runner, strview_from_slice(cmd.data, pos, cmd.length), &buf);
179 31 break;
180 239 case '\\':
181
2/2
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 23 taken 238 times.
239 if (unlikely(pos == cmd.length)) {
182 1 goto end;
183 }
184 238 ch = cmd.data[pos++];
185 // Fallthrough
186 183121 default:
187 183121 string_append_byte(&buf, ch);
188 183121 break;
189 }
190 }
191
192 33784 end:
193 33790 return buf;
194 }
195
196 33739 size_t find_end(const char *cmd, size_t pos, CommandParseError *err)
197 {
198 219294 while (1) {
199
5/5
✓ Branch 5 → 5 taken 182965 times.
✓ Branch 5 → 6 taken 1658 times.
✓ Branch 5 → 11 taken 710 times.
✓ Branch 5 → 20 taken 236 times.
✓ Branch 5 → 23 taken 33725 times.
219294 switch (cmd[pos++]) {
200 28750 case '\'':
201 55842 while (1) {
202
2/2
✓ Branch 6 → 7 taken 1653 times.
✓ Branch 6 → 8 taken 27097 times.
28750 if (cmd[pos] == '\'') {
203 1653 pos++;
204 1653 break;
205 }
206
2/2
✓ Branch 8 → 9 taken 5 times.
✓ Branch 8 → 10 taken 27092 times.
27097 if (unlikely(cmd[pos] == '\0')) {
207 5 *err = CMDERR_UNCLOSED_SQUOTE;
208 5 return 0;
209 }
210 27092 pos++;
211 }
212 1653 break;
213 1409 case '"':
214 5429 while (1) {
215
2/2
✓ Branch 12 → 13 taken 703 times.
✓ Branch 12 → 14 taken 4726 times.
5429 if (cmd[pos] == '"') {
216 703 pos++;
217 703 break;
218 }
219
2/2
✓ Branch 14 → 15 taken 5 times.
✓ Branch 14 → 16 taken 4721 times.
4726 if (unlikely(cmd[pos] == '\0')) {
220 5 *err = CMDERR_UNCLOSED_DQUOTE;
221 5 return 0;
222 }
223
2/2
✓ Branch 16 → 12 taken 4020 times.
✓ Branch 16 → 17 taken 701 times.
4721 if (cmd[pos++] == '\\') {
224
2/2
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 19 taken 699 times.
701 if (unlikely(cmd[pos] == '\0')) {
225 2 *err = CMDERR_UNEXPECTED_EOF;
226 2 return 0;
227 }
228 699 pos++;
229 }
230 }
231 703 break;
232 236 case '\\':
233
2/2
✓ Branch 20 → 21 taken 2 times.
✓ Branch 20 → 22 taken 234 times.
236 if (unlikely(cmd[pos] == '\0')) {
234 2 *err = CMDERR_UNEXPECTED_EOF;
235 2 return 0;
236 }
237 234 pos++;
238 234 break;
239 33725 case '\0':
240 case '\t':
241 case '\n':
242 case '\r':
243 case ' ':
244 case ';':
245 33725 *err = CMDERR_NONE;
246 33725 return pos - 1;
247 }
248 }
249
250 BUG("Unexpected break of outer loop");
251 }
252
253 // Note: `array` must be freed, regardless of the return value
254 12209 CommandParseError parse_commands (
255 const CommandRunner *runner,
256 PointerArray *array,
257 const char *cmd
258 ) {
259 12209 for (size_t pos = 0; true; ) {
260
2/2
✓ Branch 5 → 3 taken 22829 times.
✓ Branch 5 → 6 taken 45812 times.
68641 while (ascii_isspace(cmd[pos])) {
261 22829 pos++;
262 }
263
264
2/2
✓ Branch 6 → 7 taken 33617 times.
✓ Branch 6 → 18 taken 12195 times.
45812 if (cmd[pos] == '\0') {
265 break;
266 }
267
268
2/2
✓ Branch 7 → 8 taken 105 times.
✓ Branch 7 → 10 taken 33512 times.
33617 if (cmd[pos] == ';') {
269 105 ptr_array_append(array, NULL);
270 105 pos++;
271 105 continue;
272 }
273
274 33512 CommandParseError err;
275 33512 size_t end = find_end(cmd, pos, &err);
276
2/2
✓ Branch 11 → 12 taken 14 times.
✓ Branch 11 → 13 taken 33498 times.
33512 if (err != CMDERR_NONE) {
277 14 return err;
278 }
279
280 33498 String arg = parse_command_arg(runner, strview_from_slice(cmd, pos, end));
281 33498 ptr_array_append(array, string_steal_cstring(&arg));
282 33498 pos = end;
283 }
284
285 12195 ptr_array_append(array, NULL);
286 12195 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