/* reduce.c - 把 fuzz 找到的失败输入最小化到最短复现样例 * * 判定 (oracle): 同一段输入, "一次性喂" 与 "逐字节喂" 的渲染结果不一致. * 用法: build/reduce build/md_fail.bin */ #include "markdown.h" #include "util.h" #include #include #include /* 注意: md_feed 只接受 NUL 结尾的字符串, 而最小化过程中的缓冲区 * 是"长度受限"的, 所以必须先做一份带结尾符的副本. */ static char *render(const char *src, size_t len, size_t chunk) { char *text = xmalloc(len + 1); memcpy(text, src, len); text[len] = '\0'; char *buf = NULL; size_t size = 0; FILE *f = open_memstream(&buf, &size); if (!f) { free(text); return NULL; } MdRenderer *r = md_new(1); md_set_out(r, f); if (chunk == 0) { md_feed(r, text); } else { for (size_t i = 0; i < len; i += chunk) { size_t n = len - i < chunk ? len - i : chunk; char *t = xstrndup(text + i, n); md_feed(r, t); free(t); } } md_finish(r); md_free(r); fclose(f); free(text); return buf; } /* 只要"一次性"与"逐字节"不一致就算失败 */ static int fails(const char *src, size_t len) { if (len == 0) return 0; /* md_feed 以 NUL 结尾, 输入里不能有 NUL */ for (size_t i = 0; i < len; i++) if (src[i] == '\0') return 0; char *a = render(src, len, 0); char *b = render(src, len, 1); int bad = (a && b && strcmp(a, b) != 0); if (bad) { /* 确认真的不是"空输出 vs 空输出"这种假阳性 */ if (strcmp(a, b) == 0) bad = 0; } free(a); free(b); return bad; } static void show(const char *label, const char *buf, size_t len) { printf("%s (%zu 字节): ", label, len); for (size_t i = 0; i < len; i++) { unsigned char c = (unsigned char)buf[i]; if (c == '\n') printf("\\n"); else if (c == '\t') printf("\\t"); else if (c == 0x1b) printf(""); else if (c >= 0x20 && c < 0x7f) putchar(c); else printf("\\x%02x", c); } putchar('\n'); } int main(int argc, char **argv) { if (argc < 2) { fputs("用法: reduce <失败输入文件>\n", stderr); return 2; } size_t len = 0; char *buf = read_entire_file(argv[1], &len); if (!buf) { perror("读取失败"); return 2; } if (!fails(buf, len)) { puts("该输入当前不复现失败"); free(buf); return 1; } puts("== 原始失败输入 =="); show(" ", buf, len); { char *a = render(buf, len, 0), *b = render(buf, len, 1); show(" 一次性", a, strlen(a)); show(" 逐字节", b, strlen(b)); free(a); free(b); } /* 贪心块删除 */ size_t gran = len / 2; while (gran >= 1) { size_t i = 0; while (i + gran <= len) { char *save = xmalloc(gran); memcpy(save, buf + i, gran); memmove(buf + i, buf + i + gran, len - i - gran); if (fails(buf, len - gran)) { len -= gran; } else { memmove(buf + i + gran, buf + i, len - i - gran); memcpy(buf + i, save, gran); i += gran; } free(save); } if (gran == 1) break; gran /= 2; } printf("\n== 最小复现样例 (%zu 字节) ==\n", len); show(" ", buf, len); { char *a = render(buf, len, 0), *b = render(buf, len, 1); show(" 一次性", a, strlen(a)); show(" 逐字节", b, strlen(b)); free(a); free(b); } free(buf); return 0; }