fix(launcher): §310 追加两修——未冻结App锁屏输入穿透(独占触控) + 主界面应用名截断
真机 M6.3 反馈(bcef8e0 三连修复未好全,追加两问题):
P1 锁屏 PIN 输入穿透仍在(媒体保活 App 实时读 evdev)
根因:媒体保活 App(播放中)从不 SIGSTOP(保 PCM),SDK 输入线程实时读 evdev,不走缓冲。
bcef8e0 的「SIGCONT 排空」只作用于冻结 App;对未冻结 App,隔离全靠 /tmp/meow_notif_active
让 SDK 排空——若 App 的 meow_ui.a 缺该排空(或节流窗内漏网),锁屏期触摸依旧抵达 App。
修法:run_lock_screen() 与 lck_input_pin_modal() 打开 DEV_TOUCH 后 EVIOCGRAB 独占触控设备
(真机 goodix 实测 EVIOCGRAB 返回 0)——抓取期事件只投递抓取者、不为其它 fd 缓冲/回放,
故**任何**前台进程(冻结或未冻结)锁屏期都收不到触摸(真全模态)。失败时记日志降级
(仍靠标记+SIGSTOP 隔离)。overlay_gate.h 增 overlay_keepalive_needs_grab 判据。
P2 主界面应用名只显示 5 字(「流媒体客户」式,半字残形)
根因:app_launcher.c `strncpy(a->name, name, 16)` 仍把显示名硬截 16 字节——M6.3 只扩了
name[49]+json_str 整序列,漏改此二处。UTF-8 下 16B = 5 汉字 + 「端」首字节 + 无 NUL,
显示成「流媒体客户」+ 残形;main_menu.c menu_name_fit 按字节回退又停在半截字符、加 "..." 溢出。
修法:新增 meow_utf8.h(meow_utf8_char_len / meow_utf8_copy_bounded,纯决策可单测)——
app_launcher.c 两处 strncpy(16) 改 app_name_copy(=copy_bounded, 拷满 sizeof name=49);
menu_name_fit 改整字符前进 + 预留省略号宽度(mfont 用「…」/点阵用 "..."),总宽 ≤ maxw,
绝不劈开多字节字符。
新增 test_utf8_name.c(14 断言:整字符长/整序列有界拷贝,含 cap=16/17/12 边界不劈字)+
test_overlay_gate.c 扩展(S1b 未冻结 App 靠 EVIOCGRAB 隔离 4 断言)。launcher 全量宿主回归
+ SDK 叠加/状态栏/输入回归全 PASS;mipsel 交叉 -Wall -Wextra 0 warning。
md5 56dac860841c3884b4322707246e22af(/usr/bin 只读,待固件打包 M6.4;fw_content.conf 未动)。
This commit is contained in:
1 parent
67aa5fc7a8
commit
c730333776
8 files changed
+224
-14
No files matched your search
@@ -3,6 +3,7 @@
|
||||
#include "meow_launcher_internal.h"
|
||||
#include "meow_ams_start.h" /* MSG_AMS_START pending/拉起决策(meow_ams_start.c) */
|
||||
#include "overlay_gate.h" /* SIGSTOP 叠加会话关闭顺序/死亡回退纯决策(§310 三连 bug 收敛点) */
|
||||
#include "meow_utf8.h" /* UTF-8 整序列有界拷贝(显示名不劈多字节字符,问题2) */
|
||||
|
||||
|
||||
static struct meow_acct g_accts[MEOW_MAX_ACTS]; /* 主循环单写者 */
|
||||
@@ -11,6 +12,16 @@ int g_naccts = 0;
|
||||
|
||||
volatile int g_app_session_active = 0; /* §10.5:1=launch_app waitpid 前台会话中(util.c 读) */
|
||||
|
||||
/* 显示名 UTF-8 整序列有界拷贝(dst 容量 cap 含 NUL)。
|
||||
* M6.3「name[49]+json_str 整序列」只把 json 读入局部 name[49] 与 a->name[49] 放大了,**漏改
|
||||
* strncpy(a->name, name, 16)**——仍把显示名硬截 16 字节,UTF-8 下「流媒体客户端」(18B) 变
|
||||
* 「流媒体客户」+ 半个「端」(16B = 5 汉字 + 首字节),真机主界面应用名截成 5 字(用户 M6.3 实测)。
|
||||
* 本助手(= meow_utf8_copy_bounded)snprintf 到 cap 后回退掉被截断的半截多字节字符。 */
|
||||
static void app_name_copy(char *dst, int cap, const char *src)
|
||||
{
|
||||
meow_utf8_copy_bounded(dst, cap, src);
|
||||
}
|
||||
|
||||
|
||||
/* ---- amsd 上报 / 控制挂点(AMSD_DESIGN §4):best-effort,amsd 不在不影响 launcher ---- */
|
||||
#define MEOW_AMS_SOCK_DEFAULT "/usr/data/meow_ams.sock"
|
||||
@@ -309,7 +320,7 @@ static void meow_acct_sync(const char *app_id, const struct app *ap)
|
||||
a->background = ap->background; /* M4 */
|
||||
a->background_audio = ap->background_audio;
|
||||
strncpy(a->icon, ap->icon, 128);
|
||||
strncpy(a->name, ap->name, 16);
|
||||
app_name_copy(a->name, (int)sizeof a->name, ap->name);
|
||||
strncpy(a->exec, ap->exec, 128);
|
||||
a->ok = ap->ok;
|
||||
a->nroute = ap->nroute > MEOW_MAX_ROUTES ? MEOW_MAX_ROUTES : ap->nroute;
|
||||
@@ -813,7 +824,7 @@ void meow_parse_app_json(FILE *f, struct app *a)
|
||||
}
|
||||
}
|
||||
}
|
||||
strncpy(a->name, name, 16);
|
||||
app_name_copy(a->name, (int)sizeof a->name, name);
|
||||
strncpy(a->exec, exec, 128);
|
||||
strncpy(a->package, package, sizeof a->package - 1);
|
||||
a->framework = fram ? '1' : 0;
|
||||
|
||||
@@ -126,6 +126,17 @@ int run_lock_screen(void)
|
||||
|
||||
meow_touch mt; mt_reset(&mt);
|
||||
int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
|
||||
/* §310 输入穿透(媒体保活 App 不被 SIGSTOP,实时读 evdev):锁屏页独占触控设备——
|
||||
* EVIOCGRAB 使本 fd 之外的其它进程(保活 App 的 SDK 输入线程 / watchdog 手势)在该设备
|
||||
* 上收不到事件(事件仅投递抓取者),且抓取期事件不会为未抓取 fd 缓冲/回放。由此无论 App
|
||||
* 冻结与否,锁屏期**任何** App 都收不到触摸(真全模态)。失败(驱动不支持)则记日志降级,
|
||||
* 仍靠 keep_running/SIGSTOP+标记隔离。关闭 tfd 时抓取自动解除(仍显式释放一次)。 */
|
||||
if (tfd >= 0) {
|
||||
int grab = 1;
|
||||
if (ioctl(tfd, EVIOCGRAB, &grab) < 0)
|
||||
logmsg("lock: EVIOCGRAB touch fail: %s (graceful degrade, marker+SIGSTOP isola only)",
|
||||
strerror(errno));
|
||||
}
|
||||
struct keypoll kp_power, kp_keys;
|
||||
kp_open(&kp_power, DEV_POWER);
|
||||
kp_open(&kp_keys, DEV_KEYS);
|
||||
@@ -272,13 +283,21 @@ int run_lock_screen(void)
|
||||
usleep(POLL_MS * 1000);
|
||||
}
|
||||
done:
|
||||
if (tfd >= 0) close(tfd);
|
||||
if (tfd >= 0) {
|
||||
int grab = 0; /* 显式释放 EVIOCGRAB(关闭 tfd 也会自动解除,双保险) */
|
||||
ioctl(tfd, EVIOCGRAB, &grab);
|
||||
close(tfd);
|
||||
}
|
||||
kp_close(&kp_power);
|
||||
kp_close(&kp_keys);
|
||||
logmsg("lock: exit unlocked=%d screen_on=%d", unlocked, meow_screen_on);
|
||||
return unlocked;
|
||||
died:
|
||||
if (tfd >= 0) close(tfd);
|
||||
if (tfd >= 0) {
|
||||
int grab = 0;
|
||||
ioctl(tfd, EVIOCGRAB, &grab);
|
||||
close(tfd);
|
||||
}
|
||||
kp_close(&kp_power);
|
||||
kp_close(&kp_keys);
|
||||
logmsg("lock: exit overlay-app-died -> LCK_OVERLAY_RET_DIED");
|
||||
@@ -316,6 +335,13 @@ int lck_input_pin_modal(const char *title, char *out, int outcap)
|
||||
fb_pan_buf0_force();
|
||||
meow_touch mt; mt_reset(&mt);
|
||||
int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
|
||||
/* §310 输入穿透:设置页 PIN 模态同样独占触控设备(后台保活 App 不冻结实时读 evdev,防其
|
||||
* 同坐标控件被 PIN 键盘点击双重触发)。失败降级(仍靠标记+SIGSTOP 隔离)。 */
|
||||
if (tfd >= 0) {
|
||||
int grab = 1;
|
||||
if (ioctl(tfd, EVIOCGRAB, &grab) < 0)
|
||||
logmsg("pin-modal: EVIOCGRAB touch fail: %s (graceful degrade)", strerror(errno));
|
||||
}
|
||||
struct keypoll kp_power, kp_keys;
|
||||
kp_open(&kp_power, DEV_POWER);
|
||||
kp_open(&kp_keys, DEV_KEYS);
|
||||
@@ -412,7 +438,11 @@ int lck_input_pin_modal(const char *title, char *out, int outcap)
|
||||
usleep(POLL_MS * 1000);
|
||||
}
|
||||
done2:
|
||||
if (tfd >= 0) close(tfd);
|
||||
if (tfd >= 0) {
|
||||
int grab = 0; /* 显式释放 EVIOCGRAB(关闭 tfd 亦自动解除,双保险) */
|
||||
ioctl(tfd, EVIOCGRAB, &grab);
|
||||
close(tfd);
|
||||
}
|
||||
kp_close(&kp_power);
|
||||
kp_close(&kp_keys);
|
||||
return result;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
|
||||
#include "meow_launcher_internal.h"
|
||||
#include "meow_ams_start.h" /* MSG_AMS_START 拉起决策(meow_ams_start.c):run_menu 消费用 */
|
||||
#include "meow_utf8.h" /* UTF-8 整序列字符步进(menu_name_fit 截断不劈多字节字符,问题2) */
|
||||
|
||||
|
||||
/* set by run_menu: the player's panel was blanked (screen timeout) when the
|
||||
@@ -393,20 +394,41 @@ static void menu_first_glyph(const char *nm, char out[4])
|
||||
}
|
||||
}
|
||||
|
||||
/* 名称截断(按可用像素宽;超出加 '…')。 */
|
||||
/* 名称截断(按可用像素宽):UTF-8 整序列 + 「…」收尾,总宽 ≤ maxw。
|
||||
* 旧实现:①按字节回退、边界判定错(do..while 读的是刚清零的字节,恒停在一个续字节上)→ 停在
|
||||
* 半截多字节字符,显示成半字/方框;②先截满再加 "..." 未为省略号留宽 → 追加后溢出 maxw 被裁切。
|
||||
* 修法:按字符起始字节前进,绝不劈开多字节字符;预留省略号宽度(mfont_ready 用「…」/回退点阵用
|
||||
* "..."),截断后总宽 ≤ maxw。 */
|
||||
static void menu_name_fit(const char *name, int maxw, int size, char *out, int olen)
|
||||
{
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof buf, "%s", name);
|
||||
if (ui_width(buf, size) <= maxw) { snprintf(out, olen, "%s", buf); return; }
|
||||
int n = (int)strlen(buf);
|
||||
while (n > 0 && ui_width(buf, size) > maxw) {
|
||||
/* 回退一个 UTF-8 字符边界 */
|
||||
do { buf[--n] = 0; } while (n > 0 && ((unsigned char)buf[n] & 0xC0) == 0x80);
|
||||
if (!name) name = "";
|
||||
if (maxw < 0) maxw = 0;
|
||||
const char *ell = mfont_ready ? "…" : "...";
|
||||
int ell_w = ui_width(ell, size);
|
||||
int budget = maxw - ell_w;
|
||||
if (budget < 0) budget = 0;
|
||||
if (ui_width(name, size) <= budget) { snprintf(out, olen, "%s", name); return; }
|
||||
/* 整字符前进(meow_utf8_char_len 定步长):cut 恒停在一个完整字符之后,绝不劈开多字节字符;
|
||||
* 连同下一整字符会超预算即停。 */
|
||||
size_t stop = strlen(name);
|
||||
if (stop > (size_t)olen - 1) stop = (size_t)olen - 1;
|
||||
size_t cut = 0;
|
||||
while (cut < stop && name[cut]) {
|
||||
int step = meow_utf8_char_len((unsigned char)name[cut]);
|
||||
size_t clen = (cut + (size_t)step <= stop) ? (size_t)step : (stop - cut);
|
||||
if (clen == 0) break;
|
||||
char tmp[64];
|
||||
if (clen >= sizeof tmp) clen = sizeof tmp - 1;
|
||||
memcpy(tmp, name + cut, clen);
|
||||
tmp[clen] = 0;
|
||||
if (ui_width(tmp, size) > budget) break;
|
||||
cut += clen;
|
||||
}
|
||||
snprintf(out, olen, "%s...", buf);
|
||||
if (cut == 0) { snprintf(out, olen, "%s", ell); return; }
|
||||
snprintf(out, olen, "%.*s%s", (int)cut, name, ell);
|
||||
}
|
||||
|
||||
|
||||
/* 图标绘制:primary 圆角垫块始终垫底(MD3 图标 tile),首字母叠加其上,最后有图叠加
|
||||
* meow_ui_img_cover(居中裁方)。
|
||||
* 修复:原实现 `if (icon) { meow_ui_img_cover; return; }` 在图标为透明背景 logo(不透明像素
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,57 @@
|
||||
/* meow_utf8.h — UTF-8 整序列有界拷贝/字符步进纯决策(宿主可单测;无 ui_width/mfont 依赖)。
|
||||
*
|
||||
* 背景(问题2,M6.3 真机):主界面应用名「流媒体客户端」被截成 5 字 + 半个字。两处根因:
|
||||
* ① app_launcher.c `strncpy(a->name, name, 16)` 仍把显示名硬截 16 字节(UTF-8 下=5 汉字+ 半个
|
||||
* 「端」)——M6.3 只扩了 name[49]+json_str 整序列,漏改此处;
|
||||
* ② main_menu.c `menu_name_fit` 按字节回退、边界判定错(do..while 读的是刚清零的字节,恒停续字节),
|
||||
* 停在半截多字节字符;再加 "..." 未为省略号留宽 → 溢出 maxw 被裁切。
|
||||
* 本头把 UTF-8 整序列处理抽成纯函数(meow_utf8_char_len / meow_utf8_copy_bounded / 截断决策),
|
||||
* 两处复用并可宿主单测(test_utf8_name.c)。
|
||||
*/
|
||||
#ifndef MEOW_UTF8_H
|
||||
#define MEOW_UTF8_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h> /* snprintf(有界拷贝用) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 由首字节 c0 求整字符 UTF-8 字节长(1..4)。非法首字节(0x80..0xBF 续字节)返回 1(按异常字节跳过,
|
||||
* 绝不把孤立续字节与后字并成大字符)。纯按前导位,不看后续字节是否齐全(那由 copy_bounded 处理)。 */
|
||||
static inline int meow_utf8_char_len(unsigned char c0)
|
||||
{
|
||||
if (c0 < 0x80) return 1;
|
||||
if (c0 < 0xC0) return 1; /* 孤立续字节:兜底 1 */
|
||||
if (c0 < 0xE0) return 2;
|
||||
if (c0 < 0xF0) return 3;
|
||||
return 4;
|
||||
}
|
||||
|
||||
/* 有界拷贝:把 src 拷入 dst(容量 cap,含 NUL),**绝不劈开多字节字符**。snprintf 截断若落在字符
|
||||
* 中间(或 src 本身尾部是半截字符),回退到整字符边界:删去最末不完整字符。返回拷贝后字节长(不含
|
||||
* NUL)。源可为 NULL。持久化/展示名均用——保证 UTF-8 整序列,杜绝「5 汉字+半个字」式残字。 */
|
||||
static inline int meow_utf8_copy_bounded(char *dst, int cap, const char *src)
|
||||
{
|
||||
if (!dst || cap <= 0) return 0;
|
||||
if (cap == 1) { dst[0] = 0; return 0; }
|
||||
if (!src) src = "";
|
||||
snprintf(dst, (size_t)cap, "%s", src);
|
||||
int n = (int)strlen(dst);
|
||||
if (n <= 0) return 0;
|
||||
/* 定位最末一个「起始字节」(非续字节):从尾部回退续字节。 */
|
||||
int last_start = n - 1;
|
||||
while (last_start > 0 && ((unsigned char)dst[last_start] & 0xC0) == 0x80)
|
||||
last_start--;
|
||||
/* 末字符:起始字节=last_start,实际字节数=have;字符应需 need。have<need → 不完整 → 删。 */
|
||||
int need = meow_utf8_char_len((unsigned char)dst[last_start]);
|
||||
int have = n - last_start;
|
||||
if (have < need) { dst[last_start] = 0; n = last_start; }
|
||||
return n;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* MEOW_UTF8_H */
|
||||
@@ -42,4 +42,15 @@ static inline int overlay_force_sb_resync(void)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 未冻结(媒体保活,keep_running)App 是否**需要 EVIOCGRAB 独占隔离**(返回 1)。
|
||||
* §310 问题1真根:媒体保活 App 从不 SIGSTOP(保 PCM),SDK 输入线程**实时读 evdev**(不走缓冲),
|
||||
* 标记(/tmp/meow_notif_active)让 SDK 排空只在有该新 SDK 的 App 上生效;launcher 侧可**无条件**
|
||||
* 用 EVIOCGRAB 独占触控设备,使包括保活 App 在内的**任何**前台进程在锁屏期收不到触摸(且抓取期
|
||||
* 事件不为未抓取 fd 缓冲/回放)。keep_alive=1 → 隔离靠 grab(overlay_drain_required==0,无
|
||||
* SIGCONT 排空语义);冻结 App(keep_running=0)→ 隔离靠 SIGSTOP+SIGCONT 排空(drain_required==1)。 */
|
||||
static inline int overlay_keepalive_needs_grab(int keep_running)
|
||||
{
|
||||
return keep_running ? 1 : 0;
|
||||
}
|
||||
|
||||
#endif /* MEOW_OVERLAY_GATE_H */
|
||||
@@ -8,6 +8,9 @@
|
||||
* 不等 app 循环 1s 慢刷(消除「解锁回 App 无状态栏」残留)。
|
||||
* S3 App 死亡触发主屏重绘 → lock_overlay_app_died_exit():锁屏叠加期 App 崩溃(僵尸)必须走
|
||||
* 统一退出路径(交回 waitpid 循环回收 + run_menu 重绘主屏含状态栏)。
|
||||
* S1b 未冻结 App 输入被排 → overlay_keepalive_needs_grab():媒体保活 App(保持运行、SDK 输入
|
||||
* 线程实时读 evdev)在锁屏期必须靠 launcher EVIOCGRAB 独占触控设备 +
|
||||
* 标记让 SDK 排空——而非冻结 App 的 SIGCONT 排空(drain_required=0)。
|
||||
*
|
||||
* 编译运行(在 patch_v2/meow_launcherd 下,无其它依赖):
|
||||
* gcc -Wall -Wextra -o test_overlay_gate test_overlay_gate.c && ./test_overlay_gate
|
||||
@@ -32,6 +35,17 @@ int main(void)
|
||||
* 标记再整批丢弃缓冲事件;此处断言契约不被踩线。 */
|
||||
CHECK(OVERLAY_DRAIN_WINDOW_MS > 200, "S1 drain window > SDK 200ms throttle");
|
||||
|
||||
/* ---- S1b 未冻结(媒体保活)App 输入被排:靠 EVIOCGRAB 独占隔离,非 SIGCONT 排空 ----
|
||||
* 保活 App 从不 SIGSTOP(保 PCM),SDK 输入线程实时读 evdev;launcher 只能靠 EVIOCGRAB 独占
|
||||
* 触控设备(真机 goodix 已证 EVIOCGRAB 返回 0)使任何前台进程锁屏期收不到触摸。 */
|
||||
CHECK(overlay_keepalive_needs_grab(1) == 1, "S1b keep-alive app needs EVIOCGRAB isolation");
|
||||
CHECK(overlay_keepalive_needs_grab(0) == 0, "S1b frozen app -> SIGCONT drain, not grab");
|
||||
/* 保活 App 隔离与冻结 App 排空互斥:一条路径而非两条。 */
|
||||
CHECK(overlay_drain_required(1) == 0 && overlay_keepalive_needs_grab(1) == 1,
|
||||
"S1b keep-alive: grab path (drain==0)");
|
||||
CHECK(overlay_drain_required(0) == 1 && overlay_keepalive_needs_grab(0) == 0,
|
||||
"S1b frozen: drain path (grab==0)");
|
||||
|
||||
/* ---- S2 解锁状态栏恢复:必须强制重同步 ---- */
|
||||
CHECK(overlay_force_sb_resync() == 1, "S2 force status bar re-sync after unlock");
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/* test_utf8_name.c — 问题2(主界面应用名截断)宿主单测:UTF-8 整序列有界拷贝 / 字符步进。
|
||||
*
|
||||
* 背景(M6.3 真机):主界面应用名「流媒体客户端」被截成 5 字 + 半个字。真因 = app_launcher.c
|
||||
* `strncpy(a->name, name, 16)` 仍把显示名硬截 16 字节(UTF-8 下 16B = 5 汉字 + 「端」首字节),且
|
||||
* main_menu.c `menu_name_fit` 按字节回退停在半截字符、加 "..." 又溢出 maxw。本测试验证 meow_utf8.h
|
||||
* 的整序列有界拷贝:绝不劈开多字节字符、给出完整汉字序列(修复 strncpy(16) 与 menu_name_fit)。
|
||||
*
|
||||
* 编译运行(在 patch_v2/meow_launcherd 下):
|
||||
* gcc -Wall -Wextra -o test_utf8_name test_utf8_name.c && ./test_utf8_name
|
||||
* 全 PASS -> exit 0。
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "meow_utf8.h"
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
#define CHECK(cond, msg) do { \
|
||||
if (cond) { g_pass++; printf("PASS %s\n", msg); } \
|
||||
else { g_fail++; printf("FAIL %s\n", msg); } \
|
||||
} while (0)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
/* meow_utf8_char_len:由首字节求整字符长。 */
|
||||
CHECK(meow_utf8_char_len('A') == 1, "char_len ASCII = 1");
|
||||
CHECK(meow_utf8_char_len((unsigned char)0xC3) == 2, "char_len 2-byte lead = 2");
|
||||
CHECK(meow_utf8_char_len((unsigned char)0xE6) == 3, "char_len 3-byte lead = 3");
|
||||
CHECK(meow_utf8_char_len((unsigned char)0xF0) == 4, "char_len 4-byte lead = 4");
|
||||
CHECK(meow_utf8_char_len((unsigned char)0x80) == 1, "char_len lone continuation = 1 (no merge)");
|
||||
|
||||
/* meow_utf8_copy_bounded:不劈多字节字符。 */
|
||||
char buf[64];
|
||||
/* 源「流媒体客户端」= 18B(6×3B)。cap=49 → 全量(修复 strncpy(16) 后的正确行为)。 */
|
||||
meow_utf8_copy_bounded(buf, (int)sizeof buf, "流媒体客户端");
|
||||
CHECK(strcmp(buf, "流媒体客户端") == 0, "copy cap=49 keeps full '流媒体客户端'");
|
||||
CHECK(strlen(buf) == 18, "copy cap=49 len=18 (6 chars)");
|
||||
|
||||
/* cap=16(旧 bug 的容量):旧 strncpy(16) 给 5 汉字+半个字+无NUL;现应给 5 整汉字+ NUL。 */
|
||||
meow_utf8_copy_bounded(buf, 16, "流媒体客户端");
|
||||
CHECK(strcmp(buf, "流媒体客户") == 0, "copy cap=16 -> 5 whole chars '流媒体客户'");
|
||||
CHECK(strlen(buf) == 15, "copy cap=16 -> 15 bytes (5 CJK, no partial 6th)");
|
||||
|
||||
/* cap=17:尾部「端」首字节越界 → 整字符去掉,仍 5 整汉字,绝不半字残形。 */
|
||||
meow_utf8_copy_bounded(buf, 17, "流媒体客户端");
|
||||
CHECK(strcmp(buf, "流媒体客户") == 0, "copy cap=17 -> still 5 whole chars (no partial)");
|
||||
|
||||
/* cap=12:缓冲区 12B 含 NUL → 内容上限 11B,4 整汉字(12B)放不下 → 截到 3 整汉字(绝非半字)。
|
||||
* 注:snprintf 语义 = cap 字节含 NUL,故内容 ≤ cap-1(旧 strncpy(dst,src,12) 拷 12B 无 NUL,
|
||||
* 有越界读风险;此处改为安全 NUL 结尾,代价是内容少 1B——正确取舍)。 */
|
||||
meow_utf8_copy_bounded(buf, 12, "流媒体客户端");
|
||||
CHECK(strcmp(buf, "流媒体") == 0, "copy cap=12 -> 3 whole chars '流媒体' (no partial 4th)");
|
||||
|
||||
/* ASCII / 混合:不劈 ASCII。 */
|
||||
meow_utf8_copy_bounded(buf, (int)sizeof buf, "Hello App");
|
||||
CHECK(strcmp(buf, "Hello App") == 0, "copy ASCII unchanged");
|
||||
|
||||
/* NULL/空源安全性。 */
|
||||
meow_utf8_copy_bounded(buf, (int)sizeof buf, NULL);
|
||||
CHECK(buf[0] == 0, "copy NULL src -> empty");
|
||||
meow_utf8_copy_bounded(buf, 1, "流媒体客户端");
|
||||
CHECK(buf[0] == 0, "copy cap=1 -> empty (no write overflow)");
|
||||
|
||||
printf("\n%s (%d passed, %d failed)\n", g_fail ? "SOME TESTS FAILED" : "ALL PASS", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user