Files
ligent/tests/reduce.c
T
2026-10-03 14:43:43 +08:00

123 lines
3.6 KiB
C

/* reduce.c - 把 fuzz 找到的失败输入最小化到最短复现样例
*
* 判定 (oracle):同一段输入, "一次性喂" 与 "逐字节喂" 的渲染结果不一致.
* 用法: build/reduce build/md_fail.bin
*/
#include "markdown.h"
#include "util.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 注意: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("<ESC>");
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;
}