From a5260f444134a8a7b0342661400edc6f12997de1 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 10 Sep 2026 22:33:18 +0800 Subject: [PATCH] =?UTF-8?q?fix(tpv):=20ASan=20=E8=BF=90=E8=A1=8C=E5=BA=93?= =?UTF-8?q?=E6=94=B9=E7=94=B1=20project.vpj=20=E5=A3=B0=E6=98=8E;=20read?= =?UTF-8?q?=5Fall=20=E5=AE=B9=E9=87=8F=E8=AF=AD=E4=B9=89=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E4=B8=8E=E8=B6=85=E9=99=90=E6=BA=90=E6=96=87=E4=BB=B6=20fail-f?= =?UTF-8?q?ast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - --asan: 移除 BuildPipeline 中硬编码的 clang64 libclang_rt 注入, 改由工程在 linker.static_libs / dynamic_libs / flags 中自行声明; 新增 Config.asan_runtime_declared(), main.vp 在未声明时 fail-fast 并列出所需运行库, 不再给含混的 undefined reference - read_all: 容量参数含终止符(最多读 cap-1 字节), 故删除 12 处恒不可达的 else 截断兜底; 并在读取前按 f.size() fail-fast 拒绝超限源文件(main.vp / BuildPipeline / Phase1Main / Phase1Scan / Phase2), 避免静默截断后报出莫名其妙的语法错误 - project.vpj(根 / Test / NegativeTest / Sha1Test): linker.cmd 由裸 clang++ 改为绝对路径并补 -fuse-ld=lld --- App/lib/Phase/BuildPipeline.vp | 39 +++++++++------------- App/lib/Phase/Phase1/Phase1Main.vp | 28 +++++++++++----- App/lib/Phase/Phase1/Phase1Scan.vp | 15 ++++++--- App/lib/Phase/Phase2/__init__.vp | 32 ++++++++++-------- App/lib/Projectrans/Config.vp | 26 +++++++++++++++ App/lib/Projectrans/Utils.vp | 2 -- App/lib/Stub/StubGen/VpiReader.vp | 8 ----- App/lib/Stub/StubMerger/StubMergerBuild.vp | 10 ------ App/lib/Stub/StubMerger/StubMergerIR.vp | 2 -- App/lib/core/Gen/AssignGenMixin.vp | 2 -- App/lib/core/Gen/Call/CallGenBase.vp | 2 -- App/lib/core/Gen/Expr/ExprGenCdef.vp | 4 --- App/lib/core/Gen/Type/TypeGenResolve.vp | 4 --- App/main.vp | 26 +++++++++++---- Test/NegativeTest/project.vpj | 4 +-- Test/Sha1Test/project.vpj | 4 +-- Test/project.vpj | 4 +-- project.vpj | 4 +-- 18 files changed, 119 insertions(+), 97 deletions(-) diff --git a/App/lib/Phase/BuildPipeline.vp b/App/lib/Phase/BuildPipeline.vp index 11786e8..03bcb42 100644 --- a/App/lib/Phase/BuildPipeline.vp +++ b/App/lib/Phase/BuildPipeline.vp @@ -36,7 +36,8 @@ import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect # 全局 mbuddy 指针 _mbuddy: memhub.MemBuddy | t.CPtr -# ASan 模式(--asan):对 .ll 先 opt -passes=asan 插桩,链接加 -fsanitize=address +# ASan 模式(--asan):对 .ll 先 opt -passes=asan 插桩;ASan 运行库由 +# project.vpj 的 linker.static_libs / dynamic_libs 显式声明(TPV 不注入) _asan_mode: bool = 0 # --phase2command xxx.bat:不执行 llc/链接命令,只把 Phase B/B+/C 的 @@ -84,6 +85,12 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy | t.CPtr, file_path: str, if file_path is None: return None f: fileio.File | t.CPtr = fileio.File(file_path, fileio.MODE.R) if f.closed: return None + # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被**静默截断**; + # 这里先按文件大小拒绝, 否则拿截断后的源码去 parse 只会得到莫名其妙的语法错误 + if f.size() >= SRC_BUF_SIZE: + VLogger.error(f"源文件超过 {SRC_BUF_SIZE} 字节上限, 拒绝读取: {file_path}", "fileio") + f.close() + return None src_buf: bytes = stdlib.malloc(SRC_BUF_SIZE) if src_buf is None: f.close() @@ -95,8 +102,6 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy | t.CPtr, file_path: str, return None if bytes_read < SRC_BUF_SIZE: src_buf[bytes_read] = 0 - else: - src_buf[SRC_BUF_SIZE - 1] = 0 lx: ast.Lexer | t.CPtr = ast.new_lexer(mb) ast._lexer_init(lx, src_buf, mb) tokens: ast.Token | t.CPtr = ast.tokenize(lx) @@ -760,18 +765,13 @@ def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_f # 注意: .obj 文件必须在 -l 库标志之前,否则链接器无法解析符号依赖 # linker_static_libs / linker_dynamic_libs(project.vpj 显式指定的 .a/.obj/.dll/.so) # 同样位于 -l 之前;动态库作为输入文件由 GNU ld 提取导入符号 - # ASan 模式:mingw64 clang 的 -fsanitize=address 在 mingw64/lib 下找不到 - # asan 运行库(实际在 clang64),改为显式链接 clang64 的 libclang_rt - # (DLL 版,运行时需 D:/msys64/clang64/bin 在 PATH) + # ASan 模式:ASan 运行库**不再**由 TPV 硬编码。clang 没有给 + # x86_64-w64-windows-gnu 出 ASan 运行库,只能借用 clang64(MSVC ABI) 的 + # libclang_rt,但那是工程级选择(且会牵进 MSVC 专有的 .drectve 指令), + # 不该由所有工程无条件承担 —— 改由 project.vpj 的 + # linker.static_libs / linker.dynamic_libs 显式声明,main.vp 在 --asan + # 下会用 Config.asan_runtime_declared 校验是否声明过。 use_flags: str = linker_flags - if _asan_mode != 0: - fl_len: t.CSizeT = len(linker_flags) + 256 - use_flags = _mbuddy.alloc(fl_len) - if use_flags is None: - return 1 - viperlib.snprintf(use_flags, fl_len, - "%s -L D:/msys64/clang64/lib/clang/22/lib/windows -l:libclang_rt.asan_dynamic-x86_64.dll.a -l:libclang_rt.asan_dynamic_runtime_thunk-x86_64.a", - linker_flags) static_libs: str = linker_static_libs if static_libs is None: static_libs = "" @@ -1009,16 +1009,9 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: t.CSizeT, extra_len = collect_obj_files(includes_binary_dir, extra_objs, EXTRA_BUF_SIZE) # 构造命令: clang++ obj_paths extra_objs -o linker_output linker_flags - # ASan 模式:显式链接 clang64 的 libclang_rt(同 link_obj_to_exe) + # ASan 运行库同样不再由 TPV 注入(在 link_obj_to_exe 里已有说明): + # 由 project.vpj 声明,--asan 下由 main.vp 校验。 use_flags: str = linker_flags - if _asan_mode != 0: - fl_len: t.CSizeT = len(linker_flags) + 256 - use_flags = _mbuddy.alloc(fl_len) - if use_flags is None: - return 1 - viperlib.snprintf(use_flags, fl_len, - "%s -L D:/msys64/clang64/lib/clang/22/lib/windows -l:libclang_rt.asan_dynamic-x86_64.dll.a -l:libclang_rt.asan_dynamic_runtime_thunk-x86_64.a", - linker_flags) static_libs2: str = linker_static_libs if static_libs2 is None: static_libs2 = "" diff --git a/App/lib/Phase/Phase1/Phase1Main.vp b/App/lib/Phase/Phase1/Phase1Main.vp index bfb5ae2..f18758d 100644 --- a/App/lib/Phase/Phase1/Phase1Main.vp +++ b/App/lib/Phase/Phase1/Phase1Main.vp @@ -567,6 +567,13 @@ def RunPhase1(mb: memhub.MemBuddy | t.CPtr, includes_dir: str, temp_dir: str, p1a_fl += 1 continue + # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + if f_r.size() >= Phase1.SRC_BUF_SIZE: + VLogger.error(f"源文件超过 {Phase1.SRC_BUF_SIZE} 字节上限, 拒绝读取: {file_path_r}", "Phase1") + f_r.close() + p1a_fl += 1 + continue + src_buf_r: bytes = stdlib.malloc(Phase1.SRC_BUF_SIZE) if src_buf_r is None: f_r.close() @@ -581,8 +588,6 @@ def RunPhase1(mb: memhub.MemBuddy | t.CPtr, includes_dir: str, temp_dir: str, continue if bytes_read_r < Phase1.SRC_BUF_SIZE: src_buf_r[bytes_read_r] = 0 - else: - src_buf_r[Phase1.SRC_BUF_SIZE - 1] = 0 # 解析 AST lx_r: ast.Lexer | t.CPtr = ast.new_lexer(mb) @@ -718,6 +723,12 @@ def RunPhase1(mb: memhub.MemBuddy | t.CPtr, includes_dir: str, temp_dir: str, p1bpre_failed += 1 VLogger.error(f"[Phase 1b-pre] {file_path_pv} | 无法打开源文件", "Phase1") continue + # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + if f_pv.size() >= Phase1.SRC_BUF_SIZE: + p1bpre_failed += 1 + VLogger.error(f"[Phase 1b-pre] {file_path_pv} | 源文件超过 {Phase1.SRC_BUF_SIZE} 字节上限", "Phase1") + f_pv.close() + continue src_buf_pv: bytes = stdlib.malloc(Phase1.SRC_BUF_SIZE) if src_buf_pv is None: f_pv.close() @@ -733,8 +744,6 @@ def RunPhase1(mb: memhub.MemBuddy | t.CPtr, includes_dir: str, temp_dir: str, continue if bytes_read_pv < Phase1.SRC_BUF_SIZE: src_buf_pv[bytes_read_pv] = 0 - else: - src_buf_pv[Phase1.SRC_BUF_SIZE - 1] = 0 lx_pv: ast.Lexer | t.CPtr = ast.new_lexer(mb) if lx_pv is None: stdlib.free(src_buf_pv) @@ -848,8 +857,6 @@ def RunPhase1(mb: memhub.MemBuddy | t.CPtr, includes_dir: str, temp_dir: str, if vrd_1b > 0: if vrd_1b < 1048576: vbuf_1b[vrd_1b] = 0 - else: - vbuf_1b[1048576 - 1] = 0 vtotal_1b: t.CSizeT = string.strlen(vbuf_1b) vpos_1b: t.CSizeT = 0 while vpos_1b < vtotal_1b: @@ -913,6 +920,13 @@ def RunPhase1(mb: memhub.MemBuddy | t.CPtr, includes_dir: str, temp_dir: str, failed += 1 continue + # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + if f.size() >= Phase1.SRC_BUF_SIZE: + VLogger.error(f"源文件超过 {Phase1.SRC_BUF_SIZE} 字节上限, 拒绝读取: {file_path}", "Phase1") + f.close() + failed += 1 + continue + src_buf: bytes = stdlib.malloc(Phase1.SRC_BUF_SIZE) if src_buf is None: f.close() @@ -928,8 +942,6 @@ def RunPhase1(mb: memhub.MemBuddy | t.CPtr, includes_dir: str, temp_dir: str, continue if bytes_read < Phase1.SRC_BUF_SIZE: src_buf[bytes_read] = 0 - else: - src_buf[Phase1.SRC_BUF_SIZE - 1] = 0 # 解析 AST lx: ast.Lexer | t.CPtr = ast.new_lexer(mb) diff --git a/App/lib/Phase/Phase1/Phase1Scan.vp b/App/lib/Phase/Phase1/Phase1Scan.vp index 0df2a25..cf59b30 100644 --- a/App/lib/Phase/Phase1/Phase1Scan.vp +++ b/App/lib/Phase/Phase1/Phase1Scan.vp @@ -111,6 +111,12 @@ def _ScanClassInheritance(mb: memhub.MemBuddy | t.CPtr, if f.closed: continue + # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + if f.size() >= Phase1.SRC_BUF_SIZE: + VLogger.error(f"源文件超过 {Phase1.SRC_BUF_SIZE} 字节上限, 拒绝读取: {file_path}", "Phase1") + f.close() + continue + src_buf: bytes = stdlib.malloc(Phase1.SRC_BUF_SIZE) if src_buf is None: f.close() @@ -123,8 +129,6 @@ def _ScanClassInheritance(mb: memhub.MemBuddy | t.CPtr, continue if bytes_read < Phase1.SRC_BUF_SIZE: src_buf[bytes_read] = 0 - else: - src_buf[Phase1.SRC_BUF_SIZE - 1] = 0 # 解析 AST lx: ast.Lexer | t.CPtr = ast.new_lexer(mb) @@ -293,6 +297,11 @@ def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy | t.CPtr, f_pg: fileio.File | t.CPtr = fileio.File(file_path_pg, fileio.MODE.R) if f_pg.closed: continue + # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + if f_pg.size() >= Phase1.SRC_BUF_SIZE: + VLogger.error(f"源文件超过 {Phase1.SRC_BUF_SIZE} 字节上限, 拒绝读取: {file_path_pg}", "Phase1") + f_pg.close() + continue src_buf_pg: bytes = stdlib.malloc(Phase1.SRC_BUF_SIZE) if src_buf_pg is None: f_pg.close() @@ -304,8 +313,6 @@ def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy | t.CPtr, continue if bytes_read_pg < Phase1.SRC_BUF_SIZE: src_buf_pg[bytes_read_pg] = 0 - else: - src_buf_pg[Phase1.SRC_BUF_SIZE - 1] = 0 # 文本扫描筛选:非泛型文件跳过(不 parse、不驻留,防 OOM) if _BufHasGenericClassMarker(src_buf_pg, bytes_read_pg) == 0: stdlib.free(src_buf_pg) diff --git a/App/lib/Phase/Phase2/__init__.vp b/App/lib/Phase/Phase2/__init__.vp index a0bad05..4f76a0a 100644 --- a/App/lib/Phase/Phase2/__init__.vp +++ b/App/lib/Phase/Phase2/__init__.vp @@ -291,6 +291,11 @@ def _PreRegisterAppGenericTemplates(mb: memhub.MemBuddy | t.CPtr, f_pa: fileio.File | t.CPtr = fileio.File(ent_pa.Path, fileio.MODE.R) if f_pa.closed: continue + # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + if f_pa.size() >= SRC_BUF_SIZE: + VLogger.error(f"源文件超过 {SRC_BUF_SIZE} 字节上限, 拒绝读取: {ent_pa.Path}", "PhaseA") + f_pa.close() + continue src_buf_pa: bytes = stdlib.malloc(SRC_BUF_SIZE) if src_buf_pa is None: f_pa.close() @@ -302,8 +307,6 @@ def _PreRegisterAppGenericTemplates(mb: memhub.MemBuddy | t.CPtr, continue if bytes_read_pa < SRC_BUF_SIZE: src_buf_pa[bytes_read_pa] = 0 - else: - src_buf_pa[SRC_BUF_SIZE - 1] = 0 # 解析 AST(只解析,不完整翻译;用永久池使模板节点跨 mark2 reset 存活) perm_pa: memhub.MemBuddy | t.CPtr = CompilerFactory.factory.PermPool if perm_pa is None: @@ -500,13 +503,13 @@ def _PhaseADumpNode(temp_dir: str, td_len: t.CSizeT, sha1: str, if not df.closed: deps = stdlib.malloc(PA_DEPS_MAX_STR) if deps is not None: - rl: t.CInt64T = df.read_all(deps, PA_DEPS_MAX_STR - 1) + rl: t.CInt64T = df.read_all(deps, PA_DEPS_MAX_STR) if rl < 0: rl = 0 - if rl >= PA_DEPS_MAX_STR - 1: - deps[PA_DEPS_MAX_STR - 1] = 0 - else: - deps[rl] = 0 + # read_all 的容量参数就是缓冲区容量(含终止符): 传真实容量 + # MAX 时最多读 MAX-1 字节, 故 rl 恒 <= MAX-1, 在界内直接写 + # 终止符即可(原来传 MAX-1 白扔一个字节, 且还有个死分支) + deps[rl] = 0 df.close() stdlib.free(dpath) if deps is None or deps[0] == 0: @@ -633,14 +636,13 @@ def _PhaseADumpMroPending(temp_dir: str, td_len: t.CSizeT, sha1: str) -> None: if buf is None: rf.close() return - n: t.CInt64T = rf.read_all(buf, 8191) + # read_all 的容量参数就是缓冲区容量(含终止符), 传真实容量 8192 而不是 8191 + n: t.CInt64T = rf.read_all(buf, 8192) rf.close() if n < 0: n = 0 if n < 8191: buf[n] = 0 - else: - buf[8191] = 0 nsz: t.CSizeT = t.CSizeT(n) pos: t.CSizeT = 0 hits: int = 0 @@ -930,6 +932,12 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, vpi_failed_p2 += 1 VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | 无法打开源文件", "PhaseA") continue + # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + if f_vp2.size() >= SRC_BUF_SIZE: + vpi_failed_p2 += 1 + VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | 源文件超过 {SRC_BUF_SIZE} 字节上限", "PhaseA") + f_vp2.close() + continue src_buf_vp2: bytes = stdlib.malloc(SRC_BUF_SIZE) if src_buf_vp2 is None: f_vp2.close() @@ -945,8 +953,6 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, continue if bytes_read_vp2 < SRC_BUF_SIZE: src_buf_vp2[bytes_read_vp2] = 0 - else: - src_buf_vp2[SRC_BUF_SIZE - 1] = 0 lx_vp2: ast.Lexer | t.CPtr = ast.new_lexer(mb) if lx_vp2 is None: stdlib.free(src_buf_vp2) @@ -1743,8 +1749,6 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, if tbuf_mi is not None and tbr_mi > 0: if tbr_mi < StubMerger.STUB_READ_BUF_SIZE: tbuf_mi[tbr_mi] = '\0' - else: - tbuf_mi[StubMerger.STUB_READ_BUF_SIZE - 1] = '\0' # 逐行扫描: 找 define 行即为实现文件 # (不再要求行中含 @",因为 memhub 等模块的函数名 # 如 @ca40915f11b8b6ad._align_up 不带引号但仍是实现文件) diff --git a/App/lib/Projectrans/Config.vp b/App/lib/Projectrans/Config.vp index 573132e..dc1e852 100644 --- a/App/lib/Projectrans/Config.vp +++ b/App/lib/Projectrans/Config.vp @@ -641,6 +641,32 @@ def get_includes_binary_dir() -> str: return buf +def asan_runtime_declared() -> int: + """project.vpj 是否已声明 ASan 运行库(1 = 声明过) + + TPV 不再硬编码 clang64 的 libclang_rt 路径:clang 没有给 + x86_64-w64-windows-gnu 目标出 ASan 运行库,只能借用 clang64(MSVC ABI) + 的那两份(会牵进 MSVC 专有的 .drectve 链接指令),这是工程级选择, + 必须由工程自己声明。 + + 三种写法都认(和 openssl 那种「写在 flags 里」的写法兼容): + * linker.static_libs 里的 .a / .dll.a + * linker.dynamic_libs 里的 .dll / .so + * linker.flags 里的 -L… -l:…(如 openssl 的 -lssl -lcrypto) + 判定方式:以上任一处出现 "asan" 即视为已声明。 + """ + if LinkerStaticLibs is not None: + if string.strstr(LinkerStaticLibs, "asan") is not None: + return 1 + if LinkerDynamicLibs is not None: + if string.strstr(LinkerDynamicLibs, "asan") is not None: + return 1 + if LinkerFlags is not None: + if string.strstr(LinkerFlags, "asan") is not None: + return 1 + return 0 + + def print_config(): """打印当前配置""" if ProjectName is not None: diff --git a/App/lib/Projectrans/Utils.vp b/App/lib/Projectrans/Utils.vp index dddc676..e9f5703 100644 --- a/App/lib/Projectrans/Utils.vp +++ b/App/lib/Projectrans/Utils.vp @@ -143,8 +143,6 @@ def read_file_to_buf(path: str, buf_size: t.CSizeT) -> bytes: return None if br < buf_size: buf[br] = 0 - else: - buf[buf_size - 1] = 0 return buf diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 021dd80..4bc1b3d 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -652,8 +652,6 @@ def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int: if bytes_read > 0: if bytes_read < VPI_READ_BUF_SIZE: vpi_buf[bytes_read] = 0 - else: - vpi_buf[VPI_READ_BUF_SIZE - 1] = 0 _cache_vpi_return_annots_text(vpi_buf, src_sha1) stdlib.free(vpi_buf) stdlib.free(vpi_path) @@ -712,8 +710,6 @@ def _ensure_vpi_param_annot_loaded(src_sha1: str, temp_dir: str) -> int: if bytes_read > 0: if bytes_read < VPI_READ_BUF_SIZE: vpi_buf[bytes_read] = 0 - else: - vpi_buf[VPI_READ_BUF_SIZE - 1] = 0 _cache_vpi_param_annots_text(vpi_buf, src_sha1) stdlib.free(vpi_buf) stdlib.free(vpi_path) @@ -1316,8 +1312,6 @@ def _vpi_register_typedef_closure(pool: memhub.MemBuddy | t.CPtr, _vpi_typedef_visited_mark(sha1_r) if bytes_read_r < VPI_READ_BUF_SIZE: vpi_buf_r[bytes_read_r] = 0 - else: - vpi_buf_r[VPI_READ_BUF_SIZE - 1] = 0 lx_r: ast.Lexer | t.CPtr = ast.new_lexer(pool) if lx_r is not None: ast._lexer_init(lx_r, vpi_buf_r, pool) @@ -1463,8 +1457,6 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy | t.CPtr, return 0 if bytes_read < VPI_READ_BUF_SIZE: vpi_buf[bytes_read] = 0 - else: - vpi_buf[VPI_READ_BUF_SIZE - 1] = 0 # 2. 解析为 AST lx: ast.Lexer | t.CPtr = ast.new_lexer(pool) diff --git a/App/lib/Stub/StubMerger/StubMergerBuild.vp b/App/lib/Stub/StubMerger/StubMergerBuild.vp index 095299e..5564770 100644 --- a/App/lib/Stub/StubMerger/StubMergerBuild.vp +++ b/App/lib/Stub/StubMerger/StubMergerBuild.vp @@ -302,8 +302,6 @@ def _LoadAndAppendStub(temp_dir: str, td_len: t.CSizeT, dep_sha1: str, return out_pos if dep_br_ls < StubMerger.STUB_READ_BUF_SIZE: dep_buf[dep_br_ls] = '\0' - else: - dep_buf[StubMerger.STUB_READ_BUF_SIZE - 1] = '\0' # 注释掉依赖 stub 中的字符串常量行(模块内部的,不需要) # 简单方案:搜索 "= external unnamed_addr constant" 并将行首的 @ 改为 ;@ @@ -499,8 +497,6 @@ def _LoadAndAppendTextDeclares(temp_dir: str, td_len: t.CSizeT, dep_sha1: str, return out_pos if text_br < StubMerger.STUB_READ_BUF_SIZE: dep_buf[text_br] = '\0' - else: - dep_buf[StubMerger.STUB_READ_BUF_SIZE - 1] = '\0' dep_pos_lt: t.CSizeT = 0 in_body: int = 0 @@ -1303,8 +1299,6 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str, return 0 if stub_br < StubMerger.STUB_READ_BUF_SIZE: stub_content[stub_br] = '\0' - else: - stub_content[StubMerger.STUB_READ_BUF_SIZE - 1] = '\0' slen: t.CSizeT = stub_br # 修复本地 stub.ll 中字符串常量的非法 IR: # llvmlite OUTPUT_STUB 模式输出 "= external unnamed_addr constant" 的非法形式。 @@ -1361,8 +1355,6 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str, if deps_br > 0: if deps_br < 4096: deps_buf[deps_br] = '\0' - else: - deps_buf[4095] = '\0' deps_loaded = 1 elif deps_br == 0: # 空 deps.txt:无 import 的包标记文件(如 lib/core/Gen/__init__.vp、 @@ -1536,8 +1528,6 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str, if text_br > 0: if text_br < StubMerger.STUB_READ_BUF_SIZE: text_content[text_br] = '\0' - else: - text_content[StubMerger.STUB_READ_BUF_SIZE - 1] = '\0' # 行级去重追加(与 _LoadAndAppendStub 相同逻辑) tx_pos: t.CSizeT = 0 tx_skip: int = 1 diff --git a/App/lib/Stub/StubMerger/StubMergerIR.vp b/App/lib/Stub/StubMerger/StubMergerIR.vp index 8809a76..9e3e160 100644 --- a/App/lib/Stub/StubMerger/StubMergerIR.vp +++ b/App/lib/Stub/StubMerger/StubMergerIR.vp @@ -65,8 +65,6 @@ def IsDeclOnlyModule(temp_dir: str, td_len: t.CSizeT, sha1: str) -> int: return 1 if tbr_d < StubMerger.STUB_READ_BUF_SIZE: tbuf_d[tbr_d] = '\0' - else: - tbuf_d[StubMerger.STUB_READ_BUF_SIZE - 1] = '\0' # 逐行扫描:找 "define " 行即为实现文件 tpos_d: t.CSizeT = 0 while tpos_d < tbr_d: diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 2fc8eec..c2a7097 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -242,8 +242,6 @@ class AssignGenMixin: return 0 if nrd < BUF_SZ: buf[nrd] = 0 - else: - buf[BUF_SZ - 1] = 0 name_len: t.CSizeT = string.strlen(name) total_len: t.CSizeT = string.strlen(buf) diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index 97a3b4e..cdab94c 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -205,8 +205,6 @@ class CallGenBase: return None if nrd < READ_SZ: buf[nrd] = 0 - else: - buf[READ_SZ - 1] = 0 name_len: t.CSizeT = string.strlen(func_name) # 模块自含定义标志:vpi 中行首 "def {func_name}(" 命中且未被同名 # from-import 覆盖 → 该模块自身定义 func_name,不是 re-export 源。 diff --git a/App/lib/core/Gen/Expr/ExprGenCdef.vp b/App/lib/core/Gen/Expr/ExprGenCdef.vp index 8382960..40212fe 100644 --- a/App/lib/core/Gen/Expr/ExprGenCdef.vp +++ b/App/lib/core/Gen/Expr/ExprGenCdef.vp @@ -325,8 +325,6 @@ class ExprGenCdef(ExprGenBase.ExprGenBase): else: if bytes_read < PYI_READ_BUF_SIZE: pyi_buf[bytes_read] = 0 - else: - pyi_buf[PYI_READ_BUF_SIZE - 1] = 0 # vpi(存根签名文件)中的 CDefine 声明可能只带名字不带值 # (Converter 旧版只对 CONST_INT/BOOL 等输出 "= value",CONST_FLOAT # 等不输出 → numpy 的 pi/e 在 stub 中无值),真正的值在源 @@ -402,8 +400,6 @@ class ExprGenCdef(ExprGenBase.ExprGenBase): return -1 if bytes_read < PYI_READ_BUF_SIZE: pyi_buf[bytes_read] = 0 - else: - pyi_buf[PYI_READ_BUF_SIZE - 1] = 0 # 8. 在 pyi_buf 中查找 "name: t.CDefine = value" 行 # 格式: NAME: t.CDefine = value diff --git a/App/lib/core/Gen/Type/TypeGenResolve.vp b/App/lib/core/Gen/Type/TypeGenResolve.vp index 20115ef..db3345a 100644 --- a/App/lib/core/Gen/Type/TypeGenResolve.vp +++ b/App/lib/core/Gen/Type/TypeGenResolve.vp @@ -901,8 +901,6 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): return None if b_read < LONG(BUF_B): b_buf[t.CSizeT(b_read)] = 0 - else: - b_buf[BUF_B - 1] = 0 # 逐行扫描并注册 builtins 全部 "NAME: t.CTypedef = TYPE"(一次读文件 # 注册全部,置 scanned 标记后同文件后续查表即中;对齐 # register_ctypedef_from_source 的文本匹配策略)。已存在的表条目 @@ -1079,8 +1077,6 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): return None if m_read < LONG(BUF_L): m_buf[t.CSizeT(m_read)] = 0 - else: - m_buf[BUF_L - 1] = 0 attr_len: t.CSizeT = string.strlen(attr) m_total: t.CSizeT = string.strlen(m_buf) m_i: t.CSizeT = 0 diff --git a/App/main.vp b/App/main.vp index 9fec60a..663495a 100644 --- a/App/main.vp +++ b/App/main.vp @@ -142,7 +142,7 @@ def main() -> int: parser.add_argument("--run", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Run the generated executable after compilation") parser.add_argument("--rebuild-includes", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Delete the includes.binary precompiled cache and rebuild all the includes") parser.add_argument("--clear-cache", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Clear .transpyc_cache global cache") - parser.add_argument("--asan", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "ASan mode: .ll first opt -passes=asan then link -fsanitize=address") + parser.add_argument("--asan", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "ASan mode: .ll first opt -passes=asan; ASan runtime libs must be declared in project.vpj (linker.static_libs)") parser.add_argument("--phase2command", None, argparse.STRING, 0, None, False, argparse.STORE, "Do not run llc/link: write Phase2 llc(opt+llc) and clang link commands to the given .bat path") parser.add_argument("--nowarn-register", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Suppress Phase A dependency WARN") parser.add_argument("--detection", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Enable MemBuddy allocation detection (0xCDCD markers + record-table probe for out-of-bounds writes; applies to both mb and perm_pool)") @@ -220,7 +220,7 @@ Target: %s if args.get_bool("asan"): BuildPipeline._asan_mode = True Config._inc_bin_suffix = ".binary_asan" - log.info("ASan 模式已启用(opt -passes=asan + libclang_rt)", "asan") + log.info("ASan 模式已启用(opt -passes=asan 插桩; 运行库需 project.vpj 声明)", "asan") # ASan 模式清理 asan includes 缓存(强制重建 asan 插桩 obj) mf_inc_bin_asan: str = Config.get_includes_binary_dir() if mf_inc_bin_asan is not None: @@ -291,6 +291,17 @@ Target: %s Config.resolve_paths(proj_dir) Config.print_config() + # --asan 要求工程自己在 project.vpj 里声明 ASan 运行库(TPV 不再硬编码 + # clang64 的 libclang_rt 路径):clang 没有给 x86_64-w64-windows-gnu 出 + # ASan 运行库,只能借用 clang64(MSVC ABI) 的那两份,是否承担这个选择 + # 属于工程自身的事,故在此 fail-fast 而不是给个含混的 undefined reference。 + if BuildPipeline._asan_mode != 0 and Config.asan_runtime_declared() == 0: + log.error("启用 --asan 需要在 project.vpj 的 linker.static_libs 里声明 ASan 运行库:", "asan") + log.error(" D:/msys64/clang64/lib/clang/22/lib/windows/libclang_rt.asan_dynamic-x86_64.dll.a", "asan") + log.error(" D:/msys64/clang64/lib/clang/22/lib/windows/libclang_rt.asan_dynamic_runtime_thunk-x86_64.a", "asan") + log.error("运行时还需要 PATH 里有 D:/msys64/clang64/bin(libclang_rt.asan_dynamic-x86_64.dll)", "asan") + return 1 + # === --clean: 清理 temp、output 和 includes.binary 目录 === if _gb_clean: log.info("清理临时目录...", "clean") @@ -407,6 +418,13 @@ Target: %s log.error(f"无法打开文件: {src_path}", "fileio") argparse.release(args) return 1 + # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被**静默截断**; + # 这里先按文件大小拒绝, 否则拿截断后的源码去 parse 只会得到莫名其妙的语法错误 + if f.size() >= SRC_BUF_SIZE: + log.error(f"源文件超过 {SRC_BUF_SIZE} 字节上限, 拒绝读取: {src_path}", "fileio") + f.close() + argparse.release(args) + return 1 # 分配源代码缓冲区 src_buf: bytes = stdlib.malloc(SRC_BUF_SIZE) if src_buf is None: @@ -424,10 +442,6 @@ Target: %s # 添加 null 终止符,确保 strlen 和 SHA1 计算正确 if bytes_read < SRC_BUF_SIZE: src_buf[bytes_read] = 0 - else: - src_buf[SRC_BUF_SIZE - 1] = 0 - log.info("文件读取完成", "fileio") - log.info(f"读取 {bytes_read} 字节", "fileio") # 解析 AST(拆分为词法+语法两阶段,便于调试) ast._init_tables(mb) lx: ast.Lexer | t.CPtr = ast.new_lexer(mb) diff --git a/Test/NegativeTest/project.vpj b/Test/NegativeTest/project.vpj index f701fd1..2fbe19d 100644 --- a/Test/NegativeTest/project.vpj +++ b/Test/NegativeTest/project.vpj @@ -9,8 +9,8 @@ "flags": ["-filetype=obj", "-relocation-model=pic"] }, "linker": { - "cmd": "clang++", - "flags": ["-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32", "-Wl,--allow-multiple-definition"], + "cmd": "D:/msys64/mingw64/bin/clang++.exe", + "flags": ["-fuse-ld=lld", "-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32", "-Wl,--allow-multiple-definition"], "output": "neg_app.exe" }, "includes": [ diff --git a/Test/Sha1Test/project.vpj b/Test/Sha1Test/project.vpj index db2764f..acbbf46 100644 --- a/Test/Sha1Test/project.vpj +++ b/Test/Sha1Test/project.vpj @@ -8,8 +8,8 @@ "flags": ["-filetype=obj", "-relocation-model=pic"] }, "linker": { - "cmd": "clang++", - "flags": ["-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32", "-Wl,--allow-multiple-definition"], + "cmd": "D:/msys64/mingw64/bin/clang++.exe", + "flags": ["-fuse-ld=lld", "-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32", "-Wl,--allow-multiple-definition"], "output": "sha1test.exe" }, "includes": [ diff --git a/Test/project.vpj b/Test/project.vpj index a3ba696..fc78815 100644 --- a/Test/project.vpj +++ b/Test/project.vpj @@ -9,8 +9,8 @@ "flags": ["-filetype=obj", "-relocation-model=pic"] }, "linker": { - "cmd": "clang++", - "flags": ["-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32", "-Wl,--allow-multiple-definition"], + "cmd": "D:/msys64/mingw64/bin/clang++.exe", + "flags": ["-fuse-ld=lld", "-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32", "-Wl,--allow-multiple-definition"], "output": "app.exe" }, "includes": [ diff --git a/project.vpj b/project.vpj index 52da420..6e775ef 100644 --- a/project.vpj +++ b/project.vpj @@ -9,8 +9,8 @@ "flags": ["-filetype=obj", "-relocation-model=pic"] }, "linker": { - "cmd": "clang++", - "flags": ["-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32", "-Wl,--allow-multiple-definition", "-Wl,--stack,0x1000000", "-Wl,--no-insert-timestamp"], + "cmd": "D:/msys64/mingw64/bin/clang++.exe", + "flags": ["-fuse-ld=lld", "-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32", "-Wl,--allow-multiple-definition", "-Wl,--stack,0x1000000", "-Wl,--no-insert-timestamp"], "output": "TransPyV.exe" }, "includes": [