图库: 修 M27 评审 P1 门控旁路 + 三个 P2(r1)

P1-1:判定为 backend 的页不再无条件放行 + 后端中途失败不再静默回退 stb。
- meow_img_load_file_step_ex 增加 allow_stb_fallback 参数:0 = 只试后端,
  失败返回 NULL(坏图占位),绝不回落 stb。阅读器按 dec.path 传入。
- 旧行为实测:截断 12MP JPEG 18,403kB、3000×3000 24bpp 头-only BMP 36,056kB、
  非法位深 PNG 16,409kB(stb 都"成功"出图)。修复后:
  截断 JPEG 275kB→NULL、头-only BMP 908kB→NULL、非法位深 PNG 门控占位(0 分配)。

P2-1:PNG 支持矩阵补 png_depth_ok 同式(color 2/4/6 仅 8/16;color 3 无 16;
color 0 允许 1/2/4/8/16),不再出现「矩阵说支持、后端拒绝」。
P2-2:后端命中页也保留两道安全阀:MemAvailable<6MB 占位、估算 >12MB 硬上限占位。
P2-3:B8 指标改为「窗口稳定后只 reset 一次」,量整个 30 翻窗口内相对稳态基线的
最大存活增量(淘汰不再掩盖峰值),并打印稳态/驻留/增量。
P2-4:page_source.h 的估算精度口径限定为「后端命中/典型页」。

回归:
- B7c 新增截断 12MP JPEG / 24bpp 头-only BMP / 非法位深 PNG,断言 backend-only
  NULL 且峰值 ≤3MB、或 stb 门控占位;B7 典型页与 B7b 回退矩阵保持全绿。
- test_reader_many_pages 恢复 24bpp 头-only 夹具(1bpp 保留):24bpp 判 failed、
  1bpp 判 skipped;断言 failed=2 / skipped=1。
- run_tests.sh / run_webtoon_tests.sh EXIT=0;sdk/test_meow_img_stream ALL PASS;
  交叉构建(SDK + tuku)零警告。
This commit is contained in:
AskaEth committed 2026-11-06 11:46:16 +08:00
1 parent 1725af631b
commit 11e09af7a4
8 files changed
+171 -49

No files matched your search

+26 -16
View File
@@ -85,9 +85,13 @@ static int write_garbage(const char *path)
}
/* 合法 BMP24 头(声称 3000x3000,无像素数据):stbi_info 能读到尺寸,
* 整幅 RGBA 峰值 36MB 必须被 MR_DECODE_PEAK_CAP(12MB) 在解码前拦下
* (旧代码的 raw 上限是 40MB,这种页会被放行 → 真机上瞬时分配即可能被 OOM 杀掉)。 */
static int write_huge_bmp_header(const char *path)
* 整幅 RGBA 峰值 36MB 必须在解码前拦下(旧代码的 raw 上限是 40MB,这种页会被放行 →
* 真机上瞬时分配即可能被 OOM 杀掉)。
* M27 r1:24bpp(后端支持)与 1bpp(后端拒绝→stb)两种都要覆盖:
* - 24bpp 头-only:判定 backend → backend-only 失败 → NULL/坏图占位,峰值必须 ≤3MB;
* - 1bpp 头-only:判定 stb → 旧门控 skip=peak,零分配。
* 旧实现把 24bpp 换成 1bpp 恰好绕开了「矩阵接受、解码失败→静默回退 stb」的 P1 旁路。 */
static int write_huge_bmp_header(const char *path, int bpp)
{
unsigned char b[54] = { 0 };
b[0] = 'B'; b[1] = 'M';
@@ -97,7 +101,7 @@ static int write_huge_bmp_header(const char *path)
put32(b + 18, 3000); /* width */
put32(b + 22, 3000); /* height */
put16(b + 26, 1); /* planes */
put16(b + 28, 1); /* bpp=1:M23 后端不支持 → 回退 stb(M27 门控仍生效) */
put16(b + 28, (unsigned)bpp); /* 24=后端支持但无像素数据;1=后端拒绝→stb */
FILE *f = fopen(path, "wb");
if (!f) return -1;
int ok = fwrite(b, 1, sizeof b, f) == sizeof b;
@@ -177,12 +181,15 @@ int main(void)
snprintf(p, sizeof p, "%s/p%03d.bmp", book, i + 1);
if (write_bmp24(p, 200, 267, (unsigned)i) != 0) { printf("SKIP: bmp write failed\n"); return 0; }
}
/* 坏册:第 2 页是垃圾字节(解码失败 → 占位,不许崩/死循环)。 */
for (int i = 0; i < 4; i++) {
/* 坏册(5 页):1=有效;2=垃圾字节(永久 failed);3=3000×3000 24bpp 头-only
* (判定 backend→后端失败→NULL,永久 failed,峰值必须小);4=3000×3000 1bpp 头-only
* (回退 stb→skip=peak,可重试);5=有效。 */
for (int i = 0; i < 5; i++) {
char p[MR_PATH_MAX];
snprintf(p, sizeof p, "%s/p%d.bmp", bad, i + 1);
if (i == 1) { if (write_garbage(p) != 0) { printf("SKIP: write failed\n"); return 0; } }
else if (i == 2) { if (write_huge_bmp_header(p) != 0) { printf("SKIP: write failed\n"); return 0; } }
else if (i == 2) { if (write_huge_bmp_header(p, 24) != 0) { printf("SKIP: write failed\n"); return 0; } }
else if (i == 3) { if (write_huge_bmp_header(p, 1) != 0) { printf("SKIP: write failed\n"); return 0; } }
else if (write_bmp24(p, 200, 267, (unsigned)i) != 0) { printf("SKIP: bmp write failed\n"); return 0; }
}
@@ -259,7 +266,7 @@ int main(void)
CHECK(mr_reader_ui_resident_kb() <= 9 * 1024, "resident bounded on 140p book (%ldKB)",
mr_reader_ui_resident_kb());
/* 坏图册:翻过坏页不崩、可继续翻。 */
/* 坏图册(5 页):翻过坏页不崩、可继续翻。最后一页是有效页,允许不前进。 */
meow_view_page_pop();
mr_reader_open_dir_at(bad, 0);
pump_ms(600);
@@ -268,18 +275,21 @@ int main(void)
int before = mr_reader_ui_page();
tap(40, 300);
pump_ms(300);
if (mr_reader_ui_page() == before && before < 3) bad_ok = 0;
if (mr_reader_ui_page() == before && before < 4) bad_ok = 0;
}
CHECK(bad_ok, "flip across corrupt/huge pages keeps working (page=%d)", mr_reader_ui_page());
CHECK(mr_reader_ui_resident_kb() <= 9 * 1024,
"huge page skipped without allocating it (%ldKB)", mr_reader_ui_resident_kb());
/* M22/M27:坏图 = 永久占位(failed);超预算的**回退路径**大页 = 预算跳过(skipped,
* 可重试,不是 failed)。M27 起后端能接住的 24bpp 大图会直接解出,所以这里用 1bpp
* (后端拒绝 → stb)保证仍在验证门控,而不是「大图一律占位」的旧语义。 */
CHECK(mr_reader_ui_failed_pages() == 1,
"garbage page is a permanent placeholder (failed=%d)", mr_reader_ui_failed_pages());
"huge pages skipped/rejected without allocating them (%ldKB)", mr_reader_ui_resident_kb());
/* M22/M27 r1:坏图 = 永久占位(failed);超预算的**回退路径**大页 = 预算跳过(skipped)。
* - 垃圾页 + 3000×3000 24bpp 头-only(判定 backend,backend-only 失败→NULL)→ failed=2;
* - 3000×3000 1bpp 头-only(判定 stb,超预算)→ skipped=1。
* 这条 24bpp 夹具正是 P1 旁路的复现:旧实现会静默回退 stb 并分配 36MB。 */
CHECK(mr_reader_ui_failed_pages() == 2,
"garbage + 24bpp head-only are permanent placeholders (failed=%d)",
mr_reader_ui_failed_pages());
CHECK(mr_reader_ui_skipped_pages() == 1,
"oversized page is a retryable budget skip (skipped=%d)", mr_reader_ui_skipped_pages());
"1bpp oversized page is a retryable budget skip (skipped=%d)",
mr_reader_ui_skipped_pages());
CHECK(meow_ui_activity_active() == ACT_READER, "still in reader after corrupt page");
/* ---- M5 条漫:140 页排版 + 跳页落位 ---- */