feat(len): len() 对「非 i8 裸指针 / 无 __len__ 的 struct」改为 fail-fast(对齐 TransPyV)

旧实现:非 i8 裸指针(如 []T 形参实为 T*)静默返回常量 0;struct 无 __len__ 时回落 strlen —— 两者都用假长度掩盖错误。

新口径(两侧逐字一致):定长数组/数组指针 → 常量 N;i8*(str/bytes)→ strlen;带 __len__ 的结构体 → 调 __len__;其余一律编译期 TypeError。

新增 _LEN_SUPPORTED_HINT 共享提示语 + _FailFastLenUnsupported 出口(含 行:列 + 源码片段 + 正确做法)。HandlesExprCall.py 为同源死代码副本,一并同步语义未删。
This commit is contained in:
TermiNexus committed 2026-09-28 15:53:02 +08:00
1 parent 97c196bfc7
commit 809f9d08fa
2 files changed
+164 -48

No files matched your search

+82 -24
View File
@@ -13,6 +13,15 @@ from lib.includes import t as _t
from lib.core.SymbolUtils import IsTModule
# len() 参数形态不支持时的共享提示语(与 TransPyV App/lib/core/Gen/Call/
# CallGenDispatch.vp 的 len() 分支同口径,措辞逐字一致,便于用户对照两侧报文)。
_LEN_SUPPORTED_HINT: str = (
"len() 只支持定长数组/数组指针(t.CArray[T, N])、i8*(str/bytes)或带 __len__ 的结构体;"
"非 i8 裸指针/标量指针没有长度语义:字符串请用 str/bytes,需要元素个数请自行记录长度,"
"或改用 t.CPtr[元素] 并显式维护元素计数"
)
class ExprBuiltinHandle(BaseHandle):
_C_LIB_FUNCS: dict[str, object] = {
'strlen': lambda: (ir.IntType(64), [ir.PointerType(ir.IntType(8))]),
@@ -571,6 +580,41 @@ class ExprBuiltinHandle(BaseHandle):
Gen.functions[func_name] = func
return func
def _FailFastLenUnsupported(self, arg_node: ast.AST, Gen: LlvmGeneratorMixin,
val: ir.Value | None, reason: str) -> None:
"""len(x) 参数形态不支持时的编译期 fail-fast 出口(TPC 与 TransPyV 同口径)。
规则(对齐 TransPyV App/lib/core/Gen/Call/CallGenDispatch.vp 的 len() 分支):
定长数组/数组指针 → 常量 N;i8*(str/bytes)→ strlen;
带 __len__ 的结构体 → 调用 __len__;其余一律编译期报错。
非 i8 裸指针(如 `[]T` 形参实为 `T*`)与标量指针**没有长度语义**,旧实现会
静默返回常量 0(或回落 strlen)从而掩盖错误,这里改为响亮失败。
报文含 行:列 + 源码片段 + 期望形态 + 正确做法提示。
"""
raw_lineno: int = getattr(arg_node, 'lineno', 0) or 0
lineno: int = raw_lineno or getattr(Gen, '_current_lineno', 0) or 0
col: int = getattr(arg_node, 'col_offset', -1)
src_file: str = getattr(Gen, '_current_source_file', '') or ''
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and lineno > _line_offset:
lineno -= _line_offset
code_line: str = ''
if lineno > 0 and hasattr(Gen, '_get_source_line'):
try:
code_line = (Gen._get_source_line(lineno) or '').strip()
except Exception:
code_line = ''
loc: str = src_file
if lineno > 0:
loc += f":{lineno}"
if col >= 0:
loc += f":{col}"
actual: str = str(val.type) if val is not None else '<未知>'
msg: str = (f"[fast-fail] {reason}。{_LEN_SUPPORTED_HINT}。"
f"实际类型 '{actual}'。位置 {loc}"
+ (f",源码: {code_line}" if code_line else ''))
raise TypeError(msg)
def _HandleLenLlvm(self, arg_node: ast.AST) -> ir.Value | None:
Gen: LlvmGeneratorMixin = self.Trans.LlvmGen
# len(obj.attr):obj 可为 self 或任意局部变量(VpiReader 的
@@ -657,37 +701,51 @@ class ExprBuiltinHandle(BaseHandle):
val = Gen._load(val, name="len_deref")
pointee = val.type.pointee
if isinstance(pointee, (ir.IdentifiedStructType, ir.LiteralStructType)):
# struct(或任何非基本类型)→ 仍先走既有 __len__ 分派(保持不变);
# 没有 __len__ 时由旧实现的「回落 strlen」改为 fail-fast(与 TPV 同口径)。
_matched_cn: str | None = None
for CN, ST in Gen.structs.items():
if pointee == ST:
result = self.Trans.ExprUtils._try_operator_overLoad(CN, '__len__', val, Gen)
if result is not None:
return result
_matched_cn = CN
break
if _matched_cn is not None:
result = self.Trans.ExprUtils._try_operator_overLoad(_matched_cn, '__len__', val, Gen)
if result is not None:
return result
self._FailFastLenUnsupported(arg_node, Gen, val, f"len() 参数类型 '{_matched_cn}' 没有 __len__ 方法")
else:
# 未在 Gen.structs 登记的 struct(opaque/匿名)→ fail-fast。
self._FailFastLenUnsupported(arg_node, Gen, val, "len() 参数类型不支持(非字符串/数组/对象)")
if isinstance(pointee, ir.ArrayType):
return ir.Constant(ir.IntType(64), pointee.count)
if isinstance(pointee, (ir.FloatType, ir.DoubleType, ir.IntType)) and pointee.width != 8:
return ir.Constant(ir.IntType(64), 0)
if val.type != ir.PointerType(ir.IntType(8)):
val = Gen.builder.bitcast(val, ir.PointerType(ir.IntType(8)), name="str_ptr_cast")
correct_strlen_type = ir.FunctionType(ir.IntType(64), [ir.PointerType(ir.IntType(8))])
strlen_func = None
if 'strlen' in Gen.functions:
existing = Gen.functions['strlen']
if existing.ftype == correct_strlen_type:
strlen_func = existing
if not strlen_func:
try:
strlen_func = ir.Function(Gen.module, correct_strlen_type, name='strlen')
Gen.functions['strlen'] = strlen_func
except Exception as _e:
if _config_mode == "strict":
self.Trans.LogWarning(f"异常被忽略: {_e}")
if strlen_func:
result = Gen.builder.call(strlen_func, [val], name="strlen_call")
return result
# i8*(含 str/bytes 指针形态)→ strlen(保持不变)。
if isinstance(pointee, ir.IntType) and pointee.width == 8:
if val.type != ir.PointerType(ir.IntType(8)):
val = Gen.builder.bitcast(val, ir.PointerType(ir.IntType(8)), name="str_ptr_cast")
correct_strlen_type = ir.FunctionType(ir.IntType(64), [ir.PointerType(ir.IntType(8))])
strlen_func = None
if 'strlen' in Gen.functions:
existing = Gen.functions['strlen']
if existing.ftype == correct_strlen_type:
strlen_func = existing
if not strlen_func:
try:
strlen_func = ir.Function(Gen.module, correct_strlen_type, name='strlen')
Gen.functions['strlen'] = strlen_func
except Exception as _e:
if _config_mode == "strict":
self.Trans.LogWarning(f"异常被忽略: {_e}")
if strlen_func:
result = Gen.builder.call(strlen_func, [val], name="strlen_call")
return result
return None
# 非 i8 裸指针 / 标量指针(i32*/i64*/float*/double*)→ fail-fast
# (旧实现静默返回常量 0 或回落 strlen,与 TPV 口径不符)。
self._FailFastLenUnsupported(arg_node, Gen, val, "len() 参数类型不支持(非字符串/数组/对象)")
elif isinstance(val.type, ir.ArrayType):
return ir.Constant(ir.IntType(64), val.type.count)
return ir.Constant(ir.IntType(64), 0)
# 其它不支持形态(标量值 / 结构体值等)→ fail-fast(旧实现静默返回常量 0)。
self._FailFastLenUnsupported(arg_node, Gen, val, "len() 参数类型不支持(非字符串/数组/对象)")
def _resolve_nested_opaque_structs(self, struct_type: ir.Type, Gen: LlvmGeneratorMixin, visited: set | None = None) -> None:
"""递归解析嵌套的 opaque struct,从 stub 文件中加载其定义"""
+82 -24
View File
@@ -19,6 +19,15 @@ from lib.core.SymbolUtils import (
from lib.core.Handles.HandlesExpr import DefineUseWidthFromLLVMType
# len() 参数形态不支持时的共享提示语(与 TransPyV App/lib/core/Gen/Call/
# CallGenDispatch.vp 的 len() 分支同口径,措辞逐字一致,便于用户对照两侧报文)。
_LEN_SUPPORTED_HINT: str = (
"len() 只支持定长数组/数组指针(t.CArray[T, N])、i8*(str/bytes)或带 __len__ 的结构体;"
"非 i8 裸指针/标量指针没有长度语义:字符串请用 str/bytes,需要元素个数请自行记录长度,"
"或改用 t.CPtr[元素] 并显式维护元素计数"
)
def _safe_ptr_type(ty: ir.Type) -> ir.Type:
"""LLVM IR 中不允许 void* 指针,Ptr(VoidType) 统一替换为 i8*(对应 C 的 void* 语义)"""
if isinstance(ty, ir.PointerType):
@@ -3888,6 +3897,41 @@ class ExprCallHandle(BaseHandle):
Gen.functions[func_name] = func
return func
def _FailFastLenUnsupported(self, arg_node: ast.AST, Gen: LlvmGeneratorMixin,
val: ir.Value | None, reason: str) -> None:
"""len(x) 参数形态不支持时的编译期 fail-fast 出口(TPC 与 TransPyV 同口径)。
规则(对齐 TransPyV App/lib/core/Gen/Call/CallGenDispatch.vp 的 len() 分支):
定长数组/数组指针 → 常量 N;i8*(str/bytes)→ strlen;
带 __len__ 的结构体 → 调用 __len__;其余一律编译期报错。
非 i8 裸指针(如 `[]T` 形参实为 `T*`)与标量指针**没有长度语义**,旧实现会
静默返回常量 0(或回落 strlen)从而掩盖错误,这里改为响亮失败。
报文含 行:列 + 源码片段 + 期望形态 + 正确做法提示。
"""
raw_lineno: int = getattr(arg_node, 'lineno', 0) or 0
lineno: int = raw_lineno or getattr(Gen, '_current_lineno', 0) or 0
col: int = getattr(arg_node, 'col_offset', -1)
src_file: str = getattr(Gen, '_current_source_file', '') or ''
_line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0)
if _line_offset > 0 and lineno > _line_offset:
lineno -= _line_offset
code_line: str = ''
if lineno > 0 and hasattr(Gen, '_get_source_line'):
try:
code_line = (Gen._get_source_line(lineno) or '').strip()
except Exception:
code_line = ''
loc: str = src_file
if lineno > 0:
loc += f":{lineno}"
if col >= 0:
loc += f":{col}"
actual: str = str(val.type) if val is not None else '<未知>'
msg: str = (f"[fast-fail] {reason}。{_LEN_SUPPORTED_HINT}。"
f"实际类型 '{actual}'。位置 {loc}"
+ (f",源码: {code_line}" if code_line else ''))
raise TypeError(msg)
def _HandleLenLlvm(self, arg_node: ast.AST) -> ir.Value | None:
Gen = self.Trans.LlvmGen
if isinstance(arg_node, ast.Attribute) and isinstance(arg_node.value, ast.Name) and arg_node.value.id == 'self':
@@ -3949,37 +3993,51 @@ class ExprCallHandle(BaseHandle):
val = Gen._load(val, name="len_deref")
pointee = val.type.pointee
if isinstance(pointee, (ir.IdentifiedStructType, ir.LiteralStructType)):
# struct(或任何非基本类型)→ 仍先走既有 __len__ 分派(保持不变);
# 没有 __len__ 时由旧实现的「回落 strlen」改为 fail-fast(与 TPV 同口径)。
_matched_cn: str | None = None
for CN, ST in Gen.structs.items():
if pointee == ST:
result = self.Trans.ExprUtils._try_operator_overLoad(CN, '__len__', val, Gen)
if result is not None:
return result
_matched_cn = CN
break
if _matched_cn is not None:
result = self.Trans.ExprUtils._try_operator_overLoad(_matched_cn, '__len__', val, Gen)
if result is not None:
return result
self._FailFastLenUnsupported(arg_node, Gen, val, f"len() 参数类型 '{_matched_cn}' 没有 __len__ 方法")
else:
# 未在 Gen.structs 登记的 struct(opaque/匿名)→ fail-fast。
self._FailFastLenUnsupported(arg_node, Gen, val, "len() 参数类型不支持(非字符串/数组/对象)")
if isinstance(pointee, ir.ArrayType):
return ir.Constant(ir.IntType(64), pointee.count)
if isinstance(pointee, (ir.FloatType, ir.DoubleType, ir.IntType)) and pointee.width != 8:
return ir.Constant(ir.IntType(64), 0)
if val.type != ir.PointerType(ir.IntType(8)):
val = Gen.builder.bitcast(val, ir.PointerType(ir.IntType(8)), name="str_ptr_cast")
correct_strlen_type = ir.FunctionType(ir.IntType(64), [ir.PointerType(ir.IntType(8))])
strlen_func = None
if 'strlen' in Gen.functions:
existing = Gen.functions['strlen']
if existing.ftype == correct_strlen_type:
strlen_func = existing
if not strlen_func:
try:
strlen_func = ir.Function(Gen.module, correct_strlen_type, name='strlen')
Gen.functions['strlen'] = strlen_func
except Exception as _e:
if _config_mode == "strict":
self.Trans.LogWarning(f"异常被忽略: {_e}")
if strlen_func:
result = Gen.builder.call(strlen_func, [val], name="strlen_call")
return result
# i8*(含 str/bytes 指针形态)→ strlen(保持不变)。
if isinstance(pointee, ir.IntType) and pointee.width == 8:
if val.type != ir.PointerType(ir.IntType(8)):
val = Gen.builder.bitcast(val, ir.PointerType(ir.IntType(8)), name="str_ptr_cast")
correct_strlen_type = ir.FunctionType(ir.IntType(64), [ir.PointerType(ir.IntType(8))])
strlen_func = None
if 'strlen' in Gen.functions:
existing = Gen.functions['strlen']
if existing.ftype == correct_strlen_type:
strlen_func = existing
if not strlen_func:
try:
strlen_func = ir.Function(Gen.module, correct_strlen_type, name='strlen')
Gen.functions['strlen'] = strlen_func
except Exception as _e:
if _config_mode == "strict":
self.Trans.LogWarning(f"异常被忽略: {_e}")
if strlen_func:
result = Gen.builder.call(strlen_func, [val], name="strlen_call")
return result
return None
# 非 i8 裸指针 / 标量指针(i32*/i64*/float*/double*)→ fail-fast
# (旧实现静默返回常量 0 或回落 strlen,与 TPV 口径不符)。
self._FailFastLenUnsupported(arg_node, Gen, val, "len() 参数类型不支持(非字符串/数组/对象)")
elif isinstance(val.type, ir.ArrayType):
return ir.Constant(ir.IntType(64), val.type.count)
return ir.Constant(ir.IntType(64), 0)
# 其它不支持形态(标量值 / 结构体值等)→ fail-fast(旧实现静默返回常量 0)。
self._FailFastLenUnsupported(arg_node, Gen, val, "len() 参数类型不支持(非字符串/数组/对象)")
def _resolve_opaque_struct_members(self, struct_type: ir.Type, Gen: LlvmGeneratorMixin, _visited: set | None = None) -> None:
"""Recursively resolve opaque struct members by loading from stub files."""