fix(tpv): 去掉条目指针数组往返,改上界/量长直写(修自举 ASan strcat -1 崩溃)

This commit is contained in:
TermiNexus committed 2026-09-12 12:43:08 +08:00
1 parent 82eea2719f
commit 471bedbcf4
2 files changed
+45 -36

No files matched your search

+28 -24
View File
@@ -388,9 +388,10 @@ class TypeGenErr(TypeGenBase.TypeGenBase):
[D5·截断纪律] 旧实现按 `an * 32 + 1` 预估缓冲,再用 `_strappend` 以
`remain - 1` 截断且**不报错**:参数名偏长(或参数偏多)时编码串被静默
截断 ⇒ `infer_expr_type` 的 `strstr(param_types, "name:")` 查找失配 /
命中他人前缀 ⇒ 静默回落 Int32(类型误判)。现改为**两遍精确分配**:
先逐参数生成条目(指针暂存数组)并累计长度,再按精确长度分配后拼接——
不存在长度上限,故无需截断;`_strappend` 一并删除(唯一调用点在本函数)。
命中他人前缀 ⇒ 静默回落 Int32(类型误判)。现改为**上界预估 + 单遍就地
拼接**:段一是只扫参数名的上界(name + ':' + 位宽 + ',' + NUL),段二是
解析类型后逐条目就地格式化并立即拼接 —— 缓冲恒够,不存在截断;
`_strappend` 一并删除(唯一调用点在本函数)。
"""
if args_node is None:
return None
@@ -402,11 +403,25 @@ class TypeGenErr(TypeGenBase.TypeGenBase):
if an == 0:
return None
# 1) 逐参数生成条目 "name:bits"(精确分配),指针暂存
ents: sizet | t.CPtr = pool.alloc(an * 8)
if ents is None:
# 1) 上界估长(**只扫参数名**,不解析类型也不需要暂存结果指针):
# 每条 = name + ':' + 位宽(int32 十进制最长 11 字符含符号)+ ',';再加 NUL。
# ⚠ 这里必须"上界"而非"精确":此前版本把每条目指针存进 sizet 数组、
# 再取出转回指针 strcat,自举出的二进制上取回了 -1(0xffffffffffffffff)
# ⇒ ASan: access-violation in strcat(build_param_types_str)。
# 改为上界缓冲后**不需要数组往返**,每条目就地格式化、立即拼接。
bound: sizet = 1 # 末尾 NUL
pre_i: sizet
for pre_i in range(an):
pre_arg: ast.Arg | t.CPtr = (ast.Arg | t.CPtr)(alist.get(pre_i))
if pre_arg is None or pre_arg.arg is None:
continue
bound += string.strlen(pre_arg.arg) + 1 + 12 + 1
buf: str = pool.alloc(bound)
if buf is None:
return None
total: sizet = 1 # 末尾 NUL
buf[0] = '\0'
# 2) 单遍:解析类型 → 就地格式化条目 → 立即拼接(缓冲为上界,不会截断)
cnt: sizet = 0
i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
for ai in range(an):
@@ -427,28 +442,17 @@ class TypeGenErr(TypeGenBase.TypeGenBase):
if bits == 0:
bits = 32
nlen: sizet = string.strlen(arg.arg)
# name + ':' + 十进制位宽(int32 最长 11 字符)+ NUL
ent: str = pool.alloc(nlen + 1 + 12 + 1)
# 条目缓冲:name + ':' + 位宽(预留 16 字节,足够 %d 任意取值)+ NUL
ent: str = pool.alloc(nlen + 1 + 16 + 1)
if ent is None:
continue
string.strcpy(ent, arg.arg)
ent[nlen] = ':'
viperlib.snprintf(ent + nlen + 1, 12, "%d", bits)
ents[cnt] = sizet(ent)
viperlib.snprintf(ent + nlen + 1, 16, "%d", bits)
if cnt > 0:
string.strcat(buf, ",")
string.strcat(buf, ent)
cnt += 1
total += nlen + 1 + string.strlen(ent + nlen + 1)
if cnt == 0:
return None
if cnt > 1:
total += cnt - 1 # 分隔符 ","
# 2) 按精确长度分配 + 拼接(不会截断)
buf: str = pool.alloc(total)
if buf is None:
return None
buf[0] = '\0'
for i2 in range(cnt):
if i2 > 0:
string.strcat(buf, ",")
string.strcat(buf, (str | t.CPtr)(t.CVoid(ents[i2], t.CPtr)))
return buf
+17 -12
View File
@@ -163,13 +163,11 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
if elts is None or pool is None:
return None
n3: sizet = len(elts)
# 1) 解析全部元素(结果指针暂存,避免二次解析)
es_arr: sizet | t.CPtr = None
if n3 > 0:
es_arr = pool.alloc(n3 * 8)
if es_arr is None:
return None
total3: sizet = 2 # open + close
# 1) 量长(解析结果用完即弃):**不把元素指针暂存进数组再取回**——
# 该「sizet 数组往返再转回指针」的写法在自举出的二进制上会取回 -1
# (0xffffffffffffffff)⇒ ASan access-violation in strcat(同类现场见
# TypeGenErr.build_param_types_str)。此处改为二次解析 + 直写。
total3: sizet = 2 # open + close(各按 1 字节算;实参为空串时多算无害)
cnt3: sizet = 0
for i3 in range(n3):
e3: ast.AST | t.CPtr = elts.get(i3)
@@ -181,21 +179,28 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
self.Gen.TypeGenErr.fatal_error(
e3, "类型注解的元素无法解析为类型名(列表/元组注解)", "NameError")
return None
es_arr[cnt3] = sizet(es3)
cnt3 += 1
total3 += string.strlen(es3)
if cnt3 > 1:
total3 += (cnt3 - 1) * 2 # 分隔符 ", "
# 2) 按精确长度分配 + 拼接(无上限,不会截断)
# 2) 按精确长度分配 + 二次解析直写(无上限,不会截断)
buf4: str = pool.alloc(total3 + 1)
if buf4 is None:
return None
buf4[0] = '\0'
string.strcat(buf4, open_ch)
for i4 in range(cnt3):
if i4 > 0:
wrote3: sizet = 0
for i4 in range(n3):
e4: ast.AST | t.CPtr = elts.get(i4)
if e4 is None:
continue
es4: str = self._subscript_slice_to_str(pool, e4)
if es4 is None:
continue # 第一遍已校验过,此处不会发生
if wrote3 > 0:
string.strcat(buf4, ", ")
string.strcat(buf4, (str | t.CPtr)(t.CVoid(es_arr[i4], t.CPtr)))
string.strcat(buf4, es4)
wrote3 += 1
string.strcat(buf4, close_ch)
return buf4
def _is_sha1_prefixed(self, s: str) -> int: