diff --git a/App/lib/core/Gen/CallGenMixin.vp b/App/lib/core/Gen/CallGenMixin.vp index 0d5a995..4a1909b 100644 --- a/App/lib/core/Gen/CallGenMixin.vp +++ b/App/lib/core/Gen/CallGenMixin.vp @@ -85,6 +85,13 @@ class CallGenMixin(CallGenBase.CallGenBase): can: t.CSizeT = 0 if cargs is not None: can = len(cargs) + # [DET] len() 结果核对(SlotsBugTest/StringIterTest can 垃圾高位定位: + # can=0x229_0000_0000=[cargs高32][count低32],怀疑 len() 生成代码 4 字节 + # 错位。门控:真实 count 远小于 2^32,can>=2^32 即异常,打印定位)。 + if (t.CUInt64T(can) & (t.CUInt64T(1) << 32)) != 0: + lh_l: t.CUInt64T | t.CPtr = (t.CUInt64T | t.CPtr)(t.CVoid(cargs, t.CPtr)) + stdio.printf("[DET] len结果异常: can=%llu cargs=%llu data=%llu count=%llu cap=%llu\n", + t.CUInt64T(can), t.CUInt64T(cargs), lh_l[0], lh_l[1], lh_l[2]) # super()/ctraits 检测:void 方法(如 __init__)命中后 build_call 返回 # None 属预期(void call 无 SSA 值),必须用 hit_box 区分"命中已处理" @@ -149,7 +156,7 @@ class CallGenMixin(CallGenBase.CallGenBase): # BinOp 节点作为 Call func: 联合类型注解用作类型转换 if cl.func is not None and cl.func.kind() == ast.ASTKind.BinOp and trans is not None: binop_ty: llvmlite.LLVMType | t.CPtr = self.Gen.TypeGenResolve.resolve_annotation_type( - pool, cl.func, trans._imported_modules, trans._from_imports, trans) + pool, cl.func, trans._imported_modules, trans._from_imports, trans, 0) if binop_ty is not None: binop_args: list[ast.AST | t.CPtr] | t.CPtr = cl.args if binop_args is not None and len(binop_args) == 1: @@ -341,6 +348,11 @@ class CallGenMixin(CallGenBase.CallGenBase): pool, builder, mod, struct_ty, func_name, cargs, can, keywords, kw_count, trans) # 仅位置参数:按字段顺序构建 + # [DET] 调用前 can 核对(与 len结果/callee 收到的 can 三级对照, + # 定位 4 字节错位发生在 len 计算/调用传参/callee 读取哪一级) + if (t.CUInt64T(can) & (t.CUInt64T(1) << 32)) != 0: + stdio.printf("[DET] 调用前 can=%llu cargs=%llu\n", + t.CUInt64T(can), t.CUInt64T(cargs)) return self.Gen.CallGenCtor._translate_struct_ctor( pool, builder, mod, struct_ty, func_name, cargs, can, trans) @@ -751,8 +763,22 @@ class CallGenMixin(CallGenBase.CallGenBase): for ai in range(can): arg: ast.AST | t.CPtr = cargs.get(ai) if arg is not None: + # 先取形参类型:单引号单字符字面量(CONST_CHAR)传给 str/指针 + # 形参时需物化 1 字符字符串(translate_constant 依据 target_ty + # 判定),否则 coerce inttoptr 把字符值当指针(如 '"'=0x22)→ + # strlen 访问低地址 ASan 崩溃(二代编 StringTest PyI 生成根因)。 + fp: llvmlite.Param | t.CPtr = None + fpty: llvmlite.LLVMType | t.CPtr = None + if found_func is not None: + fp = llvmlite.function_get_param_head(found_func) + fpi: int = 0 + while fp is not None and fpi < ai: + fp = llvmlite.param_get_next(fp) + fpi += 1 + if fp is not None: + fpty = llvmlite.param_get_ty(fp) arg_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value( - builder, pool, mod, arg, None, 0, trans) + builder, pool, mod, arg, None, 0, trans, fpty) if arg_val is not None: # 参数类型提升:当 found_func 为 None(stub 未注入)时, # 根据外部函数签名推断期望类型,提升字面量(如 i32→i64) @@ -772,6 +798,15 @@ class CallGenMixin(CallGenBase.CallGenBase): arg_val = decayed_fc case _: pass + # 已知变参 C 库函数(snprintf/sprintf/vsprintf)固定参数 + # 按签名 coerce:stub 未注入时跨模块签名不可见,但固定 + # 参数类型是 C ABI 硬约束(snprintf size 为 i64),不提升 + # 则 call(i32 1024) 与 declare(i64 size) 不匹配,且 Win64 + # 变参槽高 4 字节残留栈垃圾(clean 日志 0x7ff700000052 根因)。 + kvc_ty: llvmlite.LLVMType | t.CPtr = self.Gen.CallGenVa._get_known_vararg_cfunc_param_ty( + pool, func_name, ai) + if kvc_ty is not None: + arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, kvc_ty, arg) ext_param_ty: llvmlite.LLVMType | t.CPtr = self.Gen.CallGenLib._get_external_func_param_ty( pool, func_name, ai) if ext_param_ty is not None: @@ -785,13 +820,7 @@ class CallGenMixin(CallGenBase.CallGenBase): elif found_func is not None: # found_func 存在:按形参类型 coerce(如 stdlib.malloc(size: t.CSizeT)) # 修复: CDefine/i32 常量传给 i64 形参未提升,导致 call(i32) 与 define(i64) 不匹配 - fp: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(found_func) - fpi: int = 0 - while fp is not None and fpi < ai: - fp = llvmlite.param_get_next(fp) - fpi += 1 if fp is not None: - fpty: llvmlite.LLVMType | t.CPtr = llvmlite.param_get_ty(fp) if fpty is not None: arg_val = self.Gen.ExprGenBase.coerce_to_type( builder, arg_val, fpty, arg) @@ -871,6 +900,52 @@ class CallGenMixin(CallGenBase.CallGenBase): else: tail.Next = df_arg_val tail = df_arg_val + elif found_func is not None: + # 默认参数缺失/表信息不全时的零值兜底: + # 1) df_param_count>0 但 df_defaults 为空(函数表有参数数、无默认值记录) + # → 现有 if 不满足(要求 df_defaults 非 None 且 default_count>0), + # 缺参调用按 x64 约定读 [rsp+0x28] 栈参数区垃圾 → ASan 崩溃。 + # 2) 调用方文件先于定义文件编译(编译顺序问题,如 Phase1Main 调 + # StubMergerBuild._BuildReachableSha1Set 缺 app_deps), + # find_func_entry_in_table/find_func_defaults 均查不到(df_param_count=0) + # → 同样缺参崩溃。 + # 两种情况都用 found_func 实际参数数兜底,缺参填零值 + # (对齐 TPC _fill_default_args 的零值兜底语义;found_func 是 stub + # declare,参数数与 define 一致,按它填充安全)。 + # 注意排除 EH 输出参数(%__eh_msg_out__/%__eh_code_out__): + # 含 raise 的函数定义尾部注入 2 个 EH 形参,若计入 fc_cnt 会为 + # raise_disk() 这类无参调用补 2 个 zeroinitializer 占位,随后 + # _emit_eh_guarded_call 又追加真实 EH 实参 → call 4 参 vs define + # 2 参 → x64 ABI 错位 → 函数写 null 输出指针 → 0xC0000005 + # (TestFBProject raise_disk 崩溃根因)。 + fc_cnt: int = 0 + fc_p: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(found_func) + while fc_p is not None: + fp_nm_z: str = llvmlite.param_get_name(fc_p) + if fp_nm_z is None or (string.strcmp(fp_nm_z, "%__eh_msg_out__") != 0 + and string.strcmp(fp_nm_z, "%__eh_code_out__") != 0): + fc_cnt += 1 + fc_p = llvmlite.param_get_next(fc_p) + if fc_cnt == 0: + fc_cnt = df_param_count if df_param_count > 0 else 0 + if can < fc_cnt: + fp_z: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(found_func) + fpi_z: int = 0 + while fp_z is not None and fpi_z < can: + fp_z = llvmlite.param_get_next(fp_z) + fpi_z += 1 + while fp_z is not None and fpi_z < fc_cnt: + zero_ty_z: llvmlite.LLVMType | t.CPtr = llvmlite.param_get_ty(fp_z) + zero_val_z: llvmlite.Value | t.CPtr = llvmlite.ConstZero(pool, zero_ty_z) + if zero_val_z is not None: + zero_val_z.Next = None + if head is None: + head = zero_val_z + else: + tail.Next = zero_val_z + tail = zero_val_z + fp_z = llvmlite.param_get_next(fp_z) + fpi_z += 1 # 使用模块中的实际函数名(可能带 SHA1 前缀,如 "83210a72e829a086.atoi") # t.CExtern/t.State/t.CExport 函数不加 SHA1 前缀,使用裸名 @@ -1164,12 +1239,6 @@ class CallGenMixin(CallGenBase.CallGenBase): else: if trans is not None: trans._deps_pending = 1 - # [DEPDBG] 临时诊断:跨模块函数依赖未就绪即 pending 的根因(用后移除) - dbg_ms5: str = trans.ModuleSha1 if (trans is not None and trans.ModuleSha1 is not None) else "?" - stdio.printf("[DEPDBG] pending(cross_fn): mod=%s call=%s sha1=%s text=%d\n", - dbg_ms5, call_name, - cf_sha1 if cf_sha1 is not None else "none", - self._dep_text_ll_exists(pool, cf_sha1) if cf_sha1 is not None else -1) cf_dbg: str = VLogger.fmt_buf() if cf_dbg is not None: viperlib.snprintf(