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% 12 / 0 / 12
Branches: 96.6% 84 / 4 / 91

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