修正了一些错误
This commit is contained in:
@@ -11,6 +11,7 @@ import w32.fileio as fileio
|
||||
import viperlib
|
||||
import sys
|
||||
import ast
|
||||
import lib.core.VLogger as VLogger
|
||||
import lib.core.IncludesScanner as IncludesScanner
|
||||
import lib.core.Handles.HandlesStruct as HandlesStruct
|
||||
import lib.core.Handles.HandlesImports as HandlesImports
|
||||
@@ -39,10 +40,20 @@ _mbuddy: memhub.MemManager | t.CPtr
|
||||
# 最大 includes SHA1 数(用于过滤)
|
||||
MAX_INCLUDES_SHA1: t.CDefine = 256
|
||||
# stub 读取缓冲区大小
|
||||
STUB_READ_BUF_SIZE: t.CDefine = 262144
|
||||
STUB_READ_BUF_SIZE: t.CDefine = 1048576
|
||||
# 最大 includes 条目数
|
||||
MAX_INCLUDES: t.CDefine = 256
|
||||
|
||||
# 全局 SHA1 映射存储器(内存中,不依赖 _sha1_map.txt 文件)
|
||||
# Phase1 扫描完成后填充,供所有函数使用
|
||||
_sha1_store_arr: bytes # SHA1 数组(每个 17 字节,MAX_INCLUDES 个)
|
||||
_sha1_store_mod: bytes # 模块名数组(每个 64 字节,MAX_INCLUDES 个)
|
||||
_sha1_store_rel: bytes # 相对路径数组(每个 256 字节,MAX_INCLUDES 个)
|
||||
_sha1_store_count: int # 条目数
|
||||
|
||||
# rel_path 最大长度
|
||||
MAX_REL_PATH_LEN: t.CDefine = 256
|
||||
|
||||
|
||||
# ============================================================
|
||||
# _sliced_path - 构建切片路径(stdlib.malloc 分配,调用者负责 free)
|
||||
@@ -217,77 +228,31 @@ def _collect_stub_sha1s(base_dir: str, bd_len: t.CSizeT, level: int,
|
||||
|
||||
|
||||
# ============================================================
|
||||
# _load_includes_sha1_set - 读取 _sha1_map.txt,收集 includes/ 开头的 SHA1
|
||||
# _load_includes_sha1_set - 从全局内存存储器收集 includes 的 SHA1
|
||||
#
|
||||
# _sha1_map.txt 格式:{sha16}:{rel_path}
|
||||
# 仅收集 rel_path 以 "includes/" 开头的 SHA1,用于过滤 stub 文件,
|
||||
# 避免加载 TransPyV 自身源文件(lib/*)的 stub 导致链接失败。
|
||||
# 数据来源是 PopulateSha1MapStore 填充的全局存储器(不读取 _sha1_map.txt)。
|
||||
# 用于过滤 stub 文件,避免加载 TransPyV 自身源文件(lib/*)的 stub 导致链接失败。
|
||||
# ============================================================
|
||||
def _load_includes_sha1_set(pool: memhub.MemBuddy | t.CPtr,
|
||||
temp_dir: str,
|
||||
sha1_set: str) -> int:
|
||||
"""读取 _sha1_map.txt,将 includes/ 开头的 SHA1 写入 sha1_set
|
||||
"""从全局内存存储器将 includes 的 SHA1 写入 sha1_set
|
||||
|
||||
pool/temp_dir 参数保留以兼容现有调用者,但不再使用。
|
||||
数据来源是 PopulateSha1MapStore 填充的全局存储器。
|
||||
sha1_set 大小为 MAX_INCLUDES_SHA1 * 17(每个 SHA1 16字符+null)
|
||||
返回找到的 includes SHA1 数量,-1 表示错误"""
|
||||
if pool is None or temp_dir is None or sha1_set is None:
|
||||
if sha1_set is None:
|
||||
return -1
|
||||
|
||||
# 构造路径 temp_dir/_sha1_map.txt(使用 stdlib.malloc 避免 mbuddy 池耗尽)
|
||||
dir_len: t.CSizeT = string.strlen(temp_dir)
|
||||
map_path: bytes = stdlib.malloc(dir_len + 32)
|
||||
if map_path is None:
|
||||
if _sha1_store_arr is None:
|
||||
return -1
|
||||
viperlib.snprintf(map_path, dir_len + 32, "%s/_sha1_map.txt", temp_dir)
|
||||
|
||||
# 打开文件
|
||||
f: fileio.File | t.CPtr = fileio.File(map_path, fileio.MODE.R)
|
||||
if f.closed:
|
||||
return -1
|
||||
|
||||
# 读取内容(使用 stdlib.malloc 避免 mbuddy 池耗尽)
|
||||
MAP_BUF_SIZE: t.CSizeT = 65536
|
||||
content: str = stdlib.malloc(MAP_BUF_SIZE)
|
||||
if content is None:
|
||||
f.close()
|
||||
return -1
|
||||
bytes_read: t.CInt64T = f.read_all(content, MAP_BUF_SIZE)
|
||||
f.close()
|
||||
if bytes_read <= 0:
|
||||
return -1
|
||||
if bytes_read < MAP_BUF_SIZE:
|
||||
content[bytes_read] = '\0'
|
||||
else:
|
||||
content[MAP_BUF_SIZE - 1] = '\0'
|
||||
|
||||
# 解析行: {sha1}:{rel_path}
|
||||
count: int = 0
|
||||
pos: t.CSizeT = 0
|
||||
content_len: t.CSizeT = bytes_read
|
||||
while pos < content_len:
|
||||
# 找行尾
|
||||
line_start: t.CSizeT = pos
|
||||
while pos < content_len:
|
||||
if content[pos] == '\n':
|
||||
break
|
||||
pos += 1
|
||||
line_len: t.CSizeT = pos - line_start
|
||||
pos += 1 # skip \n
|
||||
|
||||
# 最小长度: 16(sha1) + 1(:) + 9(includes/) = 26
|
||||
if line_len < 26:
|
||||
continue
|
||||
# 检查第17个字符是 ':'
|
||||
if content[line_start + 16] != ':':
|
||||
continue
|
||||
|
||||
# 检查 rel_path 是否以 "includes/" 开头
|
||||
rel_start: t.CSizeT = line_start + 17
|
||||
if string.strncmp(content + rel_start, "includes/", 9) == 0:
|
||||
if count < MAX_INCLUDES_SHA1:
|
||||
string.strncpy(sha1_set + count * 17, content + line_start, 16)
|
||||
sha1_set[count * 17 + 16] = '\0'
|
||||
count += 1
|
||||
|
||||
for i in range(_sha1_store_count):
|
||||
if count >= MAX_INCLUDES_SHA1:
|
||||
break
|
||||
idx: t.CSizeT = t.CSizeT(i) * 17
|
||||
string.strcpy(sha1_set + t.CSizeT(count) * 17, _sha1_store_arr + idx)
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
@@ -305,10 +270,10 @@ def _is_in_sha1_set(sha1: str, sha1_set: str, set_count: int) -> int:
|
||||
|
||||
|
||||
# ============================================================
|
||||
# WriteIncludesSha1Map - 将 includes 扫描结果写入 _sha1_map.txt
|
||||
# WriteIncludesSha1Map - 将 includes 扫描结果写入 _sha1_map.txt(人类可读输出)
|
||||
#
|
||||
# TransPyV.exe 自己生成 _sha1_map.txt(不再依赖 Projectrans.py 预生成),
|
||||
# 确保所有 includes 文件的 SHA1 都被记录,供 StubMerger 加载 stub 时过滤。
|
||||
# _sha1_map.txt 仅作为人类可读的调试输出,程序内部不读取此文件。
|
||||
# 机器分析使用 PopulateSha1MapStore 填充的全局内存存储器。
|
||||
#
|
||||
# Args:
|
||||
# mb: 内存池
|
||||
@@ -322,7 +287,7 @@ def WriteIncludesSha1Map(mb: memhub.MemBuddy | t.CPtr, temp_dir: str,
|
||||
scan_result: IncludesScanner.ScanResult | t.CPtr,
|
||||
filter_set: t.CChar | t.CPtr,
|
||||
filter_count: int) -> int:
|
||||
"""将 includes 扫描结果写入 _sha1_map.txt(供 StubMerger 使用)
|
||||
"""将 includes 扫描结果写入 _sha1_map.txt(人类可读输出,程序内部使用内存存储器)
|
||||
|
||||
若 filter_set 不为 None 且 filter_count > 0,只写入 filter_set 中的条目。
|
||||
"""
|
||||
@@ -367,6 +332,212 @@ def WriteIncludesSha1Map(mb: memhub.MemBuddy | t.CPtr, temp_dir: str,
|
||||
return 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# PopulateSha1MapStore - 从扫描结果填充全局 SHA1 映射存储器
|
||||
#
|
||||
# 遍历 ScanResult.Entries,为每个条目构建 sha1→module_name 映射。
|
||||
# 模块名通过 _PathToModuleName(includes/rel_path) 计算。
|
||||
# 内存用 stdlib.malloc 分配(全局存储器,不随 mbuddy 释放)。
|
||||
#
|
||||
# Args:
|
||||
# scan_result: IncludesScanner 扫描结果
|
||||
#
|
||||
# Returns:
|
||||
# 填充的条目数,-1 表示失败
|
||||
# ============================================================
|
||||
def PopulateSha1MapStore(scan_result: IncludesScanner.ScanResult | t.CPtr) -> int:
|
||||
"""从扫描结果填充全局 SHA1 映射存储器
|
||||
|
||||
遍历 ScanResult.Entries,为每个条目构建 sha1→module_name 映射。
|
||||
模块名通过 _PathToModuleName(includes/rel_path) 计算。
|
||||
内存用 stdlib.malloc 分配(全局存储器,不随 mbuddy 释放)。
|
||||
"""
|
||||
global _sha1_store_arr, _sha1_store_mod, _sha1_store_rel, _sha1_store_count
|
||||
if scan_result is None:
|
||||
return -1
|
||||
# 释放旧存储器
|
||||
if _sha1_store_arr is not None:
|
||||
stdlib.free(_sha1_store_arr)
|
||||
if _sha1_store_mod is not None:
|
||||
stdlib.free(_sha1_store_mod)
|
||||
if _sha1_store_rel is not None:
|
||||
stdlib.free(_sha1_store_rel)
|
||||
_sha1_store_arr = stdlib.malloc(MAX_INCLUDES * 17)
|
||||
_sha1_store_mod = stdlib.malloc(MAX_INCLUDES * 64)
|
||||
_sha1_store_rel = stdlib.malloc(MAX_INCLUDES * MAX_REL_PATH_LEN)
|
||||
if _sha1_store_arr is None or _sha1_store_mod is None or _sha1_store_rel is None:
|
||||
return -1
|
||||
string.memset(_sha1_store_arr, 0, MAX_INCLUDES * 17)
|
||||
string.memset(_sha1_store_mod, 0, MAX_INCLUDES * 64)
|
||||
string.memset(_sha1_store_rel, 0, MAX_INCLUDES * MAX_REL_PATH_LEN)
|
||||
_sha1_store_count = 0
|
||||
entry_size_p: t.CSizeT = IncludesScanner.FileEntry.__sizeof__()
|
||||
for i in range(scan_result.Count):
|
||||
if _sha1_store_count >= MAX_INCLUDES:
|
||||
break
|
||||
ea: t.CUInt64T = t.CUInt64T(scan_result.Entries) + i * entry_size_p
|
||||
ent: IncludesScanner.FileEntry | t.CPtr = (IncludesScanner.FileEntry | t.CPtr)(t.CVoid(ea, t.CPtr))
|
||||
if ent is None or ent.Sha1 is None or ent.RelPath is None:
|
||||
continue
|
||||
# 构建完整路径 includes/{RelPath}(_PathToModuleName 需要)
|
||||
rp_len: t.CSizeT = string.strlen(ent.RelPath)
|
||||
full_path_buf: bytes = stdlib.malloc(rp_len + 10)
|
||||
if full_path_buf is None:
|
||||
continue
|
||||
viperlib.snprintf(full_path_buf, rp_len + 10, "includes/%s", ent.RelPath)
|
||||
mod_name: str = _PathToModuleName(full_path_buf)
|
||||
stdlib.free(full_path_buf)
|
||||
if mod_name is not None:
|
||||
idx: t.CSizeT = t.CSizeT(_sha1_store_count) * 17
|
||||
string.strcpy(_sha1_store_arr + idx, ent.Sha1)
|
||||
idx2: t.CSizeT = t.CSizeT(_sha1_store_count) * 64
|
||||
mn_len: t.CSizeT = string.strlen(mod_name)
|
||||
if mn_len < 64:
|
||||
string.strcpy(_sha1_store_mod + idx2, mod_name)
|
||||
else:
|
||||
string.strncpy(_sha1_store_mod + idx2, mod_name, 63)
|
||||
_sha1_store_mod[idx2 + 63] = '\0'
|
||||
stdlib.free(mod_name)
|
||||
# 保存 rel_path(供 Phase2 3.5 编译缺失 includes 使用)
|
||||
idx3: t.CSizeT = t.CSizeT(_sha1_store_count) * MAX_REL_PATH_LEN
|
||||
if rp_len < MAX_REL_PATH_LEN:
|
||||
string.strcpy(_sha1_store_rel + idx3, ent.RelPath)
|
||||
else:
|
||||
string.strncpy(_sha1_store_rel + idx3, ent.RelPath, MAX_REL_PATH_LEN - 1)
|
||||
_sha1_store_rel[idx3 + MAX_REL_PATH_LEN - 1] = '\0'
|
||||
_sha1_store_count += 1
|
||||
return _sha1_store_count
|
||||
|
||||
|
||||
# ============================================================
|
||||
# AppendToSha1MapStore - 追加条目到全局 SHA1 映射存储器
|
||||
#
|
||||
# Phase1 调用 PopulateSha1MapStore 填充 includes 文件,
|
||||
# Phase2 扫描 App 源文件后调用此函数追加 App 模块条目,
|
||||
# 确保编译 App 自身时跨模块 CDefine 查找能命中 App 模块
|
||||
# (如 StubMerger.MAX_INCLUDES 在编译 Phase2 时通过
|
||||
# _build_source_path_from_sha1 查找 StubMerger 的源路径)。
|
||||
#
|
||||
# 若存储器未初始化(PopulateSha1MapStore 未调用),自动初始化。
|
||||
# 若 SHA1 已存在(去重),跳过。
|
||||
#
|
||||
# Args:
|
||||
# sha1: SHA1 字符串(16 字符 + null)
|
||||
# rel_path: 相对路径(如 "lib/core/StubMerger.py",
|
||||
# 不含 includes/ 前缀,与 _sha1_map.txt 格式一致)
|
||||
#
|
||||
# Returns:
|
||||
# 0 成功(含去重跳过),1 失败
|
||||
# ============================================================
|
||||
def AppendToSha1MapStore(sha1: str, rel_path: str) -> int:
|
||||
"""追加条目到全局 SHA1 映射存储器(Phase2 用)"""
|
||||
global _sha1_store_arr, _sha1_store_mod, _sha1_store_rel, _sha1_store_count
|
||||
if sha1 is None or rel_path is None:
|
||||
return 1
|
||||
# 存储器未初始化:自动初始化(Phase2 可能先于 Phase1 调用)
|
||||
if _sha1_store_arr is None:
|
||||
_sha1_store_arr = stdlib.malloc(MAX_INCLUDES * 17)
|
||||
_sha1_store_mod = stdlib.malloc(MAX_INCLUDES * 64)
|
||||
_sha1_store_rel = stdlib.malloc(MAX_INCLUDES * MAX_REL_PATH_LEN)
|
||||
if _sha1_store_arr is None or _sha1_store_mod is None or _sha1_store_rel is None:
|
||||
return 1
|
||||
string.memset(_sha1_store_arr, 0, MAX_INCLUDES * 17)
|
||||
string.memset(_sha1_store_mod, 0, MAX_INCLUDES * 64)
|
||||
string.memset(_sha1_store_rel, 0, MAX_INCLUDES * MAX_REL_PATH_LEN)
|
||||
_sha1_store_count = 0
|
||||
if _sha1_store_count >= MAX_INCLUDES:
|
||||
return 1
|
||||
# 去重:若 SHA1 已存在则跳过
|
||||
for i in range(_sha1_store_count):
|
||||
sidx_d: t.CSizeT = t.CSizeT(i) * 17
|
||||
if string.strcmp(_sha1_store_arr + sidx_d, sha1) == 0:
|
||||
return 0
|
||||
# 模块名:rel_path "lib/core/StubMerger.py" → "lib.core.StubMerger"
|
||||
full_path_buf: bytes = stdlib.malloc(string.strlen(rel_path) + 10)
|
||||
if full_path_buf is None:
|
||||
return 1
|
||||
viperlib.snprintf(full_path_buf, string.strlen(rel_path) + 10, "includes/%s", rel_path)
|
||||
mod_name: str = _PathToModuleName(full_path_buf)
|
||||
stdlib.free(full_path_buf)
|
||||
if mod_name is None:
|
||||
return 1
|
||||
# 写入 SHA1
|
||||
idx_a: t.CSizeT = t.CSizeT(_sha1_store_count) * 17
|
||||
string.strcpy(_sha1_store_arr + idx_a, sha1)
|
||||
# 写入模块名
|
||||
idx_m: t.CSizeT = t.CSizeT(_sha1_store_count) * 64
|
||||
mn_len: t.CSizeT = string.strlen(mod_name)
|
||||
if mn_len < 64:
|
||||
string.strcpy(_sha1_store_mod + idx_m, mod_name)
|
||||
else:
|
||||
string.strncpy(_sha1_store_mod + idx_m, mod_name, 63)
|
||||
_sha1_store_mod[idx_m + 63] = '\0'
|
||||
stdlib.free(mod_name)
|
||||
# 写入相对路径
|
||||
idx_r: t.CSizeT = t.CSizeT(_sha1_store_count) * MAX_REL_PATH_LEN
|
||||
rp_len: t.CSizeT = string.strlen(rel_path)
|
||||
if rp_len < MAX_REL_PATH_LEN:
|
||||
string.strcpy(_sha1_store_rel + idx_r, rel_path)
|
||||
else:
|
||||
string.strncpy(_sha1_store_rel + idx_r, rel_path, MAX_REL_PATH_LEN - 1)
|
||||
_sha1_store_rel[idx_r + MAX_REL_PATH_LEN - 1] = '\0'
|
||||
_sha1_store_count += 1
|
||||
return 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# GetSha1StoreCount - 获取全局存储器条目数
|
||||
# ============================================================
|
||||
def GetSha1StoreCount() -> int:
|
||||
"""返回全局 SHA1 存储器的条目数"""
|
||||
return _sha1_store_count
|
||||
|
||||
|
||||
def GetSha1StoreArrPtr() -> bytes | t.CPtr:
|
||||
"""返回 SHA1 数组指针(每个条目 17 字节)"""
|
||||
return _sha1_store_arr
|
||||
|
||||
|
||||
def GetSha1StoreModArrPtr() -> bytes | t.CPtr:
|
||||
"""返回模块名数组指针(每个条目 64 字节)"""
|
||||
return _sha1_store_mod
|
||||
|
||||
|
||||
def GetSha1StoreRelArrPtr() -> bytes | t.CPtr:
|
||||
"""返回相对路径数组指针(每个条目 MAX_REL_PATH_LEN 字节)"""
|
||||
return _sha1_store_rel
|
||||
|
||||
|
||||
# ============================================================
|
||||
# GetSha1StoreArr - 获取全局存储器数组指针(直接遍历用)
|
||||
#
|
||||
# 返回 sha1_arr、mod_arr、rel_arr 三个数组的指针和条目数。
|
||||
# 调用者直接遍历数组:sha1_arr[i*17]、mod_arr[i*64]、rel_arr[i*MAX_REL_PATH_LEN]
|
||||
# 内存归全局存储器所有,调用者不要 free
|
||||
# ============================================================
|
||||
def GetSha1StoreArr(out_sha1_arr: t.CPtr, out_mod_arr: t.CPtr, out_rel_arr: t.CPtr) -> int:
|
||||
"""获取全局存储器数组指针
|
||||
|
||||
out_sha1_arr/out_mod_arr/out_rel_arr 是调用者提供的 t.CPtr 指针,
|
||||
函数将存储器数组指针写入这些位置。
|
||||
返回条目数,-1 失败
|
||||
"""
|
||||
if out_sha1_arr is None or out_mod_arr is None or out_rel_arr is None:
|
||||
return -1
|
||||
if _sha1_store_arr is None or _sha1_store_mod is None or _sha1_store_rel is None:
|
||||
return -1
|
||||
sp: t.CPtr = (t.CPtr | t.CPtr)(t.CVoid(out_sha1_arr, t.CPtr))
|
||||
if sp is not None:
|
||||
sp[0] = _sha1_store_arr
|
||||
mp: t.CPtr = (t.CPtr | t.CPtr)(t.CVoid(out_mod_arr, t.CPtr))
|
||||
if mp is not None:
|
||||
mp[0] = _sha1_store_mod
|
||||
rp: t.CPtr = (t.CPtr | t.CPtr)(t.CVoid(out_rel_arr, t.CPtr))
|
||||
if rp is not None:
|
||||
rp[0] = _sha1_store_rel
|
||||
return _sha1_store_count
|
||||
|
||||
|
||||
# ============================================================
|
||||
# _IsFuncDeclaredOrDefined - 检查 out_buf 中是否已有函数的 declare/define
|
||||
#
|
||||
@@ -499,6 +670,39 @@ def _CommentOutOpaqueTypeInBuf(out_buf: bytes, type_name_prefix: str) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# _is_submodule_of_imported - 检查模块名是否是已导入包的子模块
|
||||
#
|
||||
# deps.txt 只记录直接导入的模块名(如 "ast"),但包的 __init__.py
|
||||
# 内部 from .lexer import Lexer 会让 IR 引用 ast.lexer 子模块类型。
|
||||
# 当模块名包含 '.' 时,检查其顶层包名是否在 deps_buf 中。
|
||||
#
|
||||
# 例如:
|
||||
# deps_buf = "ast stdio string"
|
||||
# mod_name = "ast.lexer" → 顶层包 "ast" 在 deps_buf 中 → 返回 1
|
||||
# mod_name = "ast.token" → 顶层包 "ast" 在 deps_buf 中 → 返回 1
|
||||
# mod_name = "foo.bar" → 顶层包 "foo" 不在 deps_buf 中 → 返回 0
|
||||
# ============================================================
|
||||
def _is_submodule_of_imported(deps_buf: str, mod_name: str) -> int:
|
||||
"""检查 mod_name 是否是 deps_buf 中已导入包的子模块"""
|
||||
if deps_buf is None or mod_name is None:
|
||||
return 0
|
||||
# 查找第一个 '.',提取顶层包名
|
||||
dot_pos: str = string.strchr(mod_name, 46) # '.' = 46
|
||||
if dot_pos is None:
|
||||
return 0
|
||||
pkg_len: t.CSizeT = t.CSizeT(t.CUInt64T(dot_pos) - t.CUInt64T(mod_name))
|
||||
if pkg_len == 0 or pkg_len >= 256:
|
||||
return 0
|
||||
# 构造顶层包名(截断到 '.' 之前)
|
||||
# 直接在 mod_name 上临时截断检查,避免分配
|
||||
saved_ch: t.CChar = mod_name[pkg_len]
|
||||
mod_name[pkg_len] = '\0'
|
||||
result: int = HandlesImports.is_module_imported(deps_buf, mod_name)
|
||||
mod_name[pkg_len] = saved_ch
|
||||
return result
|
||||
|
||||
|
||||
# ============================================================
|
||||
# _HasFullTypeDefinition - 检查 out_buf 中是否已有完整类型定义(非 opaque)
|
||||
#
|
||||
@@ -806,7 +1010,70 @@ def _LoadAndAppendTextDeclares(temp_dir: str, td_len: t.CSizeT, dep_sha1: str,
|
||||
in_body = 1
|
||||
continue
|
||||
|
||||
# 跳过其他行(global 带初值定义、type 定义等,stub 已有 external 声明)
|
||||
# 提取类型定义(%... = type { ... } 或 %... = type < ... >)
|
||||
# stub.ll 中只有 opaque 声明,需要从 text.ll 中提取完整定义
|
||||
# 用于 alloca 和 getelementptr 等需要完整类型的指令
|
||||
if ll > 0 and dep_buf[ls] == '%':
|
||||
eq_td: t.CSizeT = ls + 1
|
||||
while eq_td < ls + ll:
|
||||
if dep_buf[eq_td] == '=':
|
||||
break
|
||||
eq_td += 1
|
||||
if eq_td < ls + ll:
|
||||
# 检查是否为完整定义(= type { 或 = type <)
|
||||
dep_is_full_td: int = 0
|
||||
if eq_td + 8 <= ls + ll:
|
||||
if dep_buf[eq_td + 2] == 't' and dep_buf[eq_td + 3] == 'y' \
|
||||
and dep_buf[eq_td + 4] == 'p' and dep_buf[eq_td + 5] == 'e' \
|
||||
and dep_buf[eq_td + 6] == ' ':
|
||||
if dep_buf[eq_td + 7] == '{' or dep_buf[eq_td + 7] == '<':
|
||||
dep_is_full_td = 1
|
||||
if dep_is_full_td == 1:
|
||||
# 临时截断到 '=' 之前(不含 '='),辅助函数期望纯类型名前缀
|
||||
saved_eq_td: t.CChar = dep_buf[eq_td]
|
||||
dep_buf[eq_td] = '\0'
|
||||
# 1. 注释掉 out_buf 中的 opaque 声明(如果有)
|
||||
_CommentOutOpaqueTypeInBuf(out_buf, dep_buf + ls)
|
||||
# 2. 检查 out_buf 中是否已有完整定义(避免重复)
|
||||
has_full_td: int = _HasFullTypeDefinition(out_buf, dep_buf + ls)
|
||||
dep_buf[eq_td] = saved_eq_td
|
||||
if has_full_td == 0:
|
||||
# 追加完整类型定义
|
||||
if out_pos + ll + 2 < out_size:
|
||||
string.strncpy(out_buf + out_pos, dep_buf + ls, ll)
|
||||
out_pos += ll
|
||||
out_buf[out_pos] = '\n'
|
||||
out_pos += 1
|
||||
out_buf[out_pos] = '\0'
|
||||
continue
|
||||
|
||||
# 提取全局变量定义(@... = ...)
|
||||
# 依赖 text.ll 中的全局变量带初值定义(如 @_mbuddy = global ...)
|
||||
# stub.ll 中只有 external 声明,需要从 text.ll 提取完整定义
|
||||
# 注释掉 out_buf 中的同名行(external 声明或旧定义),追加 text.ll 中的完整定义
|
||||
if ll > 0 and dep_buf[ls] == '@':
|
||||
eq_gv: t.CSizeT = ls + 1
|
||||
while eq_gv < ls + ll:
|
||||
if dep_buf[eq_gv] == '=':
|
||||
break
|
||||
eq_gv += 1
|
||||
if eq_gv < ls + ll:
|
||||
# 截断到 '=' 之前(不含 '='),得到全局变量名 @name
|
||||
saved_gv: t.CChar = dep_buf[eq_gv]
|
||||
dep_buf[eq_gv] = '\0'
|
||||
# 注释掉 out_buf 中的同名行(external 声明或旧定义)
|
||||
_CommentOutGlobalInBuf(out_buf, dep_buf + ls)
|
||||
dep_buf[eq_gv] = saved_gv
|
||||
# 追加完整定义
|
||||
if out_pos + ll + 2 < out_size:
|
||||
string.strncpy(out_buf + out_pos, dep_buf + ls, ll)
|
||||
out_pos += ll
|
||||
out_buf[out_pos] = '\n'
|
||||
out_pos += 1
|
||||
out_buf[out_pos] = '\0'
|
||||
continue
|
||||
|
||||
# 跳过其他行
|
||||
return out_pos
|
||||
|
||||
|
||||
@@ -826,13 +1093,14 @@ def _PathToModuleName(path: str) -> str:
|
||||
if path is None:
|
||||
return None
|
||||
plen: t.CSizeT = string.strlen(path)
|
||||
# 跳过 includes/ 前缀
|
||||
prefix: str = "includes/"
|
||||
# 跳过 includes/ 或 includes\ 前缀(Windows 反斜杠兼容)
|
||||
prefix_len: t.CSizeT = 9
|
||||
path_start: t.CSizeT = 0
|
||||
if plen > prefix_len and string.strncmp(path, prefix, prefix_len) == 0:
|
||||
path_start = prefix_len
|
||||
plen = plen - prefix_len
|
||||
if plen > prefix_len and string.strncmp(path, "includes", 8) == 0:
|
||||
psep_ch: t.CChar = path[8]
|
||||
if psep_ch == '/' or psep_ch == '\\':
|
||||
path_start = prefix_len
|
||||
plen = plen - prefix_len
|
||||
# 去掉 .py 后缀
|
||||
if plen > 3 and path[path_start + plen - 3] == '.' and path[path_start + plen - 2] == 'p' and path[path_start + plen - 1] == 'y':
|
||||
plen -= 3
|
||||
@@ -869,94 +1137,33 @@ def _PathToModuleName(path: str) -> str:
|
||||
|
||||
|
||||
# ============================================================
|
||||
# _BuildIncludesSha1Map - 从 _sha1_map.txt 构建 sha1 → module_name 映射
|
||||
# _BuildIncludesSha1Map - 从全局内存存储器构建 sha1 → module_name 映射
|
||||
#
|
||||
# _sha1_map.txt 格式: {sha1}:includes/{rel_path}\n
|
||||
# 数据来源是 PopulateSha1MapStore 填充的全局存储器(不读取 _sha1_map.txt)。
|
||||
# 返回两个数组(sha1_list 和 mod_list),长度存储在 count 中。
|
||||
# 所有内存由 stdlib.malloc 分配,调用者负责释放。
|
||||
# ============================================================
|
||||
def _BuildIncludesSha1Map(temp_dir: str, td_len: t.CSizeT,
|
||||
sha1_list: t.CChar | t.CPtr,
|
||||
mod_list: t.CChar | t.CPtr) -> int:
|
||||
"""从 _sha1_map.txt 构建 sha1→module_name 映射,返回条目数"""
|
||||
if temp_dir is None or sha1_list is None or mod_list is None:
|
||||
return 0
|
||||
"""从全局内存存储器复制 sha1→module_name 映射到调用者数组
|
||||
|
||||
map_path: bytes = stdlib.malloc(td_len + 32)
|
||||
if map_path is None:
|
||||
temp_dir/td_len 参数保留以兼容现有调用者,但不再使用。
|
||||
数据来源是 PopulateSha1MapStore 填充的全局存储器。
|
||||
"""
|
||||
if sha1_list is None or mod_list is None:
|
||||
return 0
|
||||
viperlib.snprintf(map_path, td_len + 32, "%s/_sha1_map.txt", temp_dir)
|
||||
mf: fileio.File | t.CPtr = fileio.File(map_path, fileio.MODE.R)
|
||||
if mf.closed:
|
||||
stdlib.free(map_path)
|
||||
if _sha1_store_arr is None or _sha1_store_mod is None:
|
||||
return 0
|
||||
map_buf: bytes = stdlib.malloc(STUB_READ_BUF_SIZE)
|
||||
if map_buf is None:
|
||||
mf.close()
|
||||
stdlib.free(map_path)
|
||||
return 0
|
||||
map_br: t.CInt64T = mf.read_all(map_buf, STUB_READ_BUF_SIZE)
|
||||
mf.close()
|
||||
stdlib.free(map_path)
|
||||
if map_br <= 0:
|
||||
stdlib.free(map_buf)
|
||||
return 0
|
||||
if map_br < STUB_READ_BUF_SIZE:
|
||||
map_buf[map_br] = '\0'
|
||||
else:
|
||||
map_buf[STUB_READ_BUF_SIZE - 1] = '\0'
|
||||
|
||||
count: int = 0
|
||||
pos: t.CSizeT = 0
|
||||
# 在循环外分配 sha1 缓冲区,避免反复 malloc/free 导致堆碎片化
|
||||
sha1: t.CChar | t.CPtr = stdlib.malloc(17)
|
||||
if sha1 is None:
|
||||
stdlib.free(map_buf)
|
||||
return 0
|
||||
while pos < map_br and count < MAX_INCLUDES:
|
||||
# 提取 SHA1(16 hex)
|
||||
string.strncpy(sha1, map_buf + pos, 16)
|
||||
sha1[16] = '\0'
|
||||
pos += 16
|
||||
# 跳过 ':'
|
||||
if pos < map_br and map_buf[pos] == ':':
|
||||
pos += 1
|
||||
# 提取路径(到 \r、\n 或 \0)
|
||||
# 注意: Windows CRLF 换行符是 \r\n,必须同时检查 \r 避免路径包含 \r
|
||||
path_start: t.CSizeT = pos
|
||||
while pos < map_br and map_buf[pos] != '\n' and map_buf[pos] != '\r' and map_buf[pos] != '\0':
|
||||
pos += 1
|
||||
path_len: t.CSizeT = pos - path_start
|
||||
# 跳过 \r\n 或 \n
|
||||
if pos < map_br and map_buf[pos] == '\r':
|
||||
pos += 1
|
||||
if pos < map_br and map_buf[pos] == '\n':
|
||||
pos += 1
|
||||
if path_len > 0:
|
||||
# 仅收集 includes/ 前缀的条目(用户文件如 App/ 不应进入 includes 映射)
|
||||
# 否则用户文件会被误判为 includes,导致 is_module_imported 模块名不匹配而跳过加载
|
||||
if path_len >= 9 and string.strncmp(map_buf + path_start, "includes/", 9) == 0:
|
||||
path_buf: t.CChar | t.CPtr = stdlib.malloc(path_len + 1)
|
||||
if path_buf is not None:
|
||||
string.strncpy(path_buf, map_buf + path_start, path_len)
|
||||
path_buf[path_len] = '\0'
|
||||
mod_name: str = _PathToModuleName(path_buf)
|
||||
stdlib.free(path_buf)
|
||||
if mod_name is not None:
|
||||
# 存储 sha1 和 mod_name 到数组中
|
||||
idx: t.CSizeT = t.CSizeT(count) * 17
|
||||
string.strcpy(sha1_list + idx, sha1)
|
||||
idx2: t.CSizeT = t.CSizeT(count) * 64
|
||||
mn_len: t.CSizeT = string.strlen(mod_name)
|
||||
if mn_len < 64:
|
||||
string.strcpy(mod_list + idx2, mod_name)
|
||||
else:
|
||||
string.strncpy(mod_list + idx2, mod_name, 63)
|
||||
mod_list[idx2 + 63] = '\0'
|
||||
stdlib.free(mod_name)
|
||||
count += 1
|
||||
stdlib.free(sha1)
|
||||
stdlib.free(map_buf)
|
||||
for i in range(_sha1_store_count):
|
||||
if count >= MAX_INCLUDES:
|
||||
break
|
||||
idx: t.CSizeT = t.CSizeT(i) * 17
|
||||
idx2: t.CSizeT = t.CSizeT(i) * 64
|
||||
string.strcpy(sha1_list + t.CSizeT(count) * 17, _sha1_store_arr + idx)
|
||||
string.strcpy(mod_list + t.CSizeT(count) * 64, _sha1_store_mod + idx2)
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
@@ -977,16 +1184,33 @@ def _BuildIncludesSha1Map(temp_dir: str, td_len: t.CSizeT,
|
||||
# ============================================================
|
||||
def _FindSha1ByModName(sha1_arr: t.CChar | t.CPtr, mod_arr: t.CChar | t.CPtr,
|
||||
count: int, mod_name: str) -> str:
|
||||
"""从模块名查找 SHA1(精确匹配)"""
|
||||
"""从模块名查找 SHA1(精确匹配 + 包名回退)
|
||||
|
||||
精确匹配失败时,尝试 "{mod_name}.__init__" 回退,
|
||||
支持 import ast(包)→ ast.__init__ 的 SHA1 查找。
|
||||
"""
|
||||
if sha1_arr is None or mod_arr is None or mod_name is None:
|
||||
return None
|
||||
if count <= 0:
|
||||
return None
|
||||
# 第一遍:精确匹配
|
||||
for i in range(count):
|
||||
idx: t.CSizeT = t.CSizeT(i) * 64
|
||||
if string.strcmp(mod_arr + idx, mod_name) == 0:
|
||||
sidx: t.CSizeT = t.CSizeT(i) * 17
|
||||
return sha1_arr + sidx
|
||||
# 第二遍:包名回退("ast" → "ast.__init__")
|
||||
nm_len: t.CSizeT = string.strlen(mod_name)
|
||||
init_buf: bytes = stdlib.malloc(nm_len + 12)
|
||||
if init_buf is not None:
|
||||
viperlib.snprintf(init_buf, nm_len + 12, "%s.__init__", mod_name)
|
||||
for i in range(count):
|
||||
idx2: t.CSizeT = t.CSizeT(i) * 64
|
||||
if string.strcmp(mod_arr + idx2, init_buf) == 0:
|
||||
sidx2: t.CSizeT = t.CSizeT(i) * 17
|
||||
stdlib.free(init_buf)
|
||||
return sha1_arr + sidx2
|
||||
stdlib.free(init_buf)
|
||||
return None
|
||||
|
||||
|
||||
@@ -1027,13 +1251,13 @@ def _AddSha1ToSet(set_buf: t.CChar | t.CPtr, count: int, sha1: str) -> int:
|
||||
#
|
||||
# 算法(工作列表):
|
||||
# 1. 扫描 source_dir 下的 .py 文件,解析 AST 获取 _imported_modules
|
||||
# 2. 构建 SHA1→module_name 映射(从 _sha1_map.txt)
|
||||
# 2. 构建 SHA1→module_name 映射(从全局内存存储器)
|
||||
# 3. 工作列表递归:模块名 → 查 SHA1 → 加入 reachable_set → 读 .deps.txt → 追加新模块名
|
||||
#
|
||||
# Args:
|
||||
# mb: 内存池
|
||||
# source_dir: 源文件目录(Config.SourceDir)
|
||||
# temp_dir: 临时目录(_sha1_map.txt 和 .deps.txt 所在位置)
|
||||
# temp_dir: 临时目录(.deps.txt 所在位置;_sha1_map.txt 仅人类可读输出,不读取)
|
||||
# reachable_set: 输出参数,可达 SHA1 集合缓冲区(MAX_INCLUDES_SHA1 * 17 字节)
|
||||
#
|
||||
# Returns:
|
||||
@@ -1060,11 +1284,14 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy | t.CPtr, source_dir: str,
|
||||
return -1
|
||||
map_count: int = _BuildIncludesSha1Map(temp_dir, td_len_r, sha1_arr, mod_arr)
|
||||
if map_count <= 0:
|
||||
stdio.printf("[Reachable] 无法构建 SHA1 映射\n")
|
||||
VLogger.error("无法构建 SHA1 映射", "Reachable")
|
||||
stdlib.free(sha1_arr)
|
||||
stdlib.free(mod_arr)
|
||||
return -1
|
||||
stdio.printf("[Reachable] SHA1 映射: %d 个\n", map_count)
|
||||
fb: t.CChar | t.CPtr = VLogger.fmt_buf()
|
||||
if fb is not None:
|
||||
viperlib.snprintf(fb, 1024, "SHA1 映射: %d 个", map_count)
|
||||
VLogger.debug(fb, "Reachable")
|
||||
|
||||
# 2. 扫描 source_dir 下的 .py 文件,收集直接依赖
|
||||
dir_len: t.CSizeT = string.strlen(source_dir)
|
||||
@@ -1099,7 +1326,10 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy | t.CPtr, source_dir: str,
|
||||
|
||||
handle: win32base.HANDLE = win32file.FindFirstFileA(pattern, find_data)
|
||||
if handle == win32base.INVALID_HANDLE_VALUE:
|
||||
stdio.printf("[Reachable] 未找到 .py 文件: %s\n", pattern)
|
||||
fb: t.CChar | t.CPtr = VLogger.fmt_buf()
|
||||
if fb is not None:
|
||||
viperlib.snprintf(fb, 1024, "未找到 .py 文件: %s", pattern)
|
||||
VLogger.warning(fb, "Reachable")
|
||||
stdlib.free(pattern)
|
||||
stdlib.free(find_data)
|
||||
stdlib.free(sha1_arr)
|
||||
@@ -1176,7 +1406,10 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy | t.CPtr, source_dir: str,
|
||||
worklist[wl_len - 1] = '\0'
|
||||
wl_len -= 1
|
||||
|
||||
stdio.printf("[Reachable] 源文件直接依赖: '%s'\n", worklist)
|
||||
fb: t.CChar | t.CPtr = VLogger.fmt_buf()
|
||||
if fb is not None:
|
||||
viperlib.snprintf(fb, 1024, "源文件直接依赖: '%s'", worklist)
|
||||
VLogger.debug(fb, "Reachable")
|
||||
|
||||
# 2.5. 全局预加入基础容器模块(_list/_dict/json)
|
||||
#
|
||||
@@ -1223,7 +1456,10 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy | t.CPtr, source_dir: str,
|
||||
worklist[wl_len] = ' '
|
||||
wl_len += 1
|
||||
worklist[wl_len] = '\0'
|
||||
stdio.printf("[Reachable] 预加入容器模块后: '%s'\n", worklist)
|
||||
fb: t.CChar | t.CPtr = VLogger.fmt_buf()
|
||||
if fb is not None:
|
||||
viperlib.snprintf(fb, 1024, "预加入容器模块后: '%s'", worklist)
|
||||
VLogger.debug(fb, "Reachable")
|
||||
|
||||
# 3. 工作列表算法:递归收集可达 SHA1
|
||||
processed: bytes = stdlib.malloc(8192)
|
||||
@@ -1261,13 +1497,14 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy | t.CPtr, source_dir: str,
|
||||
if HandlesImports.is_module_imported(processed, mod_buf) != 0:
|
||||
already = 1
|
||||
if already == 0:
|
||||
# 加入 processed
|
||||
if proc_len + name_len + 1 < 8192:
|
||||
string.strcpy(processed + proc_len, mod_buf)
|
||||
proc_len += name_len
|
||||
processed[proc_len] = ' '
|
||||
proc_len += 1
|
||||
processed[proc_len] = '\0'
|
||||
# 加入 processed(嵌套 if 拆分 + 用 = 代替 += 绕过 TPC AUGASGN bug)
|
||||
if processed is not None:
|
||||
if proc_len + name_len + 1 < 8192:
|
||||
string.strcpy(processed + proc_len, mod_buf)
|
||||
proc_len = proc_len + name_len
|
||||
processed[proc_len] = ' '
|
||||
proc_len = proc_len + 1
|
||||
processed[proc_len] = '\0'
|
||||
|
||||
# 查找 SHA1
|
||||
found_sha1: str = _FindSha1ByModName(sha1_arr, mod_arr, map_count, mod_buf)
|
||||
@@ -1289,17 +1526,18 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy | t.CPtr, source_dir: str,
|
||||
deps_buf[dbr] = '\0'
|
||||
else:
|
||||
deps_buf[2047] = '\0'
|
||||
# 追加到 worklist(确保前面有空格分隔符,避免模块名合并)
|
||||
# 追加到 worklist(嵌套 if 拆分 + 用 = 代替 += 绕过 TPC AUGASGN bug)
|
||||
dl: t.CSizeT = string.strlen(deps_buf)
|
||||
if dl > 0 and wl_len + dl + 2 < 8192:
|
||||
if wl_len > 0 and worklist[wl_len - 1] != ' ':
|
||||
if worklist is not None:
|
||||
if dl > 0 and wl_len + dl + 2 < 8192:
|
||||
if wl_len > 0 and worklist[wl_len - 1] != ' ':
|
||||
worklist[wl_len] = ' '
|
||||
wl_len = wl_len + 1
|
||||
string.strcpy(worklist + wl_len, deps_buf)
|
||||
wl_len = wl_len + dl
|
||||
worklist[wl_len] = ' '
|
||||
wl_len += 1
|
||||
string.strcpy(worklist + wl_len, deps_buf)
|
||||
wl_len += dl
|
||||
worklist[wl_len] = ' '
|
||||
wl_len += 1
|
||||
worklist[wl_len] = '\0'
|
||||
wl_len = wl_len + 1
|
||||
worklist[wl_len] = '\0'
|
||||
stdlib.free(deps_buf)
|
||||
else:
|
||||
df.close()
|
||||
@@ -1310,15 +1548,19 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy | t.CPtr, source_dir: str,
|
||||
stdlib.free(worklist)
|
||||
stdlib.free(processed)
|
||||
|
||||
stdio.printf("[Reachable] 可达 SHA1: %d 个\n", reachable_count)
|
||||
fb: t.CChar | t.CPtr = VLogger.fmt_buf()
|
||||
if fb is not None:
|
||||
viperlib.snprintf(fb, 1024, "可达 SHA1: %d 个", reachable_count)
|
||||
VLogger.debug(fb, "Reachable")
|
||||
return reachable_count
|
||||
|
||||
|
||||
# ============================================================
|
||||
# BuildCombinedIR - 组合本地 stub + 依赖 stubs + 本地 text → 完整 IR
|
||||
#
|
||||
# 按需加载:读取 deps.txt 获取导入模块名,通过 _sha1_map.txt 查找 SHA1,
|
||||
# 按需加载:读取 deps.txt 获取导入模块名,通过内存存储器查找 SHA1,
|
||||
# 仅加载实际使用的依赖 stub(而非全部扫描)。
|
||||
# _sha1_map.txt 仅作为人类可读输出,程序内部不读取。
|
||||
# ============================================================
|
||||
def BuildCombinedIR(temp_dir: str, local_sha1: str,
|
||||
out_buf: bytes, out_size: t.CSizeT) -> t.CSizeT:
|
||||
@@ -1387,7 +1629,7 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str,
|
||||
|
||||
# 2. 按需加载依赖 stubs(根据 deps.txt 过滤,而非扫描全部)
|
||||
# 先加载依赖 stub(含 type 定义),确保 type 定义在本地 text.ll 的 define 块之前
|
||||
# 2a. 构建 includes SHA1 → module_name 映射(从 _sha1_map.txt)
|
||||
# 2a. 构建 includes SHA1 → module_name 映射(从全局内存存储器)
|
||||
sha1_arr: bytes = stdlib.malloc(MAX_INCLUDES * 17)
|
||||
mod_arr: bytes = stdlib.malloc(MAX_INCLUDES * 64)
|
||||
inc_count: int = 0
|
||||
@@ -1436,8 +1678,17 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str,
|
||||
is_include = 1
|
||||
if deps_loaded != 0:
|
||||
idx_mi: t.CSizeT = t.CSizeT(ii) * 64
|
||||
if HandlesImports.is_module_imported(deps_buf, mod_arr + idx_mi) != 0:
|
||||
dep_mod_name: str = mod_arr + idx_mi
|
||||
if HandlesImports.is_module_imported(deps_buf, dep_mod_name) != 0:
|
||||
should_load = 1
|
||||
else:
|
||||
# 传递性依赖:deps.txt 只记录直接导入(如 ast),
|
||||
# 但包的 __init__.py 内部 from .lexer import Lexer
|
||||
# 会让 IR 引用 ast.lexer 子模块类型。
|
||||
# 检查 dep_mod_name 是否是已导入包的子模块
|
||||
# (如 ast.lexer 是 ast 的子模块)
|
||||
if _is_submodule_of_imported(deps_buf, dep_mod_name) != 0:
|
||||
should_load = 1
|
||||
break
|
||||
if is_include == 0:
|
||||
# 非 includes stub(用户文件),总是加载
|
||||
@@ -1492,7 +1743,10 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str,
|
||||
if chk_path is not None:
|
||||
chk_f: fileio.File | t.CPtr = fileio.File(chk_path, fileio.MODE.R)
|
||||
if chk_f.closed:
|
||||
stdio.printf("[FATAL][BuildCombinedIR] 依赖模块 '%s' (sha1=%s) 的 stub 文件不存在: %s,立即终止编译\n", mod_nm, found_s, chk_path)
|
||||
fb: t.CChar | t.CPtr = VLogger.fmt_buf()
|
||||
if fb is not None:
|
||||
viperlib.snprintf(fb, 1024, "依赖模块 '%s' (sha1=%s) 的 stub 文件不存在: %s,立即终止编译", mod_nm, found_s, chk_path)
|
||||
VLogger.error(fb, "BuildCombinedIR")
|
||||
sys.exit(1)
|
||||
chk_f.close()
|
||||
stdlib.free(chk_path)
|
||||
|
||||
Reference in New Issue
Block a user