Skip to content

Commit f134091

Browse files
committed
fix: Opus found even more bugs, everything should be fixed now
1 parent b39a993 commit f134091

9 files changed

Lines changed: 573 additions & 63 deletions

File tree

libdestruct/c/c_float_types.py

Lines changed: 23 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,71 +11,54 @@
1111
from libdestruct.common.obj import obj
1212

1313

14-
class c_float(obj):
15-
"""A C float (IEEE 754 single-precision, 32-bit)."""
14+
class _c_float_base(obj):
15+
"""A generic C floating-point type, to be subclassed by c_float and c_double."""
1616

17-
size: int = 4
18-
"""The size of a float in bytes."""
17+
size: int
18+
"""The size of the float in bytes."""
19+
20+
_format: str
21+
"""The struct format character ('f' or 'd')."""
1922

2023
_frozen_value: float | None = None
2124
"""The frozen value of the float."""
2225

23-
def _format_char(self: c_float) -> str:
24-
return "<f" if self.endianness == "little" else ">f"
26+
def _format_char(self: _c_float_base) -> str:
27+
prefix = "<" if self.endianness == "little" else ">"
28+
return prefix + self._format
2529

26-
def get(self: c_float) -> float:
30+
def get(self: _c_float_base) -> float:
2731
"""Return the value of the float."""
2832
return struct.unpack(self._format_char(), self.resolver.resolve(self.size, 0))[0]
2933

30-
def _set(self: c_float, value: float) -> None:
34+
def _set(self: _c_float_base, value: float) -> None:
3135
"""Set the value of the float."""
3236
self.resolver.modify(self.size, 0, struct.pack(self._format_char(), value))
3337

34-
def to_bytes(self: c_float) -> bytes:
38+
def to_bytes(self: _c_float_base) -> bytes:
3539
"""Return the serialized representation of the float."""
3640
if self._frozen:
3741
return struct.pack(self._format_char(), self._frozen_value)
3842
return self.resolver.resolve(self.size, 0)
3943

40-
def __float__(self: c_float) -> float:
44+
def __float__(self: _c_float_base) -> float:
4145
"""Return the value as a Python float."""
4246
return self.get()
4347

44-
def __int__(self: c_float) -> int:
48+
def __int__(self: _c_float_base) -> int:
4549
"""Return the value as a Python int."""
4650
return int(self.get())
4751

4852

49-
class c_double(obj):
50-
"""A C double (IEEE 754 double-precision, 64-bit)."""
51-
52-
size: int = 8
53-
"""The size of a double in bytes."""
54-
55-
_frozen_value: float | None = None
56-
"""The frozen value of the double."""
57-
58-
def _format_char(self: c_double) -> str:
59-
return "<d" if self.endianness == "little" else ">d"
60-
61-
def get(self: c_double) -> float:
62-
"""Return the value of the double."""
63-
return struct.unpack(self._format_char(), self.resolver.resolve(self.size, 0))[0]
53+
class c_float(_c_float_base):
54+
"""A C float (IEEE 754 single-precision, 32-bit)."""
6455

65-
def _set(self: c_double, value: float) -> None:
66-
"""Set the value of the double."""
67-
self.resolver.modify(self.size, 0, struct.pack(self._format_char(), value))
56+
size: int = 4
57+
_format: str = "f"
6858

69-
def to_bytes(self: c_double) -> bytes:
70-
"""Return the serialized representation of the double."""
71-
if self._frozen:
72-
return struct.pack(self._format_char(), self._frozen_value)
73-
return self.resolver.resolve(self.size, 0)
7459

75-
def __float__(self: c_double) -> float:
76-
"""Return the value as a Python float."""
77-
return self.get()
60+
class c_double(_c_float_base):
61+
"""A C double (IEEE 754 double-precision, 64-bit)."""
7862

79-
def __int__(self: c_double) -> int:
80-
"""Return the value as a Python int."""
81-
return int(self.get())
63+
size: int = 8
64+
_format: str = "d"

libdestruct/common/array/array_field_inflater.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ def _subscripted_array_handler(
4343
if len(args) != 2:
4444
return None
4545
element_type, count = args
46-
if not isinstance(count, int):
47-
return None
46+
if not isinstance(count, int) or count <= 0:
47+
raise ValueError(f"array count must be a positive integer, got {count}")
4848
field = LinearArrayField(element_type, count)
4949
field.item = registry.inflater_for(element_type)
5050
return field.inflate

libdestruct/common/obj.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,11 @@ def _compare_value(self: obj, other: object) -> tuple[object, object] | None:
136136
"""Extract comparable values from self and other, or None if incompatible."""
137137
self_val = self.value
138138
if isinstance(other, obj):
139-
return self_val, other.value
139+
other_val = other.value
140+
# Guard against incompatible value types (e.g. int vs str from struct.get())
141+
if type(self_val) is not type(other_val) and not isinstance(self_val, type(other_val)) and not isinstance(other_val, type(self_val)):
142+
return None
143+
return self_val, other_val
140144
if isinstance(other, int | float | bytes):
141145
return self_val, other
142146
return None

libdestruct/common/struct/struct_impl.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -245,20 +245,22 @@ def get(self: struct_impl) -> str:
245245

246246
def to_bytes(self: struct_impl) -> bytes:
247247
"""Return the serialized representation of the struct, including padding."""
248-
if self._frozen:
249-
return self._frozen_struct_bytes
248+
if object.__getattribute__(self, "_frozen"):
249+
return object.__getattribute__(self, "_frozen_struct_bytes")
250250
resolver = object.__getattribute__(self, "resolver")
251251
return resolver.resolve(size_of(self), 0)
252252

253253
def to_dict(self: struct_impl) -> dict[str, object]:
254254
"""Return a JSON-serializable dict of field names to values."""
255-
return {name: member.to_dict() for name, member in self._members.items()}
255+
members = object.__getattribute__(self, "_members")
256+
return {name: member.to_dict() for name, member in members.items()}
256257

257258
def hexdump(self: struct_impl) -> str:
258259
"""Return a hex dump of this struct's bytes with field annotations."""
259260
member_offsets = object.__getattribute__(self, "_member_offsets")
260-
annotations = {member_offsets[name]: name for name in self._members}
261-
address = struct_impl.address.fget(self) if not self._frozen else 0
261+
members = object.__getattribute__(self, "_members")
262+
annotations = {member_offsets[name]: name for name in members}
263+
address = struct_impl.address.fget(self) if not object.__getattribute__(self, "_frozen") else 0
262264
return format_hexdump(self.to_bytes(), address, annotations)
263265

264266
def _set(self: struct_impl, _: str) -> None:
@@ -268,18 +270,20 @@ def _set(self: struct_impl, _: str) -> None:
268270
def freeze(self: struct_impl) -> None:
269271
"""Freeze the struct, capturing the full byte representation including padding."""
270272
resolver = object.__getattribute__(self, "resolver")
271-
self._frozen_struct_bytes = resolver.resolve(size_of(self), 0)
273+
object.__setattr__(self, "_frozen_struct_bytes", resolver.resolve(size_of(self), 0))
272274

273-
for member in self._members.values():
275+
members = object.__getattribute__(self, "_members")
276+
for member in members.values():
274277
member.freeze()
275278

276279
super().freeze()
277280

278281
def to_str(self: struct_impl, indent: int = 0) -> str:
279282
"""Return a string representation of the struct."""
280283
name = object.__getattribute__(self, "_struct_name")
284+
members_dict = object.__getattribute__(self, "_members")
281285
members = ",\n".join(
282-
[f"{' ' * (indent + 4)}{n}: {member.to_str(indent + 4)}" for n, member in self._members.items()],
286+
[f"{' ' * (indent + 4)}{n}: {member.to_str(indent + 4)}" for n, member in members_dict.items()],
283287
)
284288
return f"""{name} {{
285289
{members}
@@ -289,7 +293,8 @@ def __repr__(self: struct_impl) -> str:
289293
"""Return a string representation of the struct."""
290294
name = object.__getattribute__(self, "_struct_name")
291295
addr = struct_impl.address.fget(self)
292-
members = ",\n".join([f"{n}: {member}" for n, member in self._members.items()])
296+
members_dict = object.__getattribute__(self, "_members")
297+
members = ",\n".join([f"{n}: {member}" for n, member in members_dict.items()])
293298
return f"""{name} {{
294299
address: 0x{addr:x},
295300
size: 0x{size_of(self):x},
@@ -306,7 +311,10 @@ def __eq__(self: struct_impl, value: object) -> bool:
306311
if size_of(self) != size_of(value):
307312
return False
308313

309-
if not self._members.keys() == value._members.keys():
314+
self_members = object.__getattribute__(self, "_members")
315+
other_members = object.__getattribute__(value, "_members")
316+
317+
if self_members.keys() != other_members.keys():
310318
return False
311319

312-
return all(getattr(self, name) == getattr(value, name) for name in self._members)
320+
return all(getattr(self, name) == getattr(value, name) for name in self_members)

libdestruct/common/type_registry.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ def register_type_handler(
146146
if parent not in self.type_handlers:
147147
self.type_handlers[parent] = []
148148

149-
self.type_handlers[parent].append(handler)
149+
if handler not in self.type_handlers[parent]:
150+
self.type_handlers[parent].append(handler)
150151

151152
def register_instance_handler(
152153
self: TypeRegistry,
@@ -165,7 +166,8 @@ def register_instance_handler(
165166
if parent not in self.instance_handlers:
166167
self.instance_handlers[parent] = []
167168

168-
self.instance_handlers[parent].append(handler)
169+
if handler not in self.instance_handlers[parent]:
170+
self.instance_handlers[parent].append(handler)
169171

170172
def register_generic_handler(
171173
self: TypeRegistry,
@@ -181,7 +183,8 @@ def register_generic_handler(
181183
if origin not in self.generic_handlers:
182184
self.generic_handlers[origin] = []
183185

184-
self.generic_handlers[origin].append(handler)
186+
if handler not in self.generic_handlers[origin]:
187+
self.generic_handlers[origin].append(handler)
185188

186189
def register_mapping(
187190
self: TypeRegistry,

libdestruct/common/union/union.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,18 @@ def to_str(self: union, indent: int = 0) -> str:
119119

120120
def __getattr__(self: union, name: str) -> object:
121121
"""Delegate attribute access to named variants or the active variant."""
122-
variants = object.__getattribute__(self, "_variants")
123-
if name in variants:
124-
return variants[name]
125-
variant = object.__getattribute__(self, "_variant")
126-
if variant is not None:
127-
return getattr(variant, name)
122+
try:
123+
variants = object.__getattribute__(self, "_variants")
124+
if name in variants:
125+
return variants[name]
126+
except AttributeError:
127+
pass
128+
129+
try:
130+
variant = object.__getattribute__(self, "_variant")
131+
if variant is not None:
132+
return getattr(variant, name)
133+
except AttributeError:
134+
pass
135+
128136
raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")

libdestruct/libdestruct.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@
1616
from libdestruct.common.obj import obj
1717

1818

19+
_VALID_ENDIANNESS = ("little", "big")
20+
21+
1922
def inflater(memory: Sequence, endianness: str = "little") -> Inflater:
2023
"""Return a TypeInflater instance."""
2124
if not isinstance(memory, Sequence):
2225
raise TypeError(f"memory must be a Sequence, not {type(memory).__name__}")
2326

27+
if endianness not in _VALID_ENDIANNESS:
28+
raise ValueError(f"endianness must be 'little' or 'big', not {endianness!r}")
29+
2430
return Inflater(memory, endianness=endianness)
2531

2632

0 commit comments

Comments
 (0)