Files
Mulberry/tools/python/ccc_json_v7.py
T
2026-07-12 15:06:31 -05:00

352 lines
11 KiB
Python

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)