vis
a vi-like editor based on Plan 9's structural regular expressions
git clone https://9o.is/git/vis.git
text-fuzzer.c
(7062B)
1 #include <stddef.h>
2 #include <stdbool.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <errno.h>
6 #include <stdio.h>
7 #include <unistd.h>
8 #include <inttypes.h>
9 #include "fuzzer.h"
10 #include "text.h"
11 #include "text-util.h"
12 #include "util.h"
13
14 #ifndef BUFSIZ
15 #define BUFSIZ 1024
16 #endif
17
18 typedef enum CmdStatus (*Cmd)(Text *txt, const char *cmd);
19
20 static Mark mark = EMARK;
21
22 static char data[BUFSIZ];
23
24 static uint64_t bench(void) {
25 struct timespec ts;
26
27 if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
28 return (uint64_t)(ts.tv_sec * 1000000 + ts.tv_nsec / 1000);
29 else
30 return 0;
31 }
32
33 static size_t pos_start(Text *txt) {
34 return 0;
35 }
36
37 static size_t pos_middle(Text *txt) {
38 return text_size(txt) / 2;
39 }
40
41 static size_t pos_end(Text *txt) {
42 return text_size(txt);
43 }
44
45 static size_t pos_random(Text *txt) {
46 return rand() % (text_size(txt) + 1);
47 }
48
49 static size_t pos_prev(Text *txt) {
50 static size_t pos = EPOS;
51 size_t max = text_size(txt);
52 if (pos > max)
53 pos = max;
54 return pos-- % (max + 1);
55 }
56
57 static size_t pos_next(Text *txt) {
58 static size_t pos = 0;
59 return pos++ % (text_size(txt) + 1);
60 }
61
62 static size_t pos_stripe(Text *txt) {
63 static size_t pos = 0;
64 return pos+=1024 % (text_size(txt) + 1);
65 }
66
67 static enum CmdStatus bench_insert(Text *txt, size_t pos, const char *cmd) {
68 return text_insert(txt, pos, data, sizeof data);
69 }
70
71 static enum CmdStatus bench_delete(Text *txt, size_t pos, const char *cmd) {
72 return text_delete(txt, pos, 1);
73 }
74
75 static enum CmdStatus bench_replace(Text *txt, size_t pos, const char *cmd) {
76 text_delete(txt, pos, 1);
77 text_insert(txt, pos, "-", 1);
78 return CMD_OK;
79 }
80
81 static enum CmdStatus bench_mark(Text *txt, size_t pos, const char *cmd) {
82 Mark mark = text_mark_set(txt, pos);
83 if (mark == EMARK)
84 return CMD_FAIL;
85 if (text_mark_get(txt, mark) != pos)
86 return CMD_FAIL;
87 return CMD_OK;
88 }
89
90 static enum CmdStatus cmd_bench(Text *txt, const char *cmd) {
91
92 static enum CmdStatus (*bench_cmd[])(Text*, size_t, const char*) = {
93 ['i'] = bench_insert,
94 ['d'] = bench_delete,
95 ['r'] = bench_replace,
96 ['m'] = bench_mark,
97 };
98
99 static size_t (*bench_pos[])(Text*) = {
100 ['^'] = pos_start,
101 ['|'] = pos_middle,
102 ['$'] = pos_end,
103 ['%'] = pos_random,
104 ['-'] = pos_prev,
105 ['+'] = pos_next,
106 ['~'] = pos_stripe,
107 };
108
109 if (!data[0]) {
110 // make `p` command output more readable
111 int len = snprintf(data, sizeof data, "[ ... %zu bytes ... ]\n", sizeof data);
112 memset(data+len, '\r', sizeof(data) - len);
113 }
114
115 const char *params = cmd;
116 while (*params == ' ')
117 params++;
118
119 size_t idx_cmd = params[0];
120 if (idx_cmd >= LENGTH(bench_cmd) || !bench_cmd[idx_cmd]) {
121 puts("Invalid bench command");
122 return CMD_ERR;
123 }
124
125 for (params++; *params == ' '; params++);
126
127 size_t idx_pos = params[0];
128 if (idx_pos >= LENGTH(bench_pos) || !bench_pos[idx_pos]) {
129 puts("Invalid bench position");
130 return CMD_ERR;
131 }
132
133 size_t iter = 1;
134 sscanf(params+1, "%zu\n", &iter);
135
136 for (size_t i = 1; i <= iter; i++) {
137 size_t pos = bench_pos[idx_pos](txt);
138 uint64_t s = bench();
139 enum CmdStatus ret = bench_cmd[idx_cmd](txt, pos, NULL);
140 uint64_t e = bench();
141 if (ret != CMD_OK)
142 return ret;
143 printf("%zu: %" PRIu64 "us\n", i, e-s);
144 }
145 return CMD_OK;
146 }
147
148 static enum CmdStatus cmd_insert(Text *txt, const char *cmd) {
149 char data[BUFSIZ];
150 size_t pos;
151 if (sscanf(cmd, "%zu %s\n", &pos, data) != 2)
152 return CMD_ERR;
153 size_t len = strlen(data);
154 return text_insert(txt, pos, data, len);
155 }
156
157 static enum CmdStatus cmd_delete(Text *txt, const char *cmd) {
158 size_t pos, len;
159 if (sscanf(cmd, "%zu %zu", &pos, &len) != 2)
160 return CMD_ERR;
161 return text_delete(txt, pos, len);
162 }
163
164 static enum CmdStatus cmd_size(Text *txt, const char *cmd) {
165 printf("%zu bytes\n", text_size(txt));
166 return CMD_OK;
167 }
168
169 static enum CmdStatus cmd_snapshot(Text *txt, const char *cmd) {
170 text_snapshot(txt);
171 return CMD_OK;
172 }
173
174 static enum CmdStatus cmd_undo(Text *txt, const char *cmd) {
175 return text_undo(txt) != EPOS;
176 }
177
178 static enum CmdStatus cmd_redo(Text *txt, const char *cmd) {
179 return text_redo(txt) != EPOS;
180 }
181
182 static enum CmdStatus cmd_earlier(Text *txt, const char *cmd) {
183 return text_earlier(txt) != EPOS;
184 }
185
186 static enum CmdStatus cmd_later(Text *txt, const char *cmd) {
187 return text_later(txt) != EPOS;
188 }
189
190 static enum CmdStatus cmd_mark_set(Text *txt, const char *cmd) {
191 size_t pos;
192 if (sscanf(cmd, "%zu\n", &pos) != 1)
193 return CMD_ERR;
194 Mark m = text_mark_set(txt, pos);
195 if (m != EMARK)
196 mark = m;
197 return m != EMARK;
198 }
199
200 static enum CmdStatus cmd_mark_get(Text *txt, const char *cmd) {
201 size_t pos = text_mark_get(txt, mark);
202 if (pos != EPOS)
203 printf("%zu\n", pos);
204 return pos != EPOS;
205 }
206
207 static enum CmdStatus cmd_print(Text *txt, const char *cmd) {
208 size_t start = 0, size = text_size(txt), rem = size;
209 for (Iterator it = text_iterator_get(txt, start);
210 rem > 0 && text_iterator_valid(&it);
211 text_iterator_next(&it)) {
212 size_t prem = it.end - it.text;
213 if (prem > rem)
214 prem = rem;
215 if (fwrite(it.text, prem, 1, stdout) != 1)
216 return CMD_ERR;
217 rem -= prem;
218 }
219 if (rem != size)
220 puts("");
221 return rem == 0;
222 }
223
224 static enum CmdStatus cmd_info(Text *txt, const char *cmd) {
225 #ifdef text_info
226 TextInfo info = text_info(txt);
227 printf("meta data: %zu\nblocks: %zu\ndata: %zu\nrevisions: %zu\n"
228 "changes: %zu\nchanges total: %zu\npieces: %zu\npieces total: %zu\n",
229 info.metadata, info.blocks, info.data, info.revisions,
230 info.changes, info.changes_total, info.pieces, info.pieces_total);
231 #endif
232 return CMD_OK;
233 }
234
235 static enum CmdStatus cmd_dump(Text *txt, const char *cmd) {
236 #ifdef text_dump
237 text_dump(txt, stdout);
238 #endif
239 return CMD_OK;
240 }
241
242 static enum CmdStatus cmd_quit(Text *txt, const char *cmd) {
243 return CMD_QUIT;
244 }
245
246 static Cmd commands[] = {
247 ['%'] = cmd_info,
248 ['@'] = cmd_dump,
249 ['-'] = cmd_earlier,
250 ['+'] = cmd_later,
251 ['?'] = cmd_mark_get,
252 ['='] = cmd_mark_set,
253 ['#'] = cmd_size,
254 ['b'] = cmd_bench,
255 ['d'] = cmd_delete,
256 ['i'] = cmd_insert,
257 ['p'] = cmd_print,
258 ['q'] = cmd_quit,
259 ['r'] = cmd_redo,
260 ['s'] = cmd_snapshot,
261 ['u'] = cmd_undo,
262 };
263
264 static int repl(const char *name, FILE *input) {
265 Text *txt = text_load(name);
266 if (!name)
267 name = "-";
268 if (!txt) {
269 fprintf(stderr, "Failed to load text from `%s'\n", name);
270 return 1;
271 }
272
273 printf("Loaded %zu bytes from `%s'\n", text_size(txt), name);
274
275 char line[BUFSIZ];
276 for (;;) {
277 printf("> ");
278 if (!fgets(line, sizeof(line), input))
279 break;
280 if (!isatty(0))
281 printf("%s", line);
282 if (line[0] == '\n')
283 continue;
284 size_t idx = line[0];
285 if (idx < LENGTH(commands) && commands[idx]) {
286 enum CmdStatus ret = commands[idx](txt, line+1);
287 printf("%s", cmd_status_msg[ret]);
288 if (ret == CMD_QUIT)
289 break;
290 } else {
291 puts("Invalid command");
292 }
293 }
294
295 text_free(txt);
296
297 return 0;
298 }
299
300 #ifdef LIBFUZZER
301
302 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t len) {
303 FILE *input = fmemopen((void*)data, len, "r");
304 if (!input)
305 return 1;
306 int r = repl(NULL, input);
307 fclose(input);
308 return r;
309 }
310
311 #else
312
313 int main(int argc, char *argv[]) {
314 return repl(argc == 1 ? NULL : argv[1], stdin);
315 }
316
317 #endif /* LIBFUZZER */