LCOV - code coverage report
Current view: top level - src/command - parse.c Hit Total Coverage
Test: dte Lines: 157 157 100.0 %
Date: 2023-03-25 22:04:15

          Line data    Source code
       1             : #include <stdlib.h>
       2             : #include <string.h>
       3             : #include "parse.h"
       4             : #include "util/ascii.h"
       5             : #include "util/debug.h"
       6             : #include "util/string.h"
       7             : #include "util/strtonum.h"
       8             : #include "util/unicode.h"
       9             : #include "util/xmalloc.h"
      10             : 
      11         397 : static size_t parse_sq(const char *cmd, size_t len, String *buf)
      12             : {
      13         397 :     const char *end = memchr(cmd, '\'', len);
      14         397 :     size_t pos = end ? (size_t)(end - cmd) : len;
      15         397 :     string_append_buf(buf, cmd, pos);
      16         397 :     return pos + (end ? 1 : 0);
      17             : }
      18             : 
      19           6 : static size_t unicode_escape(const char *str, size_t count, String *buf)
      20             : {
      21             :     // Note: `u` doesn't need to be initialized here, but `gcc -Og`
      22             :     // gives a spurious -Wmaybe-uninitialized warning if it's not
      23           6 :     unsigned int u = 0;
      24           6 :     static_assert(sizeof(u) >= 4);
      25           6 :     size_t n = buf_parse_hex_uint(str, count, &u);
      26           6 :     if (likely(n > 0 && u_is_unicode(u))) {
      27           4 :         string_append_codepoint(buf, u);
      28             :     }
      29           6 :     return n;
      30             : }
      31             : 
      32          44 : static size_t hex_escape(const char *str, size_t count, String *buf)
      33             : {
      34          44 :     unsigned int x = 0;
      35          44 :     size_t n = buf_parse_hex_uint(str, count, &x);
      36          44 :     if (likely(n == 2)) {
      37          39 :         string_append_byte(buf, x);
      38             :     }
      39          44 :     return n;
      40             : }
      41             : 
      42         557 : static size_t parse_dq(const char *cmd, size_t len, String *buf)
      43             : {
      44         557 :     size_t pos = 0;
      45        1990 :     while (pos < len) {
      46        1988 :         unsigned char ch = cmd[pos++];
      47        1988 :         if (ch == '"') {
      48             :             break;
      49             :         }
      50        1433 :         if (ch == '\\' && pos < len) {
      51         553 :             ch = cmd[pos++];
      52         553 :             switch (ch) {
      53           1 :             case 'a': ch = '\a'; break;
      54           1 :             case 'b': ch = '\b'; break;
      55           1 :             case 'e': ch = '\033'; break;
      56           3 :             case 'f': ch = '\f'; break;
      57         297 :             case 'n': ch = '\n'; break;
      58           7 :             case 'r': ch = '\r'; break;
      59          90 :             case 't': ch = '\t'; break;
      60           1 :             case 'v': ch = '\v'; break;
      61             :             case '\\':
      62             :             case '"':
      63             :                 break;
      64          44 :             case 'x':
      65          44 :                 pos += hex_escape(cmd + pos, MIN(2, len - pos), buf);
      66          44 :                 continue;
      67           4 :             case 'u':
      68           4 :                 pos += unicode_escape(cmd + pos, MIN(4, len - pos), buf);
      69           4 :                 continue;
      70           2 :             case 'U':
      71           2 :                 pos += unicode_escape(cmd + pos, MIN(8, len - pos), buf);
      72           2 :                 continue;
      73           3 :             default:
      74           3 :                 string_append_byte(buf, '\\');
      75           3 :                 break;
      76             :             }
      77         880 :         }
      78        1383 :         string_append_byte(buf, ch);
      79             :     }
      80             : 
      81         557 :     return pos;
      82             : }
      83             : 
      84          21 : static size_t parse_var(const CommandRunner *runner, const char *cmd, size_t len, String *buf)
      85             : {
      86          21 :     if (len == 0 || !is_alpha_or_underscore(cmd[0])) {
      87             :         return 0;
      88             :     }
      89             : 
      90             :     size_t n = 1;
      91         121 :     while (n < len && is_alnum_or_underscore(cmd[n])) {
      92         103 :         n++;
      93             :     }
      94             : 
      95          18 :     const CommandSet *cmds = runner->cmds;
      96          18 :     void *ud = runner->userdata;
      97          18 :     char *name = xstrcut(cmd, n);
      98          18 :     char *value;
      99          18 :     if (cmds->expand_variable && cmds->expand_variable(name, &value, ud)) {
     100           9 :         if (value) {
     101           3 :             string_append_cstring(buf, value);
     102           3 :             free(value);
     103             :         }
     104           9 :     } else if (cmds->expand_env_vars) {
     105           9 :         const char *val = getenv(name);
     106           9 :         if (val) {
     107           8 :             string_append_cstring(buf, val);
     108             :         }
     109             :     }
     110             : 
     111          18 :     free(name);
     112          18 :     return n;
     113             : }
     114             : 
     115       17377 : char *parse_command_arg(const CommandRunner *runner, const char *cmd, size_t len, bool tilde)
     116             : {
     117       17377 :     String buf;
     118       17377 :     size_t pos = 0;
     119             : 
     120       17377 :     if (tilde && len >= 2 && cmd[0] == '~' && cmd[1] == '/') {
     121           3 :         buf = string_new(len + runner->home_dir->length);
     122           3 :         string_append_strview(&buf, runner->home_dir);
     123           3 :         string_append_byte(&buf, '/');
     124           3 :         pos += 2;
     125             :     } else {
     126       17374 :         buf = string_new(len);
     127             :     }
     128             : 
     129      116919 :     while (pos < len) {
     130       99548 :         char ch = cmd[pos++];
     131       99548 :         switch (ch) {
     132           5 :         case '\t':
     133             :         case '\n':
     134             :         case '\r':
     135             :         case ' ':
     136             :         case ';':
     137           5 :             goto end;
     138         397 :         case '\'':
     139         397 :             pos += parse_sq(cmd + pos, len - pos, &buf);
     140         397 :             break;
     141         557 :         case '"':
     142         557 :             pos += parse_dq(cmd + pos, len - pos, &buf);
     143         557 :             break;
     144          21 :         case '$':
     145          21 :             pos += parse_var(runner, cmd + pos, len - pos, &buf);
     146          21 :             break;
     147         159 :         case '\\':
     148         159 :             if (unlikely(pos == len)) {
     149           1 :                 goto end;
     150             :             }
     151         158 :             ch = cmd[pos++];
     152             :             // Fallthrough
     153       98567 :         default:
     154       98567 :             string_append_byte(&buf, ch);
     155       98567 :             break;
     156             :         }
     157             :     }
     158             : 
     159       17371 : end:
     160       17377 :     return string_steal_cstring(&buf);
     161             : }
     162             : 
     163       17328 : size_t find_end(const char *cmd, size_t pos, CommandParseError *err)
     164             : {
     165      116871 :     while (1) {
     166      116871 :         switch (cmd[pos++]) {
     167        5574 :         case '\'':
     168       10760 :             while (1) {
     169        5574 :                 if (cmd[pos] == '\'') {
     170         385 :                     pos++;
     171         385 :                     break;
     172             :                 }
     173        5189 :                 if (unlikely(cmd[pos] == '\0')) {
     174           3 :                     *err = CMDERR_UNCLOSED_SQUOTE;
     175           3 :                     return 0;
     176             :                 }
     177        5186 :                 pos++;
     178             :             }
     179         385 :             break;
     180        1069 :         case '"':
     181        1994 :             while (1) {
     182        1994 :                 if (cmd[pos] == '"') {
     183         539 :                     pos++;
     184         539 :                     break;
     185             :                 }
     186        1455 :                 if (unlikely(cmd[pos] == '\0')) {
     187           2 :                     *err = CMDERR_UNCLOSED_DQUOTE;
     188           2 :                     return 0;
     189             :                 }
     190        1453 :                 if (cmd[pos++] == '\\') {
     191         528 :                     if (unlikely(cmd[pos] == '\0')) {
     192           1 :                         *err = CMDERR_UNEXPECTED_EOF;
     193           1 :                         return 0;
     194             :                     }
     195         527 :                     pos++;
     196             :                 }
     197             :             }
     198         539 :             break;
     199         158 :         case '\\':
     200         158 :             if (unlikely(cmd[pos] == '\0')) {
     201           1 :                 *err = CMDERR_UNEXPECTED_EOF;
     202           1 :                 return 0;
     203             :             }
     204         157 :             pos++;
     205         157 :             break;
     206       17321 :         case '\0':
     207             :         case '\t':
     208             :         case '\n':
     209             :         case '\r':
     210             :         case ' ':
     211             :         case ';':
     212       17321 :             *err = CMDERR_NONE;
     213       17321 :             return pos - 1;
     214             :         }
     215             :     }
     216             : 
     217             :     BUG("Unexpected break of outer loop");
     218             : }
     219             : 
     220             : // Note: `array` must be freed, regardless of the return value
     221        5932 : CommandParseError parse_commands(const CommandRunner *runner, PointerArray *array, const char *cmd)
     222             : {
     223        5932 :     for (size_t pos = 0; true; ) {
     224       35351 :         while (ascii_isspace(cmd[pos])) {
     225       12260 :             pos++;
     226             :         }
     227             : 
     228       23091 :         if (cmd[pos] == '\0') {
     229             :             break;
     230             :         }
     231             : 
     232       17166 :         if (cmd[pos] == ';') {
     233          32 :             ptr_array_append(array, NULL);
     234          32 :             pos++;
     235          32 :             continue;
     236             :         }
     237             : 
     238       17134 :         CommandParseError err;
     239       17134 :         size_t end = find_end(cmd, pos, &err);
     240       17134 :         if (err != CMDERR_NONE) {
     241           7 :             return err;
     242             :         }
     243             : 
     244       17127 :         ptr_array_append(array, parse_command_arg(runner, cmd + pos, end - pos, true));
     245       17127 :         pos = end;
     246             :     }
     247             : 
     248        5925 :     ptr_array_append(array, NULL);
     249        5925 :     return CMDERR_NONE;
     250             : }
     251             : 
     252           5 : const char *command_parse_error_to_string(CommandParseError err)
     253             : {
     254           5 :     static const char error_strings[][16] = {
     255             :         [CMDERR_UNCLOSED_SQUOTE] = "unclosed '",
     256             :         [CMDERR_UNCLOSED_DQUOTE] = "unclosed \"",
     257             :         [CMDERR_UNEXPECTED_EOF] = "unexpected EOF",
     258             :     };
     259             : 
     260           5 :     BUG_ON(err <= CMDERR_NONE);
     261           5 :     BUG_ON(err >= ARRAYLEN(error_strings));
     262           5 :     return error_strings[err];
     263             : }

Generated by: LCOV version 1.14