xiaofami
0.16D

libgdiplus 吞字 bug

  •  1
     
  •   xiaofami · 1 day ago · 737 views

    这几天想把一个 dotNET WinForm 程序移植到 Linux 平台,界面上某些标签最后一个字老是不显示。经过分析定位到了 libgdiplus 并完成修复。鉴于 libgdiplus 已经凉了上游不太可能修复,发出来给大家闭坑。

    环境

    银河麒麟 V10 SP1 ( glibc 2.31 ,x86_64 )、libgdiplus 6.0.4 、Mono 6.8 。

    复现

    #include <stdio.h>
    #include <ctype.h>
    #include <locale.h>
    int main(void) {
        setlocale(LC_ALL, "");
        printf("isspace(般) = %d\n", isspace(0x822C));
    }
    
    $ gcc demo.c -o a.out
    $ LC_ALL=zh_CN.UTF-8 ./a.out
    isspace(般) = 8192      ← 8192 = 0x2000 = _ISspace ,被判成空格了
    $ LC_ALL=C ./a.out
    isspace(般) = 0
    

    值和库相同,仅仅 locale 不同结果就不一致。在 zh_CN.UTF-8 / en_US.UTF-8 + glibc 2.31 上实测,这 32 个常用字作为末字时会被吞:

    也 自 而 第 美 从 但 前 行 动 位 名 使 力 公 做
    十 像 加 表 克 员 稜 先 住 色 候 变 呢 低 般 健
    

    bug 位置

    拉取 libgdiplus 6.0.4 源码后一路测试,定位到了 src/text-cairo.c 的
    MeasureString():

    /* 除非指定要量尾随空格,否则裁掉( GDI+ 的老行为,issue #80680 ) */
    if ((format->formatFlags & StringFormatFlagsMeasureTrailingSpaces) == 0) {
        while ((StringLen > 0) && (isspace ((int) ( *(Src + StringLen - 1)))))
            StringLen--;
        if (StringLen == 0)
            StringLen = 1;
    }
    

    Src 是 GDIPCONST WCHAR *,也就是 16 位宽的 UTF-16 码元;而 isspace() 是 ctype.h
    里的窄字符函数,只对 EOF 和 0–255 有定义。把 般( U+822C = 33324 )传进去,是标准的
    未定义行为。

    修复

    -		while ((StringLen > 0) && (isspace ((int) ( *(Src + StringLen - 1)))))
    +		while ((StringLen > 0) && (g_unichar_isspace ((gunichar)(*(Src + StringLen - 1)))))
     			StringLen--;
    
    1 replies  •  2026-11-10 10:57:17 +08:00
    wfg
    wfg
    1 day ago via iPhone
    1
    虽然我用不上,但是讲的很清楚,当故事看了,感谢分享
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   2189 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 14:18 · PVG 22:18 · LAX 06:18 · JFK 09:18
    ♥ Do have faith in what you're doing.