diff --git a/App/lib/Stub/StubGen/Converter.vp b/App/lib/Stub/StubGen/Converter.vp index 0772eeb..f8754a1 100644 --- a/App/lib/Stub/StubGen/Converter.vp +++ b/App/lib/Stub/StubGen/Converter.vp @@ -890,6 +890,12 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, if k == ast.ASTKind.Import: imp2: ast.Import* = ast.Import*(stmt) pos = _PyiEmit(buf, buf_size, pos, "import ") + # fail-fast:别名列表为空 ⇒ 写出 `import ` 这种残缺行,import 名字 + # 全丢,包重导出注册随之失效(与下面逐个别名为空的检查互补)。 + if imp2.names is None or len(imp2.names) == 0: + GenApi.fatal_error(stmt, + "vpi 生成:import 语句别名列表为空(应为 ≥1 个别名)——AST 构造/解析异常", + "NameError") if imp2.names is not None: imp2_names: list[ast.AST*]* = imp2.names an2: sizet = len(imp2_names) @@ -898,11 +904,29 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, if ai2 > 0: pos = _PyiEmit(buf, buf_size, pos, ", ") al2: ast.Alias* = ast.Alias*(imp2_names.get(ai2)) - if al2 is not None and al2.name is not None: - pos = _PyiEmit(buf, buf_size, pos, al2.name) - if al2.asname is not None and string.strlen(al2.asname) > 0: - pos = _PyiEmit(buf, buf_size, pos, " as ") - pos = _PyiEmit(buf, buf_size, pos, al2.asname) + # fail-fast:别名节点或其名字为空 ⇒ 绝不静默写出 "import , "。 + # vpi 里 import 行的名字是**包重导出注册的唯一输入** + # (VpiReader._inject_reexport_class_declare 靠它把子模块里的类以 + # 包 __init__ 的 sha1 注册别名 entry),名字静默丢失会让 `包.类` + # 形式的模块限定注解解析落空,下游报出与根因无关的 NameError + # (实测:ast/__init__.vpi 的 `from .base import , , ,` 名字全空 + # ⇒ ast.AST 报"方法 'Result.__init__' 形参 'elem' 类型注解解析失败")。 + # 两种成因分开报,便于一次定位:节点指针为空 = Alias 构造失败; + # 节点在而 name 为空 = _copy_str/__init__ 未生效。 + if al2 is None: + GenApi.fatal_error(stmt, + "vpi 生成:import 别名**节点指针为空**(Alias(...) 构造未产出对象:" + "pool.alloc 失败或 __init__ 未执行)——import 名字丢失会破坏包重导出注册", + "NameError") + if al2.name is None or al2.name[0] == '\0': + GenApi.fatal_error(stmt, + "vpi 生成:import 别名节点存在但 **name 为空**(_copy_str 返回 None / " + "__init__ 未赋值 name)——import 名字丢失会破坏包重导出注册", + "NameError") + pos = _PyiEmit(buf, buf_size, pos, al2.name) + if al2.asname is not None and string.strlen(al2.asname) > 0: + pos = _PyiEmit(buf, buf_size, pos, " as ") + pos = _PyiEmit(buf, buf_size, pos, al2.asname) ai2 += 1 pos = _PyiEmit(buf, buf_size, pos, "\n") @@ -917,6 +941,14 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, if ifr.module is not None: pos = _PyiEmit(buf, buf_size, pos, ifr.module) pos = _PyiEmit(buf, buf_size, pos, " import ") + # fail-fast:同 Import 分支——`from X import *` 走的是 Alias("*"), + # 列表为空或名字为空都说明 AST 坏;静默写出 `from X import ` 会让 + # 包重导出注册失效(star 也用于 re-export 判断)。 + if ifr.names is None or len(ifr.names) == 0: + GenApi.fatal_error(stmt, + "vpi 生成:from-import 语句别名列表为空(应为 ≥1 个别名,含 '*')" + "——AST 构造/解析异常", + "NameError") if ifr.names is not None: ifr_names: list[ast.AST*]* = ifr.names an3: sizet = len(ifr_names) @@ -925,11 +957,21 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, if ai3 > 0: pos = _PyiEmit(buf, buf_size, pos, ", ") al3: ast.Alias* = ast.Alias*(ifr_names.get(ai3)) - if al3 is not None and al3.name is not None: - pos = _PyiEmit(buf, buf_size, pos, al3.name) - if al3.asname is not None and string.strlen(al3.asname) > 0: - pos = _PyiEmit(buf, buf_size, pos, " as ") - pos = _PyiEmit(buf, buf_size, pos, al3.asname) + # fail-fast:同 Import 分支(两种成因分开报,一次定位)。 + if al3 is None: + GenApi.fatal_error(stmt, + "vpi 生成:from-import 别名**节点指针为空**(Alias(...) 构造未产出对象:" + "pool.alloc 失败或 __init__ 未执行)——包重导出注册会因此失效", + "NameError") + if al3.name is None or al3.name[0] == '\0': + GenApi.fatal_error(stmt, + "vpi 生成:from-import 别名节点存在但 **name 为空**(_copy_str 返回 None / " + "__init__ 未赋值 name)——包重导出注册会因此失效", + "NameError") + pos = _PyiEmit(buf, buf_size, pos, al3.name) + if al3.asname is not None and string.strlen(al3.asname) > 0: + pos = _PyiEmit(buf, buf_size, pos, " as ") + pos = _PyiEmit(buf, buf_size, pos, al3.asname) ai3 += 1 pos = _PyiEmit(buf, buf_size, pos, "\n") diff --git a/App/lib/core/Gen/Call/CallGenDispatch.vp b/App/lib/core/Gen/Call/CallGenDispatch.vp index d2d1140..e7c9cb8 100644 --- a/App/lib/core/Gen/Call/CallGenDispatch.vp +++ b/App/lib/core/Gen/Call/CallGenDispatch.vp @@ -8,6 +8,7 @@ import ast import llvmlite import memhub import string +import stdio import stdlib import sys import viperlib @@ -1241,6 +1242,80 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase): if method_at.value.kind() == ast.ASTKind.Name and trans is not None: method_obj_nm: ast.Name* = ast.Name*(method_at.value) if method_obj_nm.id is not None: + # [DIAG] 并排取证「已知成功」与「刚落空」的形的方法调用,看 H6 各级 + # 判据的命中差异(临时探针,定位后连同 import stdio 一起删除): + # A) recv=__with_ctx m=__enter__ :with 绑定 asname 之前执行,已知成功 + # B) recv=pool m=alloc :与 A 同一注释类名(memhub.MemPool), + # 但接收者 LLVM 类型来自 __enter__ 的**返回值**(A 是构造结果) + # 只有这一对能解释 H6 为何对 B 全线落空 —— 落空且不置 hit_box 时, + # translate_call 会把接收者名当模块名,报「跨模块调用 'pool.alloc' + # 找不到模块 SHA1」(MpoolOopTest/App/mpooltest.vp:14)。 + dg_hit: int = 0 + if string.strcmp(func_name, "alloc") == 0 \ + and string.strcmp(method_obj_nm.id, "pool") == 0: + dg_hit = 1 + if string.strcmp(func_name, "__enter__") == 0 \ + and string.strcmp(method_obj_nm.id, "__with_ctx") == 0: + dg_hit = 1 + if dg_hit != 0: + dg_obj: llvmlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, method_obj_nm.id) + dg_found: int = 0 + dg_ty: str = "-" + dg_c1: str = "-" + dg_c2: str = "-" + dg_an: str = "-" + dg_struct_ok: int = -1 + dg_sha1_ok: int = -1 + dg_spc_ok: int = -1 + if dg_obj is not None: + dg_found = 1 + if dg_obj.Ty is not None: + dg_ty = pool.alloc(256) + if dg_ty is not None: + llvmlite.TypePrint(dg_ty, 256, dg_obj.Ty, pool) + dg_s1: llvmlite.LLVMType* = None + if self.Gen.ExprGenBase.is_ptr_type(dg_obj.Ty) != 0: + dg_s1 = dg_obj.Ty.Pointee + if dg_s1 is not None: + dg_x1: str = self.Gen.StructGen.get_class_name_by_type(pool, dg_s1) + if dg_x1 is not None: + dg_c1 = dg_x1 + if self.Gen.ExprGenBase.is_ptr_type(dg_s1) != 0: + dg_x2: str = self.Gen.StructGen.get_class_name_by_type(pool, dg_s1.Pointee) + if dg_x2 is not None: + dg_c2 = dg_x2 + dg_ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, method_obj_nm.id) + dg_anr: str = self.Gen.VarGen.var_annot_class_name(dg_ve) + if dg_anr is not None: + dg_an = dg_anr + dg_bare: str = dg_anr + dg_dot: str = string.strrchr(dg_anr, '.') + if dg_dot is not None: + dg_bare = dg_dot + 1 + if self.Gen.StructGen.get_struct_type(dg_bare) is not None: + dg_struct_ok = 1 + else: + dg_struct_ok = 0 + if dg_dot is not None: + dg_ml: sizet = string.strlen(dg_anr) - string.strlen(dg_bare) - 1 + dg_mb: str = pool.alloc(dg_ml + 1) + if dg_mb is not None: + string.strncpy(dg_mb, dg_anr, dg_ml) + dg_mb[dg_ml] = '\0' + if self._lookup_module_sha1(dg_mb) is not None: + dg_sha1_ok = 1 + else: + dg_sha1_ok = 0 + if self._special_method_ret_ty(pool, dg_bare, func_name) is not None: + dg_spc_ok = 1 + else: + dg_spc_ok = 0 + dg_nl: int = GenApi.is_nonlocal_name(trans, method_obj_nm.id) + stdio.printf("[MDISP] mod=%s line=%d recv=%s m=%s found=%d nonlocal=%d recv_ty=%s cls1=%s cls2=%s annot=%s struct_ok=%d sha1_ok=%d spc_ok=%d\n", + trans.ModuleSha1, cl.lineno, method_obj_nm.id, func_name, + dg_found, dg_nl, dg_ty, dg_c1, dg_c2, dg_an, + dg_struct_ok, dg_sha1_ok, dg_spc_ok) + stdio.fflush(None) # nonlocal 对象:提升函数内从 env 读对象指针(load_nonlocal_var), # 不能 lookup_var 跨函数命中父函数 alloca(SSA 编号错乱 → # llc "use of undefined value")。entry.get() 等方法调用的 diff --git a/App/lib/core/Gen/Expr/ExprGenBase.vp b/App/lib/core/Gen/Expr/ExprGenBase.vp index 8f385ba..1de7b7a 100644 --- a/App/lib/core/Gen/Expr/ExprGenBase.vp +++ b/App/lib/core/Gen/Expr/ExprGenBase.vp @@ -317,6 +317,42 @@ novirtual class ExprGenBase: if loaded is None: return obj_ptr return loaded # ============================================================ + # _cast_fail_where - 隐式转换失败时报出**出错的表达式**(→ 定位到具体实参) + # + # 调用点只传 AST 节点,报错原本只有"调用所在行",多实参时看不出是哪一个 + # (如 `_td_set_token(td, TD_TOKEN_IN, dev_addr, endpoint, toggle_ptr, max_pkt)` + # 报指针→整数,无法判断是哪个实参)。此处按节点形态给出可读名: + # Name→'id'、Attribute→'.attr'、Call/Subscript→形态名,并附列号(源码行已 + # 由 traceback 打出,列号即可指到具体实参)。 + # ============================================================ + def _cast_fail_where(self, node: ast.AST*) -> str: + """取隐式转换失败的定位文本(出错表达式 + 列号)""" + if node is None: + return "节点未知(无 AST 信息)" + k: int = node.kind() + pos: int = -1 + if k == ast.ASTKind.Name: + nm: ast.Name* = ast.Name*(node) + if nm.id is not None: + pos = nm.col_offset + return f"Name '{nm.id}'(列 {pos})" + elif k == ast.ASTKind.Attribute: + at: ast.Attribute* = ast.Attribute*(node) + if at.attr is not None: + pos = at.col_offset + return f"属性 '.{at.attr}'(列 {pos})" + elif k == ast.ASTKind.Call: + pos = (ast.Call*(node)).col_offset + return f"调用结果 Call(列 {pos})" + elif k == ast.ASTKind.Subscript: + pos = (ast.Subscript*(node)).col_offset + return f"下标表达式 Subscript(列 {pos})" + elif k == ast.ASTKind.Constant: + pos = (ast.Constant*(node)).col_offset + return f"常量 Constant(列 {pos})" + return f"表达式 kind={k}(列 {pos})" + + # ============================================================ # 类型强制转换:整数 sext/trunc,浮点 fpext/fptrunc,int↔float si2fp/fp2si # ============================================================ def coerce_to_type(self, builder: llvmlite.IRBuilder*, @@ -361,14 +397,16 @@ novirtual class ExprGenBase: # (inttoptr 要求同宽,对齐 MatchGenMixin 模式),再 inttoptr 成空指针。 if val.IsConst == 1 and val.IntVal == 0: return llvmlite.build_inttoptr(builder, llvmlite.const_int64(builder.Pool, 0), target_ty) - self.Gen.TypeGenErr.fatal_error(node, "整数隐式转指针已禁止:必须显式强转为指针(如 t.CPtr(x))才能把整数赋给指针类型", "CastError") + m_i2p: str = "整数隐式转指针已禁止:必须显式强转为指针(如 t.CPtr(x))才能把整数赋给指针类型" + self.Gen.TypeGenErr.fatal_error(node, f"{m_i2p};出错表达式: {self._cast_fail_where(node)}", "CastError") return val # Pointer -> integer: rejected (symmetric with the integer -> pointer rule above). # No implicit pointer <-> integer conversion is allowed; an explicit cast is # required. (The old code silently did ptrtoint, and for 8-bit targets fell # through to the build_load branch below, copying the pointee's first byte.) if self.is_ptr_type(val.Ty) != 0 and target_bits != 0 and self.is_ptr_type(target_ty) == 0: - self.Gen.TypeGenErr.fatal_error(node, "指针隐式转整数已禁止:必须显式强转(如 uint64(p) / t.CInt64T(p) / int(p))才能把指针赋给整数类型", "CastError") + m_p2i: str = "指针隐式转整数已禁止:必须显式强转(如 uint64(p) / t.CInt64T(p) / int(p))才能把指针赋给整数类型" + self.Gen.TypeGenErr.fatal_error(node, f"{m_p2i};出错表达式: {self._cast_fail_where(node)}", "CastError") return val if val_bits != 0 and target_bits != 0: if val_bits == target_bits: diff --git a/App/lib/core/Gen/Func/FuncGenBase.vp b/App/lib/core/Gen/Func/FuncGenBase.vp index 24dde9a..3f5cb16 100644 --- a/App/lib/core/Gen/Func/FuncGenBase.vp +++ b/App/lib/core/Gen/Func/FuncGenBase.vp @@ -43,6 +43,17 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): deco: ast.AST* = decorator_list.get(di) if deco is None: continue dk: int = deco.kind() + # 直接形式 `@t.attr.`(与类侧 ClassGenBase._has_decorator 同口径, + # 类定义早就接受两种写法):`inline def f(...)` 合成的就是这一形态, + # 且**不要求** import c。此前只认 c.Attribute(...) ⇒ 没有 import c 的文件 + # 会静默不内联。不支持的属性名由 _append_llvm_attr 返回 0 自然忽略 + # (如 @t.CVTable / @t.NoVTable / packed / aligned 对函数无意义)。 + if dk == ast.ASTKind.Attribute: + dan: str = self._get_attr_name_from_node(deco) + if dan is not None and string.strcmp(dan, "section") != 0: + if self._append_llvm_attr(attrs_buf, dan) != 0: + found_any = 1 + continue if dk != ast.ASTKind.Call: continue call_node: ast.Call* = ast.Call*(deco) if call_node.func is None: continue