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 #include "util/xstring.h"
13
14 1647 static size_t parse_sq(StringView cmd, String *buf)
15 {
16 1647 size_t pos = 0;
17 1647 string_append_strview(buf, get_delim(cmd.data, &pos, cmd.length, '\''));
18 1647 return pos;
19 }
20
21 6 static size_t unicode_escape(StringView hexstr, String *buf)
22 {
23 // Note: `u` doesn't need to be initialized here, but `gcc -Og`
24 // gives a spurious -Wmaybe-uninitialized warning if it's not
25 6 unsigned int u = 0;
26 6 static_assert(sizeof(u) >= 4);
27 6 size_t n = buf_parse_hex_uint(hexstr, &u);
28
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))) {
29 4 string_append_codepoint(buf, u);
30 }
31 6 return n;
32 }
33
34 44 static size_t hex_escape(StringView hexstr, String *buf)
35 {
36 44 unsigned int x = 0;
37 44 size_t n = buf_parse_hex_uint(hexstr, &x);
38
2/2
✓ Branch 3 → 4 taken 39 times.
✓ Branch 3 → 5 taken 5 times.
44 if (likely(n == 2)) {
39 39 string_append_byte(buf, x);
40 }
41 44 return n;
42 }
43
44 50 static StringView slice(StringView cmd, size_t pos, size_t maxlen)
45 {
46 50 return string_view(cmd.data + pos, MIN(maxlen, cmd.length - pos));
47 }
48
49 693 static size_t parse_dq(StringView cmd, String *buf)
50 {
51 693 size_t pos = 0;
52
2/2
✓ Branch 25 → 3 taken 3765 times.
✓ Branch 25 → 26 taken 2 times.
3767 while (pos < cmd.length) {
53 3765 unsigned char ch = cmd.data[pos++];
54
2/2
✓ Branch 3 → 4 taken 3074 times.
✓ Branch 3 → 26 taken 691 times.
3765 if (ch == '"') {
55 break;
56 }
57
3/4
✓ Branch 4 → 5 taken 686 times.
✓ Branch 4 → 23 taken 2388 times.
✓ Branch 5 → 6 taken 686 times.
✗ Branch 5 → 23 not taken.
3074 if (ch == '\\' && pos < cmd.length) {
58 686 ch = cmd.data[pos++];
59
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 381 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.
686 switch (ch) {
60 1 case 'a': ch = '\a'; break;
61 1 case 'b': ch = '\b'; break;
62 1 case 'e': ch = '\033'; break;
63 3 case 'f': ch = '\f'; break;
64 381 case 'n': ch = '\n'; break;
65 9 case 'r': ch = '\r'; break;
66 125 case 't': ch = '\t'; break;
67 1 case 'v': ch = '\v'; break;
68 case '\\':
69 case '"':
70 break;
71 44 case 'x':
72 44 pos += hex_escape(slice(cmd, pos, 2), buf);
73 44 continue;
74 6 case 'u':
75 case 'U':
76
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);
77 6 continue;
78 5 default:
79 5 string_append_byte(buf, '\\');
80 5 break;
81 }
82 }
83 3024 string_append_byte(buf, ch);
84 }
85
86 693 return pos;
87 }
88
89 27 static size_t expand_var (
90 const CommandRunner *runner,
91 const char *cmd,
92 size_t len,
93 String *buf
94 ) {
95
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) {
96 return len;
97 }
98
99 25 char *name = xstrcut(cmd, len);
100 25 char *value = runner->expand_variable(runner->e, name);
101 25 free(name);
102
2/2
✓ Branch 6 → 7 taken 16 times.
✓ Branch 6 → 9 taken 9 times.
25 if (value) {
103 16 string_append_cstring(buf, value);
104 16 free(value);
105 }
106
107 return len;
108 }
109
110 // Handles bracketed variables, e.g. ${varname}
111 3 static size_t parse_bracketed_var (
112 const CommandRunner *runner,
113 const char *cmd,
114 size_t len,
115 String *buf
116 ) {
117 3 const char *end = memchr(cmd + 1, '}', len - 1);
118
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 5 taken 2 times.
3 if (!end) {
119 1 LOG_WARNING("no end delimiter for bracketed variable: $%.*s", (int)len, cmd);
120 1 return len; // Consume without appending
121 }
122
123 2 size_t var_len = (size_t)(end - cmd) - 1;
124 2 TRACE_CMD("expanding variable: ${%.*s}", (int)var_len, cmd + 1);
125 2 return expand_var(runner, cmd + 1, var_len, buf) + STRLEN("{}");
126 }
127
128 31 static size_t parse_var (
129 const CommandRunner *runner,
130 StringView cmd,
131 String *buf
132 ) {
133
1/2
✓ Branch 2 → 3 taken 31 times.
✗ Branch 2 → 4 not taken.
31 char ch = cmd.length ? cmd.data[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.data, cmd.length, buf) : 0;
137 }
138
139 25 AsciiCharType type_mask = ASCII_ALNUM | ASCII_UNDERSCORE;
140 25 size_t var_len = ascii_type_prefix_length(cmd.data, cmd.length, type_mask);
141 25 TRACE_CMD("expanding variable: $%.*s", (int)var_len, cmd.data);
142 25 return expand_var(runner, cmd.data, 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 33620 String parse_command_arg(const CommandRunner *runner, StringView cmd)
151 {
152 33620 const StringView *home = runner->home_dir;
153 33620 bool expand_ts = (runner->flags & CMDRUNNER_EXPAND_TILDE_SLASH);
154
4/4
✓ Branch 2 → 3 taken 33532 times.
✓ Branch 2 → 6 taken 88 times.
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 33529 times.
33620 bool tilde_slash = expand_ts && strview_has_prefix(cmd, "~/");
155 33620 String buf = string_new(cmd.length + 1 + (tilde_slash ? home->length : 0));
156 33620 size_t pos = 0;
157
158
2/2
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 25 taken 33617 times.
33620 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 28 → 10 taken 185108 times.
✓ Branch 28 → 29 taken 33614 times.
218722 while (pos < cmd.length) {
164 185108 char ch = cmd.data[pos++];
165
6/6
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 12 taken 1647 times.
✓ Branch 10 → 15 taken 693 times.
✓ Branch 10 → 18 taken 31 times.
✓ Branch 10 → 21 taken 239 times.
✓ Branch 10 → 24 taken 182493 times.
185108 switch (ch) {
166 5 case '\t':
167 case '\n':
168 case '\r':
169 case ' ':
170 case ';':
171 5 goto end;
172 1647 case '\'':
173 1647 pos += parse_sq(strview_from_slice(cmd.data, pos, cmd.length), &buf);
174 1647 break;
175 693 case '"':
176 693 pos += parse_dq(strview_from_slice(cmd.data, pos, cmd.length), &buf);
177 693 break;
178 31 case '$':
179 31 pos += parse_var(runner, strview_from_slice(cmd.data, pos, cmd.length), &buf);
180 31 break;
181 239 case '\\':
182
2/2
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 23 taken 238 times.
239 if (unlikely(pos == cmd.length)) {
183 1 goto end;
184 }
185 238 ch = cmd.data[pos++];
186 // Fallthrough
187 182731 default:
188 182731 string_append_byte(&buf, ch);
189 182731 break;
190 }
191 }
192
193 33614 end:
194 33620 return buf;
195 }
196
197 33569 size_t find_end(const char *cmd, size_t pos, CommandParseError *err)
198 {
199 218685 while (1) {
200
5/5
✓ Branch 5 → 5 taken 182575 times.
✓ Branch 5 → 6 taken 1637 times.
✓ Branch 5 → 11 taken 682 times.
✓ Branch 5 → 20 taken 236 times.
✓ Branch 5 → 23 taken 33555 times.
218685 switch (cmd[pos++]) {
201 28286 case '\'':
202 54935 while (1) {
203
2/2
✓ Branch 6 → 7 taken 1632 times.
✓ Branch 6 → 8 taken 26654 times.
28286 if (cmd[pos] == '\'') {
204 1632 pos++;
205 1632 break;
206 }
207
2/2
✓ Branch 8 → 9 taken 5 times.
✓ Branch 8 → 10 taken 26649 times.
26654 if (unlikely(cmd[pos] == '\0')) {
208 5 *err = CMDERR_UNCLOSED_SQUOTE;
209 5 return 0;
210 }
211 26649 pos++;
212 }
213 1632 break;
214 1342 case '"':
215 3781 while (1) {
216
2/2
✓ Branch 12 → 13 taken 675 times.
✓ Branch 12 → 14 taken 3106 times.
3781 if (cmd[pos] == '"') {
217 675 pos++;
218 675 break;
219 }
220
2/2
✓ Branch 14 → 15 taken 5 times.
✓ Branch 14 → 16 taken 3101 times.
3106 if (unlikely(cmd[pos] == '\0')) {
221 5 *err = CMDERR_UNCLOSED_DQUOTE;
222 5 return 0;
223 }
224
2/2
✓ Branch 16 → 12 taken 2439 times.
✓ Branch 16 → 17 taken 662 times.
3101 if (cmd[pos++] == '\\') {
225
2/2
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 19 taken 660 times.
662 if (unlikely(cmd[pos] == '\0')) {
226 2 *err = CMDERR_UNEXPECTED_EOF;
227 2 return 0;
228 }
229 660 pos++;
230 }
231 }
232 675 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 33555 case '\0':
241 case '\t':
242 case '\n':
243 case '\r':
244 case ' ':
245 case ';':
246 33555 *err = CMDERR_NONE;
247 33555 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 12160 CommandParseError parse_commands (
256 const CommandRunner *runner,
257 PointerArray *array,
258 const char *cmd
259 ) {
260 12160 for (size_t pos = 0; true; ) {
261
2/2
✓ Branch 5 → 3 taken 22708 times.
✓ Branch 5 → 6 taken 45593 times.
68301 while (ascii_isspace(cmd[pos])) {
262 22708 pos++;
263 }
264
265
2/2
✓ Branch 6 → 7 taken 33447 times.
✓ Branch 6 → 18 taken 12146 times.
45593 if (cmd[pos] == '\0') {
266 break;
267 }
268
269
2/2
✓ Branch 7 → 8 taken 105 times.
✓ Branch 7 → 10 taken 33342 times.
33447 if (cmd[pos] == ';') {
270 105 ptr_array_append(array, NULL);
271 105 pos++;
272 105 continue;
273 }
274
275 33342 CommandParseError err;
276 33342 size_t end = find_end(cmd, pos, &err);
277
2/2
✓ Branch 11 → 12 taken 14 times.
✓ Branch 11 → 13 taken 33328 times.
33342 if (err != CMDERR_NONE) {
278 14 return err;
279 }
280
281 33328 String arg = parse_command_arg(runner, strview_from_slice(cmd, pos, end));
282 33328 ptr_array_append(array, string_steal_cstring(&arg));
283 33328 pos = end;
284 }
285
286 12146 ptr_array_append(array, NULL);
287 12146 return CMDERR_NONE;
288 }
289
290 6 const char *command_parse_error_to_string(CommandParseError err)
291 {
292 6 static const char error_strings[][16] = {
293 [CMDERR_UNCLOSED_SQUOTE] = "unclosed '",
294 [CMDERR_UNCLOSED_DQUOTE] = "unclosed \"",
295 [CMDERR_UNEXPECTED_EOF] = "unexpected EOF",
296 };
297
298 6 BUG_ON(err <= CMDERR_NONE);
299 6 BUG_ON(err >= ARRAYLEN(error_strings));
300 6 return error_strings[err];
301 }
302