dte test coverage


Directory: ./
File: src/command/parse.c
Date: 2024-12-21 16:03:22
Exec Total Coverage
Lines: 149 149 100.0%
Functions: 9 9 100.0%
Branches: 82 86 95.3%

Line Branch Exec Source
1 #include <stdbool.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include "parse.h"
5 #include "util/ascii.h"
6 #include "util/debug.h"
7 #include "util/string.h"
8 #include "util/strtonum.h"
9 #include "util/unicode.h"
10 #include "util/xmalloc.h"
11
12 1090 static size_t parse_sq(const char *cmd, size_t len, String *buf)
13 {
14 1090 const char *end = memchr(cmd, '\'', len);
15
2/2
✓ Branch 0 taken 1089 times.
✓ Branch 1 taken 1 times.
1090 size_t pos = end ? (size_t)(end - cmd) : len;
16 1090 string_append_buf(buf, cmd, pos);
17
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1089 times.
1090 return pos + (end ? 1 : 0);
18 }
19
20 6 static size_t unicode_escape(const char *str, size_t count, 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(str, count, &u);
27
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 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(const char *str, size_t count, String *buf)
34 {
35 44 unsigned int x = 0;
36 44 size_t n = buf_parse_hex_uint(str, count, &x);
37
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 5 times.
44 if (likely(n == 2)) {
38 39 string_append_byte(buf, x);
39 }
40 44 return n;
41 }
42
43 613 static size_t parse_dq(const char *cmd, size_t len, String *buf)
44 {
45 613 size_t pos = 0;
46
2/2
✓ Branch 0 taken 2536 times.
✓ Branch 1 taken 2 times.
2538 while (pos < len) {
47 2536 unsigned char ch = cmd[pos++];
48
2/2
✓ Branch 0 taken 1925 times.
✓ Branch 1 taken 611 times.
2536 if (ch == '"') {
49 break;
50 }
51
2/2
✓ Branch 0 taken 618 times.
✓ Branch 1 taken 1307 times.
1925 if (ch == '\\' && pos < len) {
52 618 ch = cmd[pos++];
53
13/13
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 334 times.
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 114 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 44 times.
✓ Branch 9 taken 4 times.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 3 times.
✓ Branch 12 taken 101 times.
618 switch (ch) {
54 1 case 'a': ch = '\a'; break;
55 1 case 'b': ch = '\b'; break;
56 1 case 'e': ch = '\033'; break;
57 3 case 'f': ch = '\f'; break;
58 334 case 'n': ch = '\n'; break;
59 9 case 'r': ch = '\r'; break;
60 114 case 't': ch = '\t'; break;
61 1 case 'v': ch = '\v'; break;
62 case '\\':
63 case '"':
64 break;
65 44 case 'x':
66 44 pos += hex_escape(cmd + pos, MIN(2, len - pos), buf);
67 44 continue;
68 4 case 'u':
69 4 pos += unicode_escape(cmd + pos, MIN(4, len - pos), buf);
70 4 continue;
71 2 case 'U':
72 2 pos += unicode_escape(cmd + pos, MIN(8, len - pos), buf);
73 2 continue;
74 3 default:
75 3 string_append_byte(buf, '\\');
76 3 break;
77 }
78 }
79 1875 string_append_byte(buf, ch);
80 }
81
82 613 return pos;
83 }
84
85 22 static size_t parse_var(const CommandRunner *runner, const char *cmd, size_t len, String *buf)
86 {
87
3/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 3 times.
22 if (len == 0 || !is_alpha_or_underscore(cmd[0])) {
88 return 0;
89 }
90
91 size_t n = 1;
92
4/4
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 105 times.
✓ Branch 3 taken 11 times.
124 while (n < len && is_alnum_or_underscore(cmd[n])) {
93 105 n++;
94 }
95
96
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 if (runner->expand_variable) {
97 19 char *name = xstrcut(cmd, n);
98 19 char *value = runner->expand_variable(runner->e, name);
99 19 free(name);
100
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 9 times.
19 if (value) {
101 10 string_append_cstring(buf, value);
102 10 free(value);
103 }
104 }
105
106 return n;
107 }
108
109 // Parse a single dterc(5) argument from `cmd`, stopping when an unquoted
110 // whitespace or semicolon character is found or when all `len` bytes have
111 // been processed without encountering such a character. Escape sequences
112 // and $variables are expanded during processing and the fully expanded
113 // result is returned as a malloc'd string.
114 25945 char *parse_command_arg(const CommandRunner *runner, const char *cmd, size_t len)
115 {
116 25945 String buf;
117 25945 size_t pos = 0;
118
119
7/8
✓ Branch 0 taken 25912 times.
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 25240 times.
✓ Branch 3 taken 672 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 25237 times.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
25945 if (runner->expand_tilde_slash && len >= 2 && cmd[0] == '~' && cmd[1] == '/') {
120 3 buf = string_new(len + runner->home_dir->length);
121 3 string_append_strview(&buf, runner->home_dir);
122 3 string_append_byte(&buf, '/');
123 3 pos += 2;
124 } else {
125 25942 buf = string_new(len);
126 }
127
128
2/2
✓ Branch 0 taken 142359 times.
✓ Branch 1 taken 25939 times.
168298 while (pos < len) {
129 142359 char ch = cmd[pos++];
130
6/6
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1090 times.
✓ Branch 2 taken 613 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 226 times.
✓ Branch 5 taken 140403 times.
142359 switch (ch) {
131 5 case '\t':
132 case '\n':
133 case '\r':
134 case ' ':
135 case ';':
136 5 goto end;
137 1090 case '\'':
138 1090 pos += parse_sq(cmd + pos, len - pos, &buf);
139 1090 break;
140 613 case '"':
141 613 pos += parse_dq(cmd + pos, len - pos, &buf);
142 613 break;
143 22 case '$':
144 22 pos += parse_var(runner, cmd + pos, len - pos, &buf);
145 22 break;
146 226 case '\\':
147
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 225 times.
226 if (unlikely(pos == len)) {
148 1 goto end;
149 }
150 225 ch = cmd[pos++];
151 // Fallthrough
152 140628 default:
153 140628 string_append_byte(&buf, ch);
154 140628 break;
155 }
156 }
157
158 25939 end:
159 25945 return string_steal_cstring(&buf);
160 }
161
162 25892 size_t find_end(const char *cmd, size_t pos, CommandParseError *err)
163 {
164 168234 while (1) {
165
5/5
✓ Branch 0 taken 140448 times.
✓ Branch 1 taken 1079 times.
✓ Branch 2 taken 600 times.
✓ Branch 3 taken 226 times.
✓ Branch 4 taken 25881 times.
168234 switch (cmd[pos++]) {
166 18811 case '\'':
167 36543 while (1) {
168
2/2
✓ Branch 0 taken 1075 times.
✓ Branch 1 taken 17736 times.
18811 if (cmd[pos] == '\'') {
169 1075 pos++;
170 1075 break;
171 }
172
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 17732 times.
17736 if (unlikely(cmd[pos] == '\0')) {
173 4 *err = CMDERR_UNCLOSED_SQUOTE;
174 4 return 0;
175 }
176 17732 pos++;
177 }
178 1075 break;
179 1192 case '"':
180 2550 while (1) {
181
2/2
✓ Branch 0 taken 595 times.
✓ Branch 1 taken 1955 times.
2550 if (cmd[pos] == '"') {
182 595 pos++;
183 595 break;
184 }
185
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1951 times.
1955 if (unlikely(cmd[pos] == '\0')) {
186 4 *err = CMDERR_UNCLOSED_DQUOTE;
187 4 return 0;
188 }
189
2/2
✓ Branch 0 taken 1358 times.
✓ Branch 1 taken 593 times.
1951 if (cmd[pos++] == '\\') {
190
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 592 times.
593 if (unlikely(cmd[pos] == '\0')) {
191 1 *err = CMDERR_UNEXPECTED_EOF;
192 1 return 0;
193 }
194 592 pos++;
195 }
196 }
197 595 break;
198 226 case '\\':
199
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 224 times.
226 if (unlikely(cmd[pos] == '\0')) {
200 2 *err = CMDERR_UNEXPECTED_EOF;
201 2 return 0;
202 }
203 224 pos++;
204 224 break;
205 25881 case '\0':
206 case '\t':
207 case '\n':
208 case '\r':
209 case ' ':
210 case ';':
211 25881 *err = CMDERR_NONE;
212 25881 return pos - 1;
213 }
214 }
215
216 BUG("Unexpected break of outer loop");
217 }
218
219 // Note: `array` must be freed, regardless of the return value
220 9232 CommandParseError parse_commands(const CommandRunner *runner, PointerArray *array, const char *cmd)
221 {
222 9232 for (size_t pos = 0; true; ) {
223
2/2
✓ Branch 0 taken 17778 times.
✓ Branch 1 taken 34935 times.
52713 while (ascii_isspace(cmd[pos])) {
224 17778 pos++;
225 }
226
227
2/2
✓ Branch 0 taken 25714 times.
✓ Branch 1 taken 9221 times.
34935 if (cmd[pos] == '\0') {
228 break;
229 }
230
231
2/2
✓ Branch 0 taken 38 times.
✓ Branch 1 taken 25676 times.
25714 if (cmd[pos] == ';') {
232 38 ptr_array_append(array, NULL);
233 38 pos++;
234 38 continue;
235 }
236
237 25676 CommandParseError err;
238 25676 size_t end = find_end(cmd, pos, &err);
239
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 25665 times.
25676 if (err != CMDERR_NONE) {
240 11 return err;
241 }
242
243 25665 ptr_array_append(array, parse_command_arg(runner, cmd + pos, end - pos));
244 25665 pos = end;
245 }
246
247 9221 ptr_array_append(array, NULL);
248 9221 return CMDERR_NONE;
249 }
250
251 6 const char *command_parse_error_to_string(CommandParseError err)
252 {
253 6 static const char error_strings[][16] = {
254 [CMDERR_UNCLOSED_SQUOTE] = "unclosed '",
255 [CMDERR_UNCLOSED_DQUOTE] = "unclosed \"",
256 [CMDERR_UNEXPECTED_EOF] = "unexpected EOF",
257 };
258
259 6 BUG_ON(err <= CMDERR_NONE);
260 6 BUG_ON(err >= ARRAYLEN(error_strings));
261 6 return error_strings[err];
262 }
263