fix(viperlib): 浮点格式化的 inf/nan 保护失效导致 %g 死循环

根因: _check_special_float 的位双关 (把 double 位模式读成 uint64) 被 TPC 前端静默丢弃——IR 里 fbits 恒为 0 (store i64 0 后直接 load,没有把 fv 写进 fbits),函数恒返回 0,于是 %f/%e/%g/%a 的 inf/nan 保护全部失效。

后果: inf 落入 %g 的指数归一化循环 while fv>=10.0 (inf/10==inf 恒真) → 死循环,实测卡死在 vsnprintf:974(Ctrl+C 栈转储权威帧 #0)。

修复: _check_special_float 改为纯浮点语义判定 (fv!=fv 判 NaN;fv-fv!=fv-fv 判 +-inf);另给 4 处对 inf 不收敛的循环各加迭代上界 400 (正常最多约 308 次) 作纵深防御。

注: 位双关语句被静默丢弃属 TPC 前端缺陷,需另立项;va_arg(ap, double) 经生成 IR 核对是正确的 (va_arg 内建读 XMM 保存区且 gp/fp 各自推进),非本次原因。
This commit is contained in:
TermiNexus committed 2026-09-25 13:15:04 +08:00
1 parent b208eae8f3
commit 093ff5f66d
1 file changed
+37 -15
+37 -15
View File
@@ -28,17 +28,23 @@ export def snprintf(buf: str, size: sizet, fmt: str, *args) -> int:
ret: int = vsnprintf(buf, size, fmt, args)
return ret
# Check floating-point special values (IEEE 754)
# Return: 0=normal, 1=+inf, 2=-inf, 3=nan
# 检查浮点特殊值(IEEE 754)
# 返回:0=正常, 1=+inf, 2=-inf, 3=nan
# 注意:不可用位双关 `*&fbits = *&fv` 读取 IEEE 位模式——该赋值会被编译器静默
# 丢弃(IR 中 fbits 恒为 0,见 TransPyV/output/db514c1af999c5f5.text.ll),会使
# 本函数恒返回 0、特殊值检查彻底失效,进而让 inf 进入浮点格式化循环而死循环。
# 这里改用纯浮点语义判定,不依赖任何位操作:
# NaN :fv != fv 为真
# ±inf:fv - fv 得到 NaN(有限值得到 0.0)
def _check_special_float(fv: double) -> int:
fbits: uint64 = 0
*&fbits = *&fv
if ((fbits >> 52) & 0x7FF) != 0x7FF: return 0
is_neg: int = 1 if (fbits >> 63) != 0 else 0
if (fbits & 0x000FFFFFFFFFFFFF) == 0:
if is_neg: return 2
if fv != fv:
return 3
diff: double = fv - fv
if diff != diff:
if fv < 0.0:
return 2
return 1
return 3
return 0
export def vsnprintf(buf: bytes, size: sizet, fmt: str, ap: str) -> int:
if fmt is None: return 0
@@ -626,8 +632,11 @@ export def vsnprintf(buf: bytes, size: sizet, fmt: str, ap: str) -> int:
di = 1
else:
scale: double = 1.0
while work >= scale * 10.0:
# 上界防护:inf 会让 `work >= scale*10.0` 永真,正常有限值最多 308 次
scale_guard: int = 0
while work >= scale * 10.0 and scale_guard < 400:
scale *= 10.0
scale_guard += 1
while scale >= 1.0 and di < TEMP_BUF_SIZE - 1:
d: int = int(work / scale)
if d > 9: d = 9
@@ -779,12 +788,17 @@ export def vsnprintf(buf: bytes, size: sizet, fmt: str, ap: str) -> int:
ptr += _step
exp10: int = 0
if fv != 0.0:
while fv >= 10.0:
# 上界防护:inf 会让下面两个循环永真(inf/10==inf、inf*10==inf)
exp_guard: int = 0
while fv >= 10.0 and exp_guard < 400:
fv /= 10.0
exp10 += 1
while fv < 1.0:
exp_guard += 1
exp_guard = 0
while fv < 1.0 and exp_guard < 400:
fv *= 10.0
exp10 -= 1
exp_guard += 1
int_digit: int = int(fv)
if int_digit > 9: int_digit = 9
if int_digit < 0: int_digit = 0
@@ -971,12 +985,17 @@ export def vsnprintf(buf: bytes, size: sizet, fmt: str, ap: str) -> int:
fv_orig: double = fv
exp10: int = 0
if fv != 0.0:
while fv >= 10.0:
# 上界防护:卡死现场即此循环(inf 会让 fv>=10.0 永真,inf/10==inf)
exp_guard: int = 0
while fv >= 10.0 and exp_guard < 400:
fv /= 10.0
exp10 += 1
while fv < 1.0:
exp_guard += 1
exp_guard = 0
while fv < 1.0 and exp_guard < 400:
fv *= 10.0
exp10 -= 1
exp_guard += 1
g_use_e: int = 0
if exp10 < -4 or exp10 >= precision:
g_use_e = 1
@@ -1029,8 +1048,11 @@ export def vsnprintf(buf: bytes, size: sizet, fmt: str, ap: str) -> int:
g_len = 1
else:
scale: double = 1.0
while work >= scale * 10.0:
# 上界防护:inf 会让 `work >= scale*10.0` 永真,正常有限值最多 308 次
scale_guard: int = 0
while work >= scale * 10.0 and scale_guard < 400:
scale *= 10.0
scale_guard += 1
while scale >= 1.0 and g_len < TEMP_BUF_SIZE - 1:
d: int = int(work / scale)
if d > 9: d = 9