src/command/run.c
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "run.h" | ||
| 2 | #include "args.h" | ||
| 3 | #include "parse.h" | ||
| 4 | #include "change.h" | ||
| 5 | #include "options.h" | ||
| 6 | #include "util/debug.h" | ||
| 7 | #include "util/ptr-array.h" | ||
| 8 | #include "util/xmalloc.h" | ||
| 9 | |||
| 10 | enum { | ||
| 11 | MAX_RECURSION_DEPTH = 16, // Maximum number of `alias` expansions | ||
| 12 | }; | ||
| 13 | |||
| 14 | static bool run_commands(CommandRunner *runner, const PointerArray *array); | ||
| 15 | |||
| 16 | // Recursion is limited by MAX_RECURSION_DEPTH | ||
| 17 | // NOLINTNEXTLINE(misc-no-recursion) | ||
| 18 | 8049 | static bool run_command(CommandRunner *runner, char **av) | |
| 19 | { | ||
| 20 | 8049 | const CommandSet *cmds = runner->cmds; | |
| 21 | 8049 | struct EditorState *e = runner->e; | |
| 22 | 8049 | ErrorBuffer *ebuf = runner->ebuf; | |
| 23 | 8049 | BUG_ON(!cmds); | |
| 24 | 8049 | BUG_ON(!e); | |
| 25 | 8049 | BUG_ON(!ebuf); | |
| 26 | |||
| 27 | 8049 | const Command *cmd = cmds->lookup(av[0]); | |
| 28 |
2/2✓ Branch 9 → 10 taken 35 times.
✓ Branch 9 → 31 taken 8014 times.
|
8049 | if (!cmd) { |
| 29 | 35 | const char *name = av[0]; | |
| 30 |
2/2✓ Branch 10 → 11 taken 2 times.
✓ Branch 10 → 12 taken 33 times.
|
35 | if (!runner->lookup_alias) { |
| 31 | 2 | return error_msg_for_cmd(ebuf, NULL, "No such command: %s", name); | |
| 32 | } | ||
| 33 | |||
| 34 | 33 | const char *alias_value = runner->lookup_alias(e, name); | |
| 35 |
2/2✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 15 taken 31 times.
|
33 | if (unlikely(!alias_value)) { |
| 36 | 2 | return error_msg_for_cmd(ebuf, NULL, "No such command or alias: %s", name); | |
| 37 | } | ||
| 38 | |||
| 39 | 31 | PointerArray array = ptr_array_new(0); | |
| 40 | 31 | CommandParseError err = parse_commands(runner, &array, alias_value); | |
| 41 |
2/2✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 21 taken 30 times.
|
31 | if (unlikely(err != CMDERR_NONE)) { |
| 42 | 1 | const char *err_msg = command_parse_error_to_string(err); | |
| 43 | 1 | ptr_array_free(&array); | |
| 44 | 1 | return error_msg_for_cmd(ebuf, NULL, "Parsing alias %s: %s", name, err_msg); | |
| 45 | } | ||
| 46 | |||
| 47 | // Remove NULL | ||
| 48 | 30 | array.count--; | |
| 49 | |||
| 50 |
2/2✓ Branch 25 → 22 taken 35 times.
✓ Branch 25 → 26 taken 30 times.
|
65 | for (size_t i = 1; av[i]; i++) { |
| 51 | 35 | ptr_array_append(&array, xstrdup(av[i])); | |
| 52 | } | ||
| 53 | 30 | ptr_array_append(&array, NULL); | |
| 54 | |||
| 55 | 30 | bool r = run_commands(runner, &array); | |
| 56 | 30 | ptr_array_free(&array); | |
| 57 | 30 | return r; | |
| 58 | } | ||
| 59 | |||
| 60 |
4/4✓ Branch 31 → 32 taken 7364 times.
✓ Branch 31 → 34 taken 650 times.
✓ Branch 32 → 33 taken 1 time.
✓ Branch 32 → 34 taken 7363 times.
|
8014 | if (unlikely(ebuf->sourcepos.filename && !(cmd->cmdopts & CMDOPT_ALLOW_IN_RC))) { |
| 61 | 1 | return error_msg_for_cmd(ebuf, NULL, "Command %s not allowed in config file", cmd->name); | |
| 62 | } | ||
| 63 | |||
| 64 | // Record command in macro buffer, if recording (this needs to be done | ||
| 65 | // before parse_args() mutates the array) | ||
| 66 |
3/4✓ Branch 34 → 35 taken 5 times.
✓ Branch 34 → 37 taken 8008 times.
✓ Branch 35 → 36 taken 5 times.
✗ Branch 35 → 37 not taken.
|
8013 | if ((runner->flags & CMDRUNNER_ALLOW_RECORDING) && runner->cmds->macro_record) { |
| 67 | 5 | runner->cmds->macro_record(e, cmd, av + 1); | |
| 68 | } | ||
| 69 | |||
| 70 | // By default change can't be merged with previous one. | ||
| 71 | // Any command can override this by calling begin_change() again. | ||
| 72 | 8013 | begin_change(CHANGE_MERGE_NONE); | |
| 73 | |||
| 74 | 8013 | CommandArgs a = cmdargs_new(av + 1); | |
| 75 |
4/4✓ Branch 39 → 40 taken 8006 times.
✓ Branch 39 → 43 taken 7 times.
✓ Branch 41 → 42 taken 137 times.
✓ Branch 41 → 43 taken 7869 times.
|
8013 | bool r = likely(parse_args(cmd, &a, ebuf)) && command_func_call(e, ebuf, cmd, &a); |
| 76 | |||
| 77 | 8013 | end_change(); | |
| 78 | 8013 | return r; | |
| 79 | } | ||
| 80 | |||
| 81 | // Recursion is limited by MAX_RECURSION_DEPTH | ||
| 82 | // NOLINTNEXTLINE(misc-no-recursion) | ||
| 83 | 9482 | static bool run_commands(CommandRunner *runner, const PointerArray *array) | |
| 84 | { | ||
| 85 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 9481 times.
|
9482 | if (unlikely(runner->recursion_count > MAX_RECURSION_DEPTH)) { |
| 86 | 1 | return error_msg_for_cmd(runner->ebuf, NULL, "alias recursion limit reached"); | |
| 87 | } | ||
| 88 | |||
| 89 | 9481 | bool stop_at_first_err = (runner->flags & CMDRUNNER_STOP_AT_FIRST_ERROR); | |
| 90 | 9481 | void **ptrs = array->ptrs; | |
| 91 | 9481 | size_t len = array->count; | |
| 92 | 9481 | size_t nfailed = 0; | |
| 93 | 9481 | BUG_ON(len == 0); | |
| 94 | 9481 | BUG_ON(ptrs[len - 1] != NULL); | |
| 95 | 9481 | runner->recursion_count++; | |
| 96 | |||
| 97 |
2/2✓ Branch 18 → 11 taken 9578 times.
✓ Branch 18 → 19 taken 9479 times.
|
19057 | for (size_t s = 0, e = 0; s < len; ) { |
| 98 | // Iterate over strings, until a terminating NULL is encountered | ||
| 99 | 37789 | while (ptrs[e]) { | |
| 100 | 28211 | e++; | |
| 101 | 37789 | BUG_ON(e >= len); | |
| 102 | } | ||
| 103 | |||
| 104 | // If the value of `e` (end) changed, there's a run of at least | ||
| 105 | // 1 string, which is a command followed by 0 or more arguments | ||
| 106 |
2/2✓ Branch 12 → 13 taken 8049 times.
✓ Branch 12 → 17 taken 1529 times.
|
9578 | if (e != s) { |
| 107 |
2/2✓ Branch 14 → 15 taken 167 times.
✓ Branch 14 → 17 taken 7882 times.
|
8049 | if (!run_command(runner, (char**)ptrs + s)) { |
| 108 | 167 | nfailed++; | |
| 109 |
2/2✓ Branch 15 → 16 taken 2 times.
✓ Branch 15 → 17 taken 165 times.
|
167 | if (stop_at_first_err) { |
| 110 | 2 | goto out; | |
| 111 | } | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | // Skip past the NULL, onto the next command (if any) | ||
| 116 | 9576 | s = ++e; | |
| 117 | } | ||
| 118 | |||
| 119 | 9479 | out: | |
| 120 | 9481 | runner->recursion_count--; | |
| 121 | 9481 | return (nfailed == 0); | |
| 122 | } | ||
| 123 | |||
| 124 | 9457 | bool handle_command(CommandRunner *runner, const char *cmd) | |
| 125 | { | ||
| 126 | 9457 | BUG_ON(runner->recursion_count != 0); | |
| 127 | 9457 | PointerArray array = ptr_array_new(0); | |
| 128 | 9457 | CommandParseError err = parse_commands(runner, &array, cmd); | |
| 129 | 9457 | bool r; | |
| 130 |
2/2✓ Branch 6 → 7 taken 9452 times.
✓ Branch 6 → 10 taken 5 times.
|
9457 | if (likely(err == CMDERR_NONE)) { |
| 131 | 9452 | r = run_commands(runner, &array); | |
| 132 | 9452 | BUG_ON(runner->recursion_count != 0); | |
| 133 | } else { | ||
| 134 | 5 | const char *str = command_parse_error_to_string(err); | |
| 135 | 5 | error_msg_for_cmd(runner->ebuf, NULL, "Command syntax error: %s", str); | |
| 136 | 5 | r = false; | |
| 137 | } | ||
| 138 | 9457 | ptr_array_free(&array); | |
| 139 | 9457 | return r; | |
| 140 | } | ||
| 141 | |||
| 142 | 120 | void check_cmds_array(const Command cmds[], size_t ncmds, const char *array_name) | |
| 143 | { | ||
| 144 | 120 | BUG_ON(ncmds == 0); | |
| 145 | 120 | BUG_ON(array_name[0] == '\0'); | |
| 146 | |||
| 147 | if (!DEBUG_ASSERTIONS_ENABLED) { | ||
| 148 | return; | ||
| 149 | } | ||
| 150 | |||
| 151 | // See also: check_bsearch_array() and check_array() | ||
| 152 |
2/2✓ Branch 34 → 6 taken 3192 times.
✓ Branch 34 → 35 taken 120 times.
|
3312 | for (size_t i = 0; i < ncmds; i++) { |
| 153 | 3192 | const char *name = cmds[i].name; | |
| 154 | 3192 | const char *flags = cmds[i].flags; | |
| 155 |
1/2✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 3192 times.
|
3192 | if (name[0] == '\0') { |
| 156 | − | BUG("Empty string at %s[%zu].name", array_name, i); | |
| 157 | } | ||
| 158 |
1/2✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 3192 times.
|
3192 | if (name[sizeof(cmds[0].name) - 1] != '\0') { |
| 159 | − | BUG("String sentinel missing from %s[%zu].name", array_name, i); | |
| 160 | } | ||
| 161 |
1/2✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 3192 times.
|
3192 | if (flags[ARRAYLEN(cmds[0].flags) - 1] != '\0') { |
| 162 | − | BUG("String sentinel missing from %s[%zu].flags", array_name, i); | |
| 163 | } | ||
| 164 | |||
| 165 |
3/4✓ Branch 12 → 13 taken 24 times.
✓ Branch 12 → 15 taken 3168 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 24 times.
|
3192 | if (is_option(name) && !streq(name, "syntax")) { |
| 166 | // Preventing this isn't strictly necessary, but not having | ||
| 167 | // commands and options with the same name makes links and | ||
| 168 | // cross-references in documentation less error prone | ||
| 169 | − | BUG("command and option with identical name: \"%s\"", name); | |
| 170 | } | ||
| 171 | |||
| 172 |
3/4✓ Branch 15 → 16 taken 3072 times.
✓ Branch 15 → 29 taken 120 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 29 taken 3072 times.
|
3192 | if (i > 0 && strcmp(name, cmds[i - 1].name) <= 0) { |
| 173 | − | BUG ( | |
| 174 | "String at %s[%zu].name not in sorted order: \"%s\" (prev: \"%s\")", | ||
| 175 | array_name, i, name, cmds[i - 1].name | ||
| 176 | ); | ||
| 177 | } | ||
| 178 | |||
| 179 | unsigned char prev_flag = 0; | ||
| 180 | size_t nr_real_flags = 0; | ||
| 181 | |||
| 182 |
2/2✓ Branch 30 → 18 taken 5328 times.
✓ Branch 30 → 31 taken 3192 times.
|
8520 | for (size_t j = 0; flags[j]; j++) { |
| 183 | 5328 | unsigned char flag = flags[j]; | |
| 184 |
2/2✓ Branch 18 → 19 taken 144 times.
✓ Branch 18 → 23 taken 5184 times.
|
5328 | if (flag == '=') { |
| 185 |
2/4✓ Branch 19 → 20 taken 144 times.
✗ Branch 19 → 22 not taken.
✓ Branch 20 → 21 taken 144 times.
✗ Branch 20 → 22 not taken.
|
144 | if (j && flags[j - 1] != '=') { |
| 186 | 144 | continue; | |
| 187 | } | ||
| 188 | − | BUG("invalid = in %s[%zu].flags (%s): %s", array_name, i, name, flags); | |
| 189 | } | ||
| 190 | |||
| 191 |
1/2✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 5184 times.
|
5184 | if (!ascii_isalnum(flag)) { |
| 192 | − | BUG("invalid command flag: 0x%02hhX", flag); | |
| 193 | } | ||
| 194 | |||
| 195 |
1/2✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 5184 times.
|
5184 | if (prev_flag >= flag) { // Using >= here also catches duplicate flags |
| 196 | − | BUG ( | |
| 197 | "flags -%c and -%c not in sorted order for %s[%zu] (%s): %s", | ||
| 198 | flag, prev_flag, array_name, i, name, flags | ||
| 199 | ); | ||
| 200 | } | ||
| 201 | |||
| 202 | 5184 | nr_real_flags++; | |
| 203 | 5184 | prev_flag = flag; | |
| 204 | } | ||
| 205 | |||
| 206 | // Check that the number of real flags (not including '=') fits | ||
| 207 | // in the CommandArgs::flags array and leaves 1 byte for the | ||
| 208 | // null terminator | ||
| 209 | 3192 | CommandArgs a; | |
| 210 | 3192 | BUG_ON(nr_real_flags >= ARRAYLEN(a.flags)); | |
| 211 | } | ||
| 212 | } | ||
| 213 |