diff --git a/patch_v2/meow_launcherd/font_theme.c b/patch_v2/meow_launcherd/font_theme.c index 6bd24b5..3354a29 100644 --- a/patch_v2/meow_launcherd/font_theme.c +++ b/patch_v2/meow_launcherd/font_theme.c @@ -3,6 +3,10 @@ #include "meow_launcher_internal.h" #include "mf_logsig.h" /* M52:多字体日志只在配置签名跃迁时打(纯函数,宿主单测) */ +/* M52 r2:class 缺失 WARN 的跃迁位图是 32 位 unsigned(见 meow_font_reload_multifont)。 + * 类别数一旦超过 32,高位会被静默丢掉(= 该类的 WARN 不再打)——编译期钉死。 */ +_Static_assert(MEOW_FCLS_COUNT <= 32, "mf_missing_warn_mask assumes <= 32 font classes"); + /* M2.6 字重结构 + M2.8 三级定案(FONT_SYSTEM):卡内字体 = * <卡根>/MeowOS/fonts///(两级,向后兼容)或 * <卡根>/MeowOS/fonts////(三级,tier=common/gbk/full)。 @@ -304,6 +308,7 @@ void meow_font_reload_multifont(void) static char cpng[MEOW_FCLS_COUNT][400], cidx[MEOW_FCLS_COUNT][400]; meow_font_class_cfg cfg[MEOW_FCLS_COUNT]; memset(cfg, 0, sizeof cfg); + unsigned miss_now = 0; /* 本轮「配置了但缺失/不可读」的类别位图(M52 r2:见下方门控) */ for (int c = 0; c < MEOW_FCLS_COUNT; c++) { char ref[MEOW_SET_MAX_LEN]; if (meow_settings_get_str(cls_keys[c], ref, sizeof ref) == 0 && ref[0]) { @@ -317,11 +322,26 @@ void meow_font_reload_multifont(void) } cfg[c].png = cpng[c]; cfg[c].idx = cidx[c]; } else { - /* M2.58:类别字体配置了但缺失 → 该类别回退全局,并记系统 WARN。 */ - logmsg("multi_font: class %s font %s missing, fallback global", cls_keys[c], ref); + /* M2.58:类别字体配置了但缺失 → 该类别回退全局(回退行为不变)。 + * M52 r2(r1 评审 P2-1):这条 WARN 也在这条 2s 热路径上,旧实现每轮每类 + * 各打一条(最多 6 行/2s = 3 行/s,比刚修掉的 enabled 行凶 6 倍)。改成 + * 「只记位图、跃迁时才打」——判定在循环外统一做,日志文本与参数不变。 */ + miss_now |= 1u << c; } } } + /* 新出现缺失的类别才打(缺失集合不变 → 静默;恢复后再缺失 → 会再打一次)。 */ + { + static unsigned mfont_miss_logged; + unsigned fresh = mf_missing_warn_mask(&mfont_miss_logged, miss_now); + for (int c = 0; c < MEOW_FCLS_COUNT; c++) { + if (!(fresh & (1u << c))) continue; + char ref[MEOW_SET_MAX_LEN]; + /* 跃迁是稀有事件:这里重读一次 settings 复原原始相对路径(不占热路径) */ + if (meow_settings_get_str(cls_keys[c], ref, sizeof ref) != 0) ref[0] = 0; + logmsg("multi_font: class %s font %s missing, fallback global", cls_keys[c], ref); + } + } int distinct = 0, truncated = 0; int rc = meow_font_config_multifont(on, gpng, gidx, cfg, &distinct, &truncated); /* M52(日志刷屏):本函数被主循环 hotplug 轮询每 2s 调一次,配置绝大多数周期不变。 diff --git a/patch_v2/meow_launcherd/mf_logsig.h b/patch_v2/meow_launcherd/mf_logsig.h index eddfaa9..a5aa243 100644 --- a/patch_v2/meow_launcherd/mf_logsig.h +++ b/patch_v2/meow_launcherd/mf_logsig.h @@ -62,4 +62,16 @@ static inline int mf_logsig_changed(uint64_t *last, int *valid, uint64_t sig) return 1; } +/* 「class 字体缺失」WARN 的跃迁门控(r1 评审 P2-1)。 + * mask = 本轮「配置了但缺失/不可读」的类别位图(bit c = MEOW_FCLS_* 第 c 类)。 + * 返回 mask 里**上一次没报过**的那些位(本轮该打 WARN 的类别),并把 *logged 更新为 mask。 + * 于是:缺失集合不变 → 返回 0(静默);新类别变缺失 → 只返回新增位; + * 某类恢复后再次缺失 → 会重新返回(不是一次性闩锁)。 */ +static inline unsigned mf_missing_warn_mask(unsigned *logged, unsigned mask) +{ + unsigned fresh = mask & ~(*logged); + *logged = mask; + return fresh; +} + #endif /* MF_LOGSIG_H */ diff --git a/patch_v2/meow_launcherd/test_mf_logsig.c b/patch_v2/meow_launcherd/test_mf_logsig.c index edff375..1c43409 100644 --- a/patch_v2/meow_launcherd/test_mf_logsig.c +++ b/patch_v2/meow_launcherd/test_mf_logsig.c @@ -97,6 +97,40 @@ int main(void) printf(" (旧实现 60s 空转 = 30 行;新实现稳态 = 0 行,本用例全程 %d 行)\n", emits); } + /* ---- 6. 「class 字体缺失」WARN 的跃迁门控(r1 评审 P2-1 的验收断言)---- + * 场景:某个 font.class_* 配置了但字体文件缺失 → 该类别回退全局(行为不变), + * 旧实现每 2s 每类各打一条(最多 6 行/2s = 3 行/s)。要求: + * 缺失集合不变 → 60s(30 次) 只出现 1 次; + * 缺失的类别变了(另一个 class 也缺失)→ 再出现 1 次(不是一次性闩锁); + * 某类恢复后再缺失 → 也算新跃迁,再 1 次。 */ + { + unsigned logged = 0; + int warns = 0; + /* (a) 只有 class_num(2) 缺失:连续 30 次(60s @2s) */ + for (int i = 0; i < 30; i++) + if (mf_missing_warn_mask(&logged, 1u << 2)) warns++; + check("class missing 60s @2s -> exactly 1 WARN", warns, 1); + /* (b) 缺失集合不变(再 30 次):不增长 */ + for (int i = 0; i < 30; i++) + if (mf_missing_warn_mask(&logged, 1u << 2)) warns++; + check("class missing unchanged -> no new WARN", warns, 1); + /* (c) class_latin(3) 也变缺失:只多 1 次(且只针对新缺失的那一类) */ + { + unsigned fresh = mf_missing_warn_mask(&logged, (1u << 2) | (1u << 3)); + warns += fresh ? 1 : 0; + check("new missing class -> exactly 1 more WARN", fresh, 1u << 3); + } + check("total WARN after set change", warns, 2); + /* (d) class_num(2) 恢复 → 位图缩小但不再打;再次缺失 → 重新打一次 */ + check("recovered class -> no WARN", mf_missing_warn_mask(&logged, 1u << 3), 0); + check("re-missing after recovery -> WARN again", + mf_missing_warn_mask(&logged, (1u << 2) | (1u << 3)), 1u << 2); + /* (e) 全部恢复 → 位图清零、静默 */ + check("none missing -> no WARN", mf_missing_warn_mask(&logged, 0), 0); + check("logged mask reset", logged, 0); + printf(" (旧实现 60s @2s 单类缺失 = 30 行;新实现 = 1 行)\n"); + } + printf("\n%d pass, %d fail\n", g_pass, g_fail); return g_fail ? 1 : 0; } diff --git a/patch_v2/meow_watchdog/meow_watchdog.c b/patch_v2/meow_watchdog/meow_watchdog.c index ad9e20e..f948df8 100644 --- a/patch_v2/meow_watchdog/meow_watchdog.c +++ b/patch_v2/meow_watchdog/meow_watchdog.c @@ -181,6 +181,10 @@ static void wd_log(const char *fmt, ...) va_end(ap); char line[544]; int l = snprintf(line, sizeof line, "%llu [watchdog] %s\n", now_ms(), buf); + /* M52 r2(r1 评审 nit):显式 clamp,别把「buf ≤511 + 前缀 ≤23 < 544」当隐式不变量—— + * 日后调大 buf 或调小 line 就是栈越界读。snprintf 返回「本应写入的长度」,可能 ≥ sizeof line。 */ + if (l < 0) l = 0; + else if (l >= (int)sizeof line) l = (int)sizeof line - 1; if (wd_log_fd >= 0) { if (write(wd_log_fd, line, l) < 0) wd_log_fd = -1; return; diff --git a/patch_v2/meow_watchdog/test_wd_fw.c b/patch_v2/meow_watchdog/test_wd_fw.c index 914f2cf..21dc15b 100644 --- a/patch_v2/meow_watchdog/test_wd_fw.c +++ b/patch_v2/meow_watchdog/test_wd_fw.c @@ -436,11 +436,30 @@ static void test_resguard(void) check("emit warn->crit", wd_rg_should_emit(WD_RG_WARN, WD_RG_CRIT, 0, 0), 1); check("emit crit->ok (recovery)", wd_rg_should_emit(WD_RG_CRIT, WD_RG_OK, 0, 0), 1); - /* 8) 真机 /proc 冒烟:本进程必须能被扫到,且自身 ppid 合理(Linux 宿主机也适用) */ + /* 8) 真机 /proc 冒烟:本进程必须真的被扫进表里(r1 评审 nit:旧断言带 `|| np > 0` 恒真,等于没测) */ { int np = wd_rg_scan(WD_RG_MAX_PIDS); check("scan /proc ok", np > 0, 1); - check("scan finds self", wd_rg_name_of(np, getpid())[0] != '?' || np > 0, 1); + int self = getpid(), self_idx = -1; + for (int i = 0; i < np; i++) if (wd_rg_pid[i] == self) { self_idx = i; break; } + check("scan table contains getpid()", self_idx >= 0, 1); + if (self_idx >= 0) { + check("scan self comm non-empty", wd_rg_comm[self_idx][0] != 0, 1); + check("scan self comm not '?'", wd_rg_name_of(np, self)[0] != '?', 1); + /* 本进程的 ppid 必须与 /proc/self/stat 一致(解析链路端到端自证) */ + char sbuf[256]; + int fd = open("/proc/self/stat", O_RDONLY); + int got = (fd >= 0) ? (int)read(fd, sbuf, sizeof sbuf - 1) : -1; + if (fd >= 0) close(fd); + int pid2 = 0, ppid2 = 0, ok = 0; + char comm2[WD_RG_COMM_LEN]; + if (got > 0) { + sbuf[got] = 0; + ok = (wd_rg_parse_stat(sbuf, &pid2, comm2, sizeof comm2, &ppid2) == 0); + } + check("scan self pid matches /proc/self/stat", ok && pid2 == self, 1); + check("scan self ppid matches /proc/self/stat", ok && wd_rg_ppid[self_idx] == ppid2, 1); + } int tp[WD_RG_TOP_PARENTS], tc[WD_RG_TOP_PARENTS]; int k = wd_rg_top_parents(wd_rg_ppid, np, WD_RG_TOP_PARENTS, tp, tc); check("scan top parents >=0", k >= 0, 1);