from __future__ import annotations import pydantic from typing import Optional, Literal, Any class AddressRange(pydantic.BaseModel): low: int high: int class ValueType(pydantic.BaseModel): descriptor: Optional[str] = None source: Optional[str] = None type_name: Optional[str] = None referenced_file_index: Optional[int] = None referenced_stabs_type_number: Optional[int] = None value_type: Optional[ValueType] = None return_type: Optional[ReturnType] = None modifier: Optional[str] = None vtable_index: Optional[int] = None is_constructor: Optional[bool] = None element_type: Optional[ElementType] = None element_count: Optional[int] = None class DeduplicatedTypeValueType(pydantic.BaseModel): descriptor: Literal["function_type", "type_name"] source: Optional[str] = None type_name: Optional[str] = None referenced_file_index: Optional[int] = None referenced_stabs_type_number: Optional[int] = None value_type: Optional[ValueType] = None return_type: Optional[ReturnType] = None modifier: Optional[str] = None vtable_index: Optional[int] = None is_constructor: Optional[bool] = None element_type: Optional[ElementType] = None element_count: Optional[int] = None class ParameterType(pydantic.BaseModel): descriptor: str source: Optional[str] = None type_name: Optional[str] = None referenced_file_index: Optional[int] = None referenced_stabs_type_number: Optional[int] = None value_type: Optional[ValueType] = None class Parameter(pydantic.BaseModel): descriptor: str name: str class_: str = pydantic.Field(..., alias="class") storage: Storage block_low: int block_high: int type: ParameterType class ReturnType(pydantic.BaseModel): descriptor: str source: Optional[str] = None type_name: Optional[str] = None referenced_file_index: Optional[int] = None referenced_stabs_type_number: Optional[int] = None value_type: Optional[ValueType] = None class FunctionType(pydantic.BaseModel): descriptor: str parameters: list[Parameter] modifier: str vtable_index: int is_constructor: bool return_type: Optional[ReturnType] = None class Storage(pydantic.BaseModel): type: str register_: Optional[str] = pydantic.Field(None, alias="register") register_class: Optional[str] = None dbx_register_number: Optional[int] = None register_index: Optional[int] = None is_by_reference: Optional[bool] = None stack_offset: Optional[int] = None global_location: Optional[str] = None global_address: Optional[int] = None class Constant(pydantic.BaseModel): value: int name: str class ElementType(pydantic.BaseModel): descriptor: Literal["array", "pointer", "type_name", "enum"] source: Optional[str] = None type_name: Optional[str] = None referenced_file_index: Optional[int] = None referenced_stabs_type_number: Optional[int] = None element_type: Optional[ElementType] = None element_count: Optional[int] = None value_type: Optional[ValueType] = None constants: Optional[list[Constant]] = None def parsed_size(self) -> tuple[int, str]: if self.descriptor == "pointer": return 1, "pointer" elif self.descriptor == "array": assert self.element_type assert self.element_count is not None if self.element_count == 0: # implicit size array _, type_name = self.element_type.parsed_size() return 0, type_name n, type_name = self.element_type.parsed_size() return self.element_count * n, type_name elif self.descriptor == "enum": return 1, "enum" else: # "type_name" assert self.type_name return 1, self.type_name class Local(pydantic.BaseModel): descriptor: str name: str class_: str = pydantic.Field(..., alias="class") storage: Storage block_low: int block_high: int type: ElementType storage_class: Optional[str] = None @property def parsed_size(self) -> tuple[int, str]: return self.type.parsed_size() class SubSourceFile(pydantic.BaseModel): address: int path: str class Function(pydantic.BaseModel): descriptor: str name: str address_range: AddressRange type: FunctionType locals: list[Local] line_numbers: list[list[int]] sub_source_files: list[SubSourceFile] storage_class: Optional[str] = None relative_path: Optional[str] = None class Global(pydantic.BaseModel): descriptor: str name: str class_: str = pydantic.Field(..., alias="class") storage: Storage block_low: int block_high: int type: ElementType storage_class: Optional[str] = None @property def parsed_size(self) -> tuple[int, str]: return self.type.parsed_size() class File(pydantic.BaseModel): descriptor: str path: str relative_path: str text_address: int types: list[Any] functions: list[Function] globals: list[Global] stabs_type_number_to_deduplicated_type_index: dict[str, int] class UnderlyingType(pydantic.BaseModel): descriptor: str source: str type_name: str referenced_file_index: int referenced_stabs_type_number: int class Field(pydantic.BaseModel): descriptor: str name: str relative_offset_bytes: int absolute_offset_bytes: int size_bits: int bitfield_offset_bits: Optional[int] = None underlying_type: Optional[UnderlyingType] = None source: Optional[str] = None type_name: Optional[str] = None referenced_file_index: Optional[int] = None referenced_stabs_type_number: Optional[int] = None value_type: Optional[ValueType] = None base_classes: Optional[list[Any]] = None fields: Optional[list[Field]] = None member_functions: Optional[list[Any]] = None element_type: Optional[ElementType] = None element_count: Optional[int] = None class FieldModel(pydantic.BaseModel): descriptor: str name: str relative_offset_bytes: int absolute_offset_bytes: int size_bits: int value_type: Optional[ValueType] = None source: Optional[str] = None type_name: Optional[str] = None referenced_file_index: Optional[int] = None referenced_stabs_type_number: Optional[int] = None element_type: Optional[ElementType] = None element_count: Optional[int] = None bitfield_offset_bits: Optional[int] = None underlying_type: Optional[UnderlyingType] = None fields: Optional[list[Field]] = None member_functions: Optional[list[Any]] = None base_classes: Optional[list[Any]] = None class DeduplicatedType(pydantic.BaseModel): descriptor: Literal["array", "builtin", "enum", "pointer", "struct", "type_name", "union"] name: Optional[str] = None storage_class: Optional[Literal["typedef"]] = None stabs_type_number: int files: list[int] class_: Optional[str] = pydantic.Field(None, alias="class") size_bits: Optional[int] = None base_classes: Optional[list[Any]] = None fields: Optional[list[FieldModel]] = None member_functions: Optional[list[Any]] = None source: Optional[str] = None type_name: Optional[str] = None referenced_file_index: Optional[int] = None referenced_stabs_type_number: Optional[int] = None value_type: Optional[DeduplicatedTypeValueType] = None conflict: Optional[bool] = None element_type: Optional[ElementType] = None element_count: Optional[int] = None constants: Optional[list[Constant]] = None class CCCJSONv7Model(pydantic.BaseModel): version: Literal[7] files: list[File] deduplicated_types: list[DeduplicatedType] # def test(stdump_json_path: str): # import json # # with open(stdump_json_path, mode="r") as fh: # json_data = fh.read() # # model = CCCJSONv7Model.model_validate_json(json_data) # # type_map: dict[str, int] = {} # # for n, dt in enumerate(model.deduplicated_types): # if dt.descriptor == "builtin": # assert dt.name and dt.name not in type_map # assert dt.class_ is not None # size_bits = int(dt.class_.split("-", maxsplit=1)[0]) # assert size_bits % 8 == 0 # type_map[dt.name] = size_bits // 8 # # elif dt.descriptor == "type_name": # assert dt.name is not None # assert dt.type_name is not None # if dt.name in type_map: # if dt.size_bits: # assert type_map[dt.name] == dt.size_bits // 8 # if dt.name == dt.type_name == "void": # type_map["void"] = type_map["int"] # continue # assert dt.type_name in type_map, (n, dt.type_name) # type_map[dt.name] = type_map[dt.type_name] # # elif dt.descriptor == "pointer": # assert dt.name is not None # assert dt.value_type is not None # if dt.value_type.descriptor == "function_type": # assert dt.name not in type_map # type_map[dt.name] = type_map["int"] # elif dt.value_type.descriptor == "type_name": # assert dt.value_type.type_name # assert dt.value_type.type_name in type_map # type_map[dt.name] = type_map[dt.value_type.type_name] # # elif dt.descriptor == "struct": # assert dt.name is not None # if not dt.conflict: # assert dt.name not in type_map, n # else: # if dt.name in type_map: # continue # assert dt.size_bits is not None # assert dt.size_bits % 8 == 0 # type_map[dt.name] = dt.size_bits // 8 # # elif dt.descriptor == "array": # assert dt.name is not None # element_count = 1 # element_type = dt.element_type # type_name = None # while element_type: # if element_type.element_count is not None: # element_count *= element_type.element_count # if element_type.type_name is not None: # type_name = element_type.type_name # assert type_name in type_map # element_count *= type_map[type_name] # element_type = element_type.element_type # assert type_name and type_name in type_map, n # type_map[dt.name] = type_map[type_name] * element_count # # elif dt.descriptor == "enum": # if dt.name: # if not dt.conflict: # assert dt.name not in type_map, n # type_map[dt.name] = type_map["int"] # # elif dt.descriptor == "union": # assert dt.name # assert dt.size_bits # assert dt.size_bits % 8 == 0 # type_map[dt.name] = dt.size_bits // 8 # # else: # assert False, f"unknown {n}" # # print(json.dumps(type_map, indent=2)) # if __name__ == "__main__": # test(path-to-stdump-json)