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 */