#include #include #include #include #include #include #include #include #include "fuzzer.h" #include "text.h" #include "text-util.h" #include "util.h" #ifndef BUFSIZ #define BUFSIZ 1024 #endif typedef enum CmdStatus (*Cmd)(Text *txt, const char *cmd); static Mark mark = EMARK; static char data[BUFSIZ]; static uint64_t bench(void) { struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) return (uint64_t)(ts.tv_sec * 1000000 + ts.tv_nsec / 1000); else return 0; } static size_t pos_start(Text *txt) { return 0; } static size_t pos_middle(Text *txt) { return text_size(txt) / 2; } static size_t pos_end(Text *txt) { return text_size(txt); } static size_t pos_random(Text *txt) { return rand() % (text_size(txt) + 1); } static size_t pos_prev(Text *txt) { static size_t pos = EPOS; size_t max = text_size(txt); if (pos > max) pos = max; return pos-- % (max + 1); } static size_t pos_next(Text *txt) { static size_t pos = 0; return pos++ % (text_size(txt) + 1); } static size_t pos_stripe(Text *txt) { static size_t pos = 0; return pos+=1024 % (text_size(txt) + 1); } static enum CmdStatus bench_insert(Text *txt, size_t pos, const char *cmd) { return text_insert(txt, pos, data, sizeof data); } static enum CmdStatus bench_delete(Text *txt, size_t pos, const char *cmd) { return text_delete(txt, pos, 1); } static enum CmdStatus bench_replace(Text *txt, size_t pos, const char *cmd) { text_delete(txt, pos, 1); text_insert(txt, pos, "-", 1); return CMD_OK; } static enum CmdStatus bench_mark(Text *txt, size_t pos, const char *cmd) { Mark mark = text_mark_set(txt, pos); if (mark == EMARK) return CMD_FAIL; if (text_mark_get(txt, mark) != pos) return CMD_FAIL; return CMD_OK; } static enum CmdStatus cmd_bench(Text *txt, const char *cmd) { static enum CmdStatus (*bench_cmd[])(Text*, size_t, const char*) = { ['i'] = bench_insert, ['d'] = bench_delete, ['r'] = bench_replace, ['m'] = bench_mark, }; static size_t (*bench_pos[])(Text*) = { ['^'] = pos_start, ['|'] = pos_middle, ['$'] = pos_end, ['%'] = pos_random, ['-'] = pos_prev, ['+'] = pos_next, ['~'] = pos_stripe, }; if (!data[0]) { // make `p` command output more readable int len = snprintf(data, sizeof data, "[ ... %zu bytes ... ]\n", sizeof data); memset(data+len, '\r', sizeof(data) - len); } const char *params = cmd; while (*params == ' ') params++; size_t idx_cmd = params[0]; if (idx_cmd >= LENGTH(bench_cmd) || !bench_cmd[idx_cmd]) { puts("Invalid bench command"); return CMD_ERR; } for (params++; *params == ' '; params++); size_t idx_pos = params[0]; if (idx_pos >= LENGTH(bench_pos) || !bench_pos[idx_pos]) { puts("Invalid bench position"); return CMD_ERR; } size_t iter = 1; sscanf(params+1, "%zu\n", &iter); for (size_t i = 1; i <= iter; i++) { size_t pos = bench_pos[idx_pos](txt); uint64_t s = bench(); enum CmdStatus ret = bench_cmd[idx_cmd](txt, pos, NULL); uint64_t e = bench(); if (ret != CMD_OK) return ret; printf("%zu: %" PRIu64 "us\n", i, e-s); } return CMD_OK; } static enum CmdStatus cmd_insert(Text *txt, const char *cmd) { char data[BUFSIZ]; size_t pos; if (sscanf(cmd, "%zu %s\n", &pos, data) != 2) return CMD_ERR; size_t len = strlen(data); return text_insert(txt, pos, data, len); } static enum CmdStatus cmd_delete(Text *txt, const char *cmd) { size_t pos, len; if (sscanf(cmd, "%zu %zu", &pos, &len) != 2) return CMD_ERR; return text_delete(txt, pos, len); } static enum CmdStatus cmd_size(Text *txt, const char *cmd) { printf("%zu bytes\n", text_size(txt)); return CMD_OK; } static enum CmdStatus cmd_snapshot(Text *txt, const char *cmd) { text_snapshot(txt); return CMD_OK; } static enum CmdStatus cmd_undo(Text *txt, const char *cmd) { return text_undo(txt) != EPOS; } static enum CmdStatus cmd_redo(Text *txt, const char *cmd) { return text_redo(txt) != EPOS; } static enum CmdStatus cmd_earlier(Text *txt, const char *cmd) { return text_earlier(txt) != EPOS; } static enum CmdStatus cmd_later(Text *txt, const char *cmd) { return text_later(txt) != EPOS; } static enum CmdStatus cmd_mark_set(Text *txt, const char *cmd) { size_t pos; if (sscanf(cmd, "%zu\n", &pos) != 1) return CMD_ERR; Mark m = text_mark_set(txt, pos); if (m != EMARK) mark = m; return m != EMARK; } static enum CmdStatus cmd_mark_get(Text *txt, const char *cmd) { size_t pos = text_mark_get(txt, mark); if (pos != EPOS) printf("%zu\n", pos); return pos != EPOS; } static enum CmdStatus cmd_print(Text *txt, const char *cmd) { size_t start = 0, size = text_size(txt), rem = size; for (Iterator it = text_iterator_get(txt, start); rem > 0 && text_iterator_valid(&it); text_iterator_next(&it)) { size_t prem = it.end - it.text; if (prem > rem) prem = rem; if (fwrite(it.text, prem, 1, stdout) != 1) return CMD_ERR; rem -= prem; } if (rem != size) puts(""); return rem == 0; } static enum CmdStatus cmd_info(Text *txt, const char *cmd) { #ifdef text_info TextInfo info = text_info(txt); printf("meta data: %zu\nblocks: %zu\ndata: %zu\nrevisions: %zu\n" "changes: %zu\nchanges total: %zu\npieces: %zu\npieces total: %zu\n", info.metadata, info.blocks, info.data, info.revisions, info.changes, info.changes_total, info.pieces, info.pieces_total); #endif return CMD_OK; } static enum CmdStatus cmd_dump(Text *txt, const char *cmd) { #ifdef text_dump text_dump(txt, stdout); #endif return CMD_OK; } static enum CmdStatus cmd_quit(Text *txt, const char *cmd) { return CMD_QUIT; } static Cmd commands[] = { ['%'] = cmd_info, ['@'] = cmd_dump, ['-'] = cmd_earlier, ['+'] = cmd_later, ['?'] = cmd_mark_get, ['='] = cmd_mark_set, ['#'] = cmd_size, ['b'] = cmd_bench, ['d'] = cmd_delete, ['i'] = cmd_insert, ['p'] = cmd_print, ['q'] = cmd_quit, ['r'] = cmd_redo, ['s'] = cmd_snapshot, ['u'] = cmd_undo, }; static int repl(const char *name, FILE *input) { Text *txt = text_load(name); if (!name) name = "-"; if (!txt) { fprintf(stderr, "Failed to load text from `%s'\n", name); return 1; } printf("Loaded %zu bytes from `%s'\n", text_size(txt), name); char line[BUFSIZ]; for (;;) { printf("> "); if (!fgets(line, sizeof(line), input)) break; if (!isatty(0)) printf("%s", line); if (line[0] == '\n') continue; size_t idx = line[0]; if (idx < LENGTH(commands) && commands[idx]) { enum CmdStatus ret = commands[idx](txt, line+1); printf("%s", cmd_status_msg[ret]); if (ret == CMD_QUIT) break; } else { puts("Invalid command"); } } text_free(txt); return 0; } #ifdef LIBFUZZER int LLVMFuzzerTestOneInput(const uint8_t *data, size_t len) { FILE *input = fmemopen((void*)data, len, "r"); if (!input) return 1; int r = repl(NULL, input); fclose(input); return r; } #else int main(int argc, char *argv[]) { return repl(argc == 1 ? NULL : argv[1], stdin); } #endif /* LIBFUZZER */