修复了大量存在的问题,增加了假鸭子类型等等机制

This commit is contained in:
2026-06-25 14:49:46 +08:00
parent 19f2787db0
commit d88d11b646
827 changed files with 32617 additions and 18316 deletions

View File

@@ -8,8 +8,10 @@ import struct
from lib.utils.helpers import DetectFileType
from lib.core.Handles.HandlesBase import CTypeInfo
from lib.core.SymbolUtils import IsTModule, AnnotationContainsName
from lib.includes import t
from lib.constants.config import mode as _ConfigMode
from lib.core.VLogger import get_logger as _vlog
from lib.core.Translator.BaseTranslator import _StrictLog
@@ -19,13 +21,13 @@ class PythonParserMixin:
提供辅助文件解析、symbin 文件加载与 Python 源文件解析能力。
"""
def ParseHelperFiles(self, HelperFiles, encoding='utf-8'):
def ParseHelperFiles(self, HelperFiles: list[str], encoding: str = 'utf-8') -> None:
"""解析辅助文件,提取符号信息
支持 .py, .c, .h 和 .symbin 文件
"""
for FilePath in HelperFiles:
ext = DetectFileType(FilePath)
ext: str = DetectFileType(FilePath)
if ext == '.py':
self.ParsePythonFile(FilePath, encoding, UpdateCurrentFile=False)
elif ext == '.c':
@@ -33,7 +35,7 @@ class PythonParserMixin:
elif FilePath.endswith('.symbin'):
self.LoadSymbinFile(FilePath)
def LoadSymbinFile(self, FilePath):
def LoadSymbinFile(self, FilePath: str) -> None:
"""从.symbin文件加载符号表
Args:
@@ -41,32 +43,27 @@ class PythonParserMixin:
"""
try:
with open(FilePath, 'rb') as f:
data = f.read()
data: bytes = f.read()
# 导入序列化函数(避免循环导入)
import sys
import os
import json
import struct
# 解析二进制格式
if len(data) < 4:
print(f"Warning: Invalid symbin file (too short): {FilePath}")
_vlog().warning(f"无效的 symbin 文件 (太短): {FilePath}")
return
# 解析4字节长度头小端序
length = struct.unpack('<I', data[:4])[0]
JsonData = data[4:4+length]
symbols = json.loads(JsonData.decode('utf-8'))
length: int = struct.unpack('<I', data[:4])[0]
JsonData: bytes = data[4:4+length]
symbols: dict = json.loads(JsonData.decode('utf-8'))
# 合并到当前符号表
self.SymbolTable.update(symbols)
self.DebugPrint(f"[SYMBIN] Loaded {len(symbols)} symbols from {FilePath}")
except Exception as e:
print(f"Warning: Failed to Load symbin file {FilePath}: {e}")
_vlog().warning(f"加载 symbin 文件失败 {FilePath}: {e}")
def ParsePythonFile(self, FilePath, encoding='utf-8', UpdateCurrentFile=True):
def ParsePythonFile(self, FilePath: str, encoding: str = 'utf-8', UpdateCurrentFile: bool = True) -> None:
"""解析Python文件提取类、函数、变量信息
Args:
@@ -76,41 +73,41 @@ class PythonParserMixin:
"""
try:
with open(FilePath, 'r', encoding=encoding) as f:
content = f.read()
content: str = f.read()
# 保存当前处理的文件路径(仅在主文件处理时更新)
if UpdateCurrentFile:
self.CurrentFile = FilePath
# 解析Python代码为AST
tree = ast.parse(content)
tree: ast.Module = ast.parse(content)
# 保存 AST 树供后续使用
self.tree = tree
# 第一遍:检测特殊赋值语句,收集跟在 class 后面的
# 遍历 tree.body找到所有 ClassDef然后收集紧随其后的赋值语句
i = 0
i: int = 0
while i < len(tree.body):
node = tree.body[i]
node: ast.stmt = tree.body[i]
# 检测 ClassDef
if isinstance(node, ast.ClassDef):
ClassName = node.name
ClassName: str = node.name
# 使用类名_行号作为唯一 key
ClassKey = f"{ClassName}_{node.lineno}"
ClassKey: str = f"{ClassName}_{node.lineno}"
# 检查后面的语句,收集特殊赋值
j = i + 1
j: int = i + 1
while j < len(tree.body):
NextNode = tree.body[j]
NextNode: ast.stmt = tree.body[j]
if isinstance(NextNode, ast.Assign):
# 检测 xxx = xxx 或 xxx = yyy
if len(NextNode.targets) == 1 and isinstance(NextNode.targets[0], ast.Name):
TargetName = NextNode.targets[0].id
TargetName: str = NextNode.targets[0].id
if isinstance(NextNode.value, ast.Name):
ValueName = NextNode.value.id
ValueName: str = NextNode.value.id
if ValueName == ClassName:
# 这是 class 的实例化
if ClassKey not in self.EmbeddedAssignments:
@@ -121,23 +118,23 @@ class PythonParserMixin:
elif isinstance(NextNode, ast.AnnAssign):
# 检测 xxx: t.Postdefinition(yyy) 或 xxx: t.CTypedef = xxx 或 xxx: t.CPtr = xxx
if isinstance(NextNode.target, ast.Name):
TargetName = NextNode.target.id
TargetName: str = NextNode.target.id
# 先处理新格式t.Postdefinition(...)
annotation = NextNode.annotation
original_AnnotationStr = ast.dump(NextNode.annotation)
PostdefNode = None
annotation: ast.expr | None = NextNode.annotation
original_AnnotationStr: str = ast.dump(NextNode.annotation)
PostdefNode: ast.Call | None = None
# 递归搜索整个注解树,找到 t.Postdefinition(...) 调用
def find_PostdefNode(node):
def find_PostdefNode(node: ast.AST) -> ast.Call | None:
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Attribute):
if hasattr(node.func.value, 'id') and node.func.value.id == 't' and node.func.attr == 'Postdefinition':
if hasattr(node.func.value, 'id') and IsTModule(node.func.value.id, self.SymbolTable) and node.func.attr == 'Postdefinition':
return node
elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
LeftResult = find_PostdefNode(node.left)
LeftResult: ast.Call | None = find_PostdefNode(node.left)
if LeftResult:
return LeftResult
RightResult = find_PostdefNode(node.right)
RightResult: ast.Call | None = find_PostdefNode(node.right)
if RightResult:
return RightResult
return None
@@ -148,25 +145,25 @@ class PythonParserMixin:
if (isinstance(PostdefNode.func, ast.Attribute) and
hasattr(PostdefNode.func, 'value') and
hasattr(PostdefNode.func.value, 'id') and
PostdefNode.func.value.id == 't' and
IsTModule(PostdefNode.func.value.id, self.SymbolTable) and
PostdefNode.func.attr == 'Postdefinition' and
PostdefNode.args):
# 检查第一个参数是否是当前类名
arg = PostdefNode.args[0]
arg: ast.expr = PostdefNode.args[0]
if isinstance(arg, ast.Name) and arg.id == ClassName:
# 检查是否包含 CTypedef 或 CPtr
HasCtypedef = False
IsPtr = False
HasCtypedef: bool = False
IsPtr: bool = False
if 'CTypedef' in original_AnnotationStr:
HasCtypedef = True
if 'CPtr' in original_AnnotationStr:
IsPtr = True
# 解析维度列表(第二个参数)
dimensions = []
dimensions: list[ast.expr] = []
if len(PostdefNode.args) > 1:
# 第二个参数应该是一个列表
dim_arg = PostdefNode.args[1]
dim_arg: ast.expr = PostdefNode.args[1]
if isinstance(dim_arg, ast.List):
for DimExpr in dim_arg.elts:
dimensions.append(DimExpr)
@@ -174,9 +171,9 @@ class PythonParserMixin:
# 如果没有从 Postdefinition 参数中获取维度,尝试从类型注解中的 Subscript 获取
# 例如memory_block_t[MAX_ORDER + 1] | t.CPtr
if not dimensions:
def extract_subscript_dims(node):
def extract_subscript_dims(node: ast.AST) -> list[ast.expr]:
"""递归提取 Subscript 节点的维度"""
dims = []
dims: list[ast.expr] = []
if isinstance(node, ast.Subscript):
# 提取当前维度
if isinstance(node.slice, ast.Constant):
@@ -193,13 +190,13 @@ class PythonParserMixin:
return dims
# 从注解中提取维度
subscript_dims = extract_subscript_dims(annotation)
subscript_dims: list[ast.expr] = extract_subscript_dims(annotation)
if subscript_dims:
# 反转维度顺序(从内到外)
dimensions = list(reversed(subscript_dims))
# 获取赋值值
value = None
value: ast.expr | None = None
if hasattr(NextNode, 'value') and NextNode.value is not None:
value = NextNode.value
@@ -241,11 +238,11 @@ class PythonParserMixin:
# 而应该生成独立的 typedef 语句,所以不添加到 TypedefAssignments
elif NextNode.annotation and isinstance(NextNode.annotation, ast.Attribute):
if isinstance(NextNode.annotation.value, ast.Name):
annot_id = NextNode.annotation.value.id
annot_attr = NextNode.annotation.attr
annot_id: str = NextNode.annotation.value.id
annot_attr: str = NextNode.annotation.attr
if annot_id == 't' and NextNode.value:
if isinstance(NextNode.value, ast.Name):
ValueName = NextNode.value.id
ValueName: str = NextNode.value.id
if ValueName == ClassName:
# 检查是 CTypedef 还是 CPtr
if annot_attr == t.CPtr.__name__:
@@ -267,15 +264,15 @@ class PythonParserMixin:
# 提取类定义(作为结构体)
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
ClassName = node.name
ClassName: str = node.name
# 先把类名加入 GeneratedTypes这样 GetTypeName 可以正确识别
self.GeneratedTypes.add(ClassName)
# 检测是否是 CEnum
IsCenum = False
IsCenum: bool = False
for base in node.bases:
if isinstance(base, ast.Attribute):
BaseStr = ast.dump(base)
BaseStr: str = ast.dump(base)
if 'CEnum' in BaseStr or 'REnum' in BaseStr:
IsCenum = True
break
@@ -285,19 +282,19 @@ class PythonParserMixin:
break
# 检测是否有 @t.Object 装饰器或继承自 t.Object
IsCpythonObject = False
IsCpythonObject: bool = False
if hasattr(node, 'decorator_list') and node.decorator_list:
for decorator in node.decorator_list:
if isinstance(decorator, ast.Attribute):
if (hasattr(decorator.value, 'id') and
decorator.value.id == 't' and
IsTModule(decorator.value.id, self.SymbolTable) and
decorator.attr == 'Object'):
IsCpythonObject = True
break
elif isinstance(decorator, ast.Call):
if isinstance(decorator.func, ast.Attribute):
if (hasattr(decorator.func.value, 'id') and
decorator.func.value.id == 't' and
IsTModule(decorator.func.value.id, self.SymbolTable) and
decorator.func.attr == 'Object'):
IsCpythonObject = True
break
@@ -313,7 +310,7 @@ class PythonParserMixin:
for base in node.bases:
if isinstance(base, ast.Attribute):
if (hasattr(base.value, 'id') and
base.value.id == 't' and
IsTModule(base.value.id, self.SymbolTable) and
base.attr == 'Object'):
IsCpythonObject = True
break
@@ -324,7 +321,7 @@ class PythonParserMixin:
if IsCenum:
# 注册为 enum 类型
EnumNode = CTypeInfo()
EnumNode: CTypeInfo = CTypeInfo()
EnumNode.Name = ClassName
EnumNode.IsEnum = True
EnumNode.set('file', '<stdin>')
@@ -333,8 +330,8 @@ class PythonParserMixin:
# 注册 enum 成员
for item in node.body:
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
MemberName = item.target.id
MemberNode = CTypeInfo()
MemberName: str = item.target.id
MemberNode: CTypeInfo = CTypeInfo()
MemberNode.Name = MemberName
MemberNode.BaseType = t.CEnum(ClassName)
MemberNode.value = None
@@ -348,49 +345,43 @@ class PythonParserMixin:
continue
# 检测是否是 Anonymous
IsAnonymous = False
IsAnonymous: bool = False
# 提取结构体成员信息
members = {}
members: dict[str, CTypeInfo] = {}
for item in node.body:
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
VarName = item.target.id
VarName: str = item.target.id
try:
TypeInfo = self.TypeMergeHandler.GetCTypeInfo(item.annotation)
TypeInfo: CTypeInfo | None = self.TypeMergeHandler.GetCTypeInfo(item.annotation)
if TypeInfo is None:
TypeInfo = CTypeInfo()
TypeInfo.BaseType = t.CInt()
IsPtr = TypeInfo.IsPtr
IsPtr: bool = TypeInfo.IsPtr
if not IsPtr:
if isinstance(item.annotation, ast.BinOp) and isinstance(item.annotation.op, ast.BitOr):
LeftStr = ast.dump(item.annotation.left)
RightStr = ast.dump(item.annotation.right)
if 'CPtr' in LeftStr or 'CPtr' in RightStr:
if AnnotationContainsName(item.annotation, 'CPtr'):
IsPtr = True
dims = []
dims: list[int] = []
if hasattr(item.annotation, 'slice') and isinstance(item.annotation.slice, ast.Constant):
if isinstance(item.annotation.value, ast.Name):
if item.annotation.value.id == 't':
if IsTModule(item.annotation.value.id, self.SymbolTable):
if hasattr(item.annotation, 'slice'):
try:
dims.append(int(item.annotation.slice.value))
except Exception as _e:
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _ConfigMode
if _ConfigMode == "strict":
raise
_vlog().warning(f"解析数组维度失败: {_e}", "Exception")
MemberInfo = TypeInfo.Copy()
MemberInfo: CTypeInfo = TypeInfo.Copy()
if dims:
MemberInfo.ArrayDims = [str(d) for d in dims]
members[VarName] = MemberInfo
except Exception as _e:
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _ConfigMode
if _ConfigMode == "strict":
raise
_vlog().warning(f"解析结构体成员失败: {_e}", "Exception")
StructNode = CTypeInfo()
StructNode: CTypeInfo = CTypeInfo()
StructNode.Name = ClassName
StructNode.IsStruct = True
StructNode.Members = members or {}
@@ -399,26 +390,27 @@ class PythonParserMixin:
StructNode.set('IsCpythonObject', IsCpythonObject)
self.SymbolTable.insert(ClassName, StructNode)
elif isinstance(node, ast.FunctionDef):
FuncName = node.name
FuncNode = CTypeInfo()
FuncNode.Name = FuncName
FuncNode.IsFunction = True
FuncNode.set('file', '<stdin>')
self.SymbolTable.insert(FuncName, FuncNode)
FuncName: str = node.name
if not self.SymbolTable.has(FuncName):
FuncNode: CTypeInfo = CTypeInfo()
FuncNode.Name = FuncName
FuncNode.IsFunction = True
FuncNode.set('file', '<stdin>')
self.SymbolTable.insert(FuncName, FuncNode)
elif isinstance(node, ast.AnnAssign):
if isinstance(node.target, ast.Name):
VarName = node.target.id
VarName: str = node.target.id
# 检查是否是 t.CTypedef 格式,如果是则跳过(由 HandleClassDef 处理)
is_ctypedef = False
is_ctypedef: bool = False
if node.annotation:
if isinstance(node.annotation, ast.Attribute):
if isinstance(node.annotation.value, ast.Name):
if node.annotation.value.id == 't' and node.annotation.attr == 'CTypedef':
if IsTModule(node.annotation.value.id, self.SymbolTable) and node.annotation.attr == 'CTypedef':
is_ctypedef = True
elif isinstance(node.annotation, ast.BinOp) and isinstance(node.annotation.op, ast.BitOr):
def _has_ctype(ann):
def _has_ctype(ann: ast.AST) -> bool:
if isinstance(ann, ast.Attribute):
return hasattr(ann.value, 'id') and ann.value.id == 't' and ann.attr == 'CTypedef'
return hasattr(ann.value, 'id') and IsTModule(ann.value.id) and ann.attr == 'CTypedef'
if isinstance(ann, ast.BinOp) and isinstance(ann.op, ast.BitOr):
return _has_ctype(ann.left) or _has_ctype(ann.right)
if isinstance(ann, ast.Subscript):
@@ -427,25 +419,23 @@ class PythonParserMixin:
if _has_ctype(node.annotation):
is_ctypedef = True
if not is_ctypedef:
IsPtr = False
IsPtr: bool = False
if node.annotation:
try:
TypeInfo = self.TypeMergeHandler.GetCTypeInfo(node.annotation)
TypeInfo: CTypeInfo | None = self.TypeMergeHandler.GetCTypeInfo(node.annotation)
IsPtr = TypeInfo.IsPtr if TypeInfo else False
except Exception as _e:
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _ConfigMode
if _ConfigMode == "strict":
raise
_vlog().warning(f"获取类型信息失败(ParsePythonFile): {_e}", "Exception")
var_node = CTypeInfo()
var_node: CTypeInfo = CTypeInfo()
var_node.Name = VarName
var_node.IsVariable = True
var_node.set('IsPtr', IsPtr)
var_node.set('file', '<stdin>')
self.SymbolTable.insert(VarName, var_node)
except Exception as e:
print(f'Warning: Failed to parse Python file {FilePath}: {e}')
_vlog().warning(f'解析 Python 文件失败 {FilePath}: {e}')
# ParseCFile 已移除 - .h/.c 文件的旧正则解析路径不再使用
# 类型解析现在直接通过 CTypeInfo 对象进行,不经过中间字符串格式