fix(gen): 消除 ViperC 侧 P0 级静默截断

match:case 数超过 MAX_MATCH_CASES 时 fail-fast(原先 break 会静默少分支,need_case 检查也发现不了)。vtable:初始化串按真实方法表动态分配,_vtable_strcat 溢出改 fatal;VTBL_METHOD_MAX 超限不再 break(原先子类 override 静默失效)。符号名:coro_pool_/__tls_init_done_/__decor_rec_/闭包 fn_ptr 的定义与引用改为按实际长度分配,消除 96 vs 128、64 vs 128 的不对称。Stub:_PyiEmit 溢出改 VLogger.error(半截 .vpi 会被当完整文件解析);VpiReader 参数段/返回注解缓存槽放不下时 fail-fast,不再丢段导致形参注解错位。Includes:文件超过 1MiB 拒绝哈希(SHA1 冲突会让两个模块共用缓存);add_file_entry 失败不再被忽略。f-string:格式串按字面量/转换符实际长度分配,字面量与 format_spec 溢出 fail-fast。
This commit is contained in:
TermiNexus committed 2026-10-17 18:46:18 +08:00
1 parent 15952d3852
commit 3e1e1fc1b3
10 files changed
+183 -72

No files matched your search

+11 -6
View File
@@ -144,15 +144,20 @@ def _pyi_cdefine_found() -> int:
# ============================================================
# _PyiEmit - append text to buf at pos, returning the new pos (truncates on overflow)
# _PyiEmit - append text to buf at pos, returning the new pos (overflow is fatal)
def _PyiEmit(buf: bytes, buf_size: sizet, pos: sizet, text: str) -> sizet:
"""Append text to buf at pos and return the new pos"""
if buf is None or text is None or pos >= buf_size: return pos
"""Append text to buf at pos and return the new pos.
Overflow used to truncate silently, and the truncated .vpi was then parsed as if complete:
the consuming module silently lost declarations or got half a signature.
"""
if buf is None or text is None: return pos
tlen: sizet = string.strlen(text)
if tlen == 0: return pos
remain: sizet = buf_size - pos
if tlen >= remain: tlen = remain - 1
if tlen > 0: string.memcpy(buf + pos, text, tlen)
if pos + tlen + 1 > buf_size:
VLogger.error(f"generated .vpi stub text exceeds the {buf_size}-byte buffer; raise PYI_BUF_SIZE", "StubGen")
return pos
string.memcpy(buf + pos, text, tlen)
return pos + tlen
+13
View File
@@ -400,6 +400,11 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
cls_len2: sizet = 0
if cur_cls is not None: cls_len2 = string.strlen(cur_cls)
key_len: sizet = sha1_len + 1 + cls_len2 + 1 + mname_len
# fail-fast: cache entries are fixed-size slots. Skipping a too-long key or
# return text leaves the method without a cached return type, and callers
# then fall back to i32 (pointer returns get truncated).
if key_len >= VPI_KEY_LEN or rtype_len >= VPI_RET_LEN:
VLogger.error(f"return-annotation cache slot too small (key {key_len}/{VPI_KEY_LEN} bytes, return {rtype_len}/{VPI_RET_LEN} bytes); shorten the class/method name or the return annotation", "VpiReader")
if key_len < VPI_KEY_LEN and rtype_len < VPI_RET_LEN:
# copy method name to a temp buffer (vpi_buf+dstart is not NUL-terminated)
mname_buf: bytes = stdlib.malloc(mname_len + 1)
@@ -575,6 +580,10 @@ def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
while a_start < s_end and vpi_buf[a_start] == ' ': a_start += 1
a_end: sizet = s_end
while a_end > a_start and vpi_buf[a_end - 1] == ' ': a_end -= 1
# fail-fast: dropping a segment shifts every later parameter's annotation onto
# the wrong parameter (declare/define type mismatch), which is worse than a miss.
if plist_pos + (a_end - a_start) + 1 >= VPI_PARAM_LEN:
VLogger.error(f"parameter annotation list exceeds the {VPI_PARAM_LEN}-byte cache slot; shorten this method's annotations", "VpiReader")
if plist_pos + (a_end - a_start) + 1 < VPI_PARAM_LEN:
if seg_written > 0:
plist[plist_pos] = '@'
@@ -592,6 +601,10 @@ def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
cls_len2: sizet = 0
if cur_cls is not None: cls_len2 = string.strlen(cur_cls)
key_len: sizet = sha1_len + 1 + cls_len2 + 1 + mname_len
if key_len >= VPI_PARAM_KEY_LEN:
# fail-fast: a skipped key silently loses the whole parameter annotation
# list (the caller then rebuilds declare params from the degraded text).
VLogger.error(f"parameter-annotation cache key exceeds {VPI_PARAM_KEY_LEN} bytes (class/method name too long)", "VpiReader")
if key_len < VPI_PARAM_KEY_LEN:
mname_buf: bytes = stdlib.malloc(mname_len + 1)
if mname_buf is not None: