Skip to content

Commit c9a7699

Browse files
committed
feat: extend the struct parser a bit more
1 parent 9872ba2 commit c9a7699

13 files changed

Lines changed: 87 additions & 15 deletions

libdestruct/c/ctypes_generic_field.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from libdestruct.c.ctypes_generic import _ctypes_generic
1212
from libdestruct.common.type_registry import TypeRegistry
1313

14+
registry = TypeRegistry()
15+
1416

1517
def ctypes_type_handler(obj_type: type) -> type[_ctypes_generic]:
1618
"""Return the ctypes type handler for the given object type.
@@ -21,11 +23,15 @@ def ctypes_type_handler(obj_type: type) -> type[_ctypes_generic]:
2123
if not issubclass(obj_type, _SimpleCData):
2224
raise TypeError(f"Unsupported object type: {obj_type}.")
2325

24-
return type(
26+
typ = type(
2527
f"ctypes_{obj_type.__name__}",
2628
(_ctypes_generic,),
2729
{"backing_type": obj_type, "size": sizeof(obj_type)},
2830
)
2931

32+
registry.register_mapping(typ, typ)
33+
34+
return typ
35+
3036

31-
TypeRegistry().register_type_handler(_SimpleCData, ctypes_type_handler)
37+
registry.register_type_handler(_SimpleCData, ctypes_type_handler)

libdestruct/c/struct_parser.py

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
from pycparser import c_ast, c_parser
1616

17+
from libdestruct.common.array.array_of import array_of
18+
from libdestruct.common.ptr.ptr_factory import ptr_to, ptr_to_self
1719
from libdestruct.common.struct import struct
1820

1921
if TYPE_CHECKING:
@@ -60,19 +62,57 @@ def struct_to_type(struct_node: c_ast.Struct) -> type[struct]:
6062

6163
for decl in struct_node.decls:
6264
name = decl.name
63-
typ = type_decl_to_type(decl.type)
65+
typ = type_decl_to_type(decl.type, struct_node)
6466
fields[name] = typ
6567

6668
type_name = struct_node.name if struct_node.name else "anon_struct"
6769

6870
return type(type_name, (struct,), {"__annotations__": fields})
6971

7072

71-
def type_decl_to_type(decl: c_ast.TypeDecl) -> type[obj]:
73+
def ptr_to_type(ptr: c_ast.PtrDecl, parent: c_ast.Struct | None = None) -> type[obj]:
74+
"""Converts a C pointer to a type."""
75+
if not isinstance(ptr, c_ast.PtrDecl):
76+
raise TypeError("Definition must be a pointer.")
77+
78+
if not isinstance(ptr.type, c_ast.TypeDecl):
79+
raise TypeError("Definition must be a type declaration.")
80+
81+
# Special case: this is a pointer to self
82+
# Note that ptr can either be a struct or an identifier.
83+
ptr_name = ptr.type.type.name if isinstance(ptr.type.type, c_ast.Struct) else ptr.type.type.names[0]
84+
if parent and ptr_name == parent.name:
85+
return ptr_to_self()
86+
87+
typ = type_decl_to_type(ptr.type)
88+
89+
return ptr_to(typ)
90+
91+
92+
def arr_to_type(arr: c_ast.ArrayDecl) -> type[obj]:
93+
"""Converts a C array to a type."""
94+
if not isinstance(arr, c_ast.ArrayDecl):
95+
raise TypeError("Definition must be an array.")
96+
97+
if not isinstance(arr.type, c_ast.TypeDecl) and not isinstance(arr.type, c_ast.PtrDecl):
98+
raise TypeError("Definition must be a type declaration.")
99+
100+
typ = ptr_to_type(arr.type) if isinstance(arr.type, c_ast.PtrDecl) else type_decl_to_type(arr.type)
101+
102+
return array_of(typ, int(arr.dim.value))
103+
104+
105+
def type_decl_to_type(decl: c_ast.TypeDecl, parent: c_ast.Struct | None = None) -> type[obj]:
72106
"""Converts a C type declaration to a type."""
73-
if not isinstance(decl, c_ast.TypeDecl):
107+
if not isinstance(decl, c_ast.TypeDecl) and not isinstance(decl, c_ast.PtrDecl) and not isinstance(decl, c_ast.ArrayDecl):
74108
raise TypeError("Definition must be a type declaration.")
75109

110+
if isinstance(decl, c_ast.PtrDecl):
111+
return ptr_to_type(decl, parent)
112+
113+
if isinstance(decl, c_ast.ArrayDecl):
114+
return arr_to_type(decl)
115+
76116
if isinstance(decl.type, c_ast.Struct):
77117
return struct_to_type(decl.type)
78118

@@ -95,6 +135,9 @@ def to_uniform_name(name: str) -> str:
95135
name = name.replace("int32_t", "int")
96136
name = name.replace("int64_t", "longlong")
97137

138+
# We have to convert uintptr_t
139+
name = name.replace("uintptr_t", "ulonglong")
140+
98141
# Only size_t, ssize_t and time_t can end with _t
99142
if not any(x in name for x in ["size", "ssize", "time"]):
100143
name = name.replace("_t", "")

libdestruct/common/array/array_field.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@
99
from abc import abstractmethod
1010
from typing import TYPE_CHECKING
1111

12+
from libdestruct.common.array import array
1213
from libdestruct.common.field import Field
1314

1415
if TYPE_CHECKING: # pragma: no cover
1516
from libdestruct.backing.resolver import Resolver
16-
from libdestruct.common.array import array
17+
from libdestruct.common.obj import obj
1718

1819

1920
class ArrayField(Field):
2021
"""A generator for an array of items."""
2122

23+
base_type: type[obj] = array
24+
2225
@abstractmethod
2326
def inflate(self: ArrayField, resolver: Resolver) -> array:
2427
"""Inflate the field.

libdestruct/common/array/array_impl.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@
88

99
from typing import TYPE_CHECKING
1010

11+
from libdestruct.c.ctypes_generic import _ctypes_generic
1112
from libdestruct.common.array.array import array
13+
from libdestruct.common.field import Field
1214
from libdestruct.common.obj import obj
1315
from libdestruct.common.struct.struct import struct
1416

1517
if TYPE_CHECKING: # pragma: no cover
1618
from collections.abc import Generator
1719

1820
from libdestruct.backing.resolver import Resolver
19-
from libdestruct.common.obj import obj
2021

2122

2223
class array_impl(array):
@@ -36,15 +37,23 @@ def __init__(
3637

3738
self.backing_type = backing_type
3839
self._count = count
39-
self.size = self.backing_type.size * self._count
40+
41+
if hasattr(backing_type, "size"):
42+
self.size = self.backing_type.size * self._count
43+
self.item_size = self.backing_type.size
44+
elif callable(backing_type):
45+
self.size = 8 * self._count
46+
self.item_size = 8
47+
else:
48+
raise NotImplementedError("Unsupported backing type.")
4049

4150
def count(self: array_impl) -> int:
4251
"""Get the size of the array."""
4352
return self._count
4453

4554
def get(self: array, index: int) -> object:
4655
"""Return the element at the given index."""
47-
return self.backing_type(self.resolver.relative_from_own(index * self.backing_type.size, 0))
56+
return self.backing_type(self.resolver.relative_from_own(index * self.item_size, 0))
4857

4958
def _set(self: array_impl, _: list[obj]) -> None:
5059
"""Set the array from a list."""
@@ -64,7 +73,7 @@ def to_str(self: array_impl, indent: int = 0) -> str:
6473
return "[]"
6574

6675
# If the backing type is a struct, we need to indent the output differently
67-
if issubclass(self.backing_type, struct):
76+
if isinstance(self.backing_type, type) and issubclass(self.backing_type, struct):
6877
padding = ",\n" + " " * (indent + 4)
6978
spacing = " " * (indent + 4)
7079
return "[\n" + spacing + padding.join(x.to_str(indent + 4) for x in self) + "\n" + " " * (indent) + "]"

libdestruct/common/enum/enum_field.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@
99
from abc import abstractmethod
1010
from typing import TYPE_CHECKING
1111

12+
from libdestruct.common.enum import enum
1213
from libdestruct.common.field import Field
1314

1415
if TYPE_CHECKING: # pragma: no cover
1516
from libdestruct.backing.resolver import Resolver
16-
from libdestruct.common.enum import enum
17+
from libdestruct.common.obj import obj
1718

1819

1920
class EnumField(Field):
2021
"""A generator for an enum."""
2122

23+
base_type: type[obj] = enum
24+
2225
@abstractmethod
2326
def inflate(self: EnumField, resolver: Resolver) -> enum:
2427
"""Inflate the field.

libdestruct/common/field.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
class Field(ABC):
1818
"""A generator for a generic field."""
1919

20+
base_type: type[obj]
21+
2022
@abstractmethod
2123
def inflate(self: Field, resolver: Resolver) -> obj:
2224
"""Inflate the field.

libdestruct/common/ptr/__init__.py

Whitespace-only changes.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from typing import TYPE_CHECKING
1010

11-
from libdestruct.common.struct.ptr_struct_field import PtrStructField
11+
from libdestruct.common.ptr.ptr_struct_field import PtrStructField
1212

1313
if TYPE_CHECKING: # pragma: no cover
1414
from libdestruct.common.field import Field

libdestruct/common/struct/ptr_struct_field.py renamed to libdestruct/common/ptr/ptr_struct_field.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from typing import TYPE_CHECKING
1010

1111
from libdestruct.common.field import Field
12-
from libdestruct.common.ptr import ptr
12+
from libdestruct.common.ptr.ptr import ptr
1313
from libdestruct.common.struct.struct_field import StructField
1414

1515
if TYPE_CHECKING: # pragma: no cover
@@ -20,6 +20,8 @@
2020
class PtrStructField(StructField):
2121
"""A generator for a field of a struct."""
2222

23+
base_type: type[obj] = ptr
24+
2325
def __init__(self: PtrStructField, backing_type: type | Field) -> None:
2426
"""Initialize a pointer field.
2527

0 commit comments

Comments
 (0)