2626 Void ,
2727 Wchar ,
2828)
29- from dissect .cstruct .types .base import MetaType
3029
3130if TYPE_CHECKING :
3231 from collections .abc import Iterable
@@ -55,10 +54,12 @@ class cstruct:
5554 def __init__ (self , load : str = "" , * , endian : str = "<" , pointer : str | None = None ):
5655 self .endian = endian
5756
58- self .consts = {}
59- self .includes = []
57+ self .consts : dict [str , int | str | bytes ] = {}
58+ self .types : dict [str , type [BaseType ]] = {}
59+ self .includes : list [str ] = []
60+
6061 # fmt: off
61- self . typedefs = {
62+ initial_types = {
6263 # Internal types
6364 "int8" : self ._make_packed_type ("int8" , "b" , int ),
6465 "uint8" : self ._make_packed_type ("uint8" , "B" , int ),
@@ -102,6 +103,22 @@ def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = N
102103 "signed long long" : "int64" ,
103104 "unsigned long long" : "uint64" ,
104105
106+ # Other convenience types
107+ "u8" : "uint8" ,
108+ "u16" : "uint16" ,
109+ "u32" : "uint32" ,
110+ "u64" : "uint64" ,
111+ "u128" : "uint128" ,
112+ "__u8" : "uint8" ,
113+ "__u16" : "uint16" ,
114+ "__u32" : "uint32" ,
115+ "__u64" : "uint64" ,
116+ "__u128" : "uint128" ,
117+ "uchar" : "uint8" ,
118+ "ushort" : "uint16" ,
119+ "uint" : "uint32" ,
120+ "ulong" : "uint32" ,
121+
105122 # Windows types
106123 "BYTE" : "uint8" ,
107124 "CHAR" : "char" ,
@@ -169,25 +186,12 @@ def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = N
169186 "_DWORD" : "uint32" ,
170187 "_QWORD" : "uint64" ,
171188 "_OWORD" : "uint128" ,
172-
173- # Other convenience types
174- "u8" : "uint8" ,
175- "u16" : "uint16" ,
176- "u32" : "uint32" ,
177- "u64" : "uint64" ,
178- "u128" : "uint128" ,
179- "__u8" : "uint8" ,
180- "__u16" : "uint16" ,
181- "__u32" : "uint32" ,
182- "__u64" : "uint64" ,
183- "__u128" : "uint128" ,
184- "uchar" : "uint8" ,
185- "ushort" : "uint16" ,
186- "uint" : "uint32" ,
187- "ulong" : "uint32" ,
188189 }
189190 # fmt: on
190191
192+ for name , type_ in initial_types .items ():
193+ self .add_type (name , type_ )
194+
191195 pointer = pointer or ("uint64" if sys .maxsize > 2 ** 32 else "uint32" )
192196 self .pointer : type [BaseType ] = self .resolve (pointer )
193197 self ._anonymous_count = 0
@@ -202,27 +206,25 @@ def __getattr__(self, attr: str) -> Any:
202206 pass
203207
204208 try :
205- return self .resolve ( self . typedefs [attr ])
209+ return self .types [attr ]
206210 except KeyError :
207211 pass
208212
209- raise AttributeError (f"Invalid attribute: { attr } " )
213+ raise AttributeError (f"' { type ( self ). __name__ } ' object has no attribute { attr !r } " )
210214
211215 def __copy__ (self ) -> cstruct :
212216 cs = cstruct (endian = self .endian , pointer = self .pointer .__name__ )
213217 cs ._anonymous_count = self ._anonymous_count
214- cs .consts = self .consts .copy ()
215218 cs .includes = self .includes .copy ()
216219
217- # Update typedefs to point to the new cstruct instance
218- for name , type in self .typedefs .items ():
219- if name in cs .typedefs :
220- continue
220+ # Update types to point to the new cstruct instance
221+ for name , type_ in self .types .items ():
222+ new_type = copy .copy (type_ )
223+ new_type .cs = cs
224+ cs .add_type (name , new_type , replace = True )
221225
222- if isinstance (type , MetaType ):
223- new_type = copy .copy (type )
224- new_type .cs = cs
225- cs .typedefs [name ] = new_type
226+ for name , value in self .consts .items ():
227+ cs .add_const (name , value )
226228
227229 return cs
228230
@@ -231,22 +233,34 @@ def _next_anonymous(self) -> str:
231233 self ._anonymous_count += 1
232234 return name
233235
236+ def _add_attr (self , name : str , value : Any ) -> None :
237+ # Names that collide with the cstruct class (e.g. a struct named ``load``) are not set as attributes
238+ # to avoid breaking the instance. They remain accessible through ``resolve`` and the type dicts.
239+ if name in _RESERVED_NAMES :
240+ return
241+ setattr (self , name , value )
242+
234243 def add_type (self , name : str , type_ : type [BaseType ] | str , replace : bool = False ) -> None :
235- """Add a type or type reference .
244+ """Add a type or type alias .
236245
237246 Only use this method when creating type aliases or adding already bound types.
247+ All types will be resolved to their actual type objects prior to being added.
238248
239249 Args:
240250 name: Name of the type to be added.
241251 type_: The type to be added. Can be a str reference to another type or a compatible type class.
252+ If a str is given, it will be resolved to the actual type object.
253+ replace: Whether to replace the type if it already exists.
242254
243255 Raises:
244256 ValueError: If the type already exists.
245257 """
246- if not replace and (name in self .typedefs and self .resolve (self .typedefs [name ]) != self .resolve (type_ )):
258+ typeobj = self .resolve (type_ )
259+ if not replace and (existing := self .types .get (name )) is not None and existing is not typeobj :
247260 raise ValueError (f"Duplicate type: { name } " )
248261
249- self .typedefs [name ] = type_
262+ self .types [name ] = typeobj
263+ self ._add_attr (name , typeobj )
250264
251265 addtype = add_type
252266
@@ -267,6 +281,28 @@ def add_custom_type(
267281 """
268282 self .add_type (name , self ._make_type (name , (type_ ,), size , alignment = alignment , attrs = kwargs ))
269283
284+ def add_const (self , name : str , value : Any ) -> None :
285+ """Add a constant value.
286+
287+ Args:
288+ name: Name of the constant to be added.
289+ value: The value of the constant.
290+ """
291+ self .consts [name ] = value
292+ self ._add_attr (name , value )
293+
294+ def del_const (self , name : str ) -> None :
295+ """Delete a constant value.
296+
297+ Args:
298+ name: Name of the constant to be deleted.
299+
300+ Raises:
301+ KeyError: If the constant does not exist.
302+ """
303+ del self .consts [name ]
304+ self .__dict__ .pop (name , None )
305+
270306 def load (self , definition : str , deftype : int | None = None , ** kwargs ) -> cstruct :
271307 """Parse structures from the given definitions using the given definition type.
272308
@@ -324,8 +360,8 @@ def cdef(self) -> str:
324360 if defines :
325361 blocks .append ("\n " .join (defines ))
326362
327- for name , typedef in self .typedefs .items ():
328- if name in empty .typedefs or not isinstance (typedef , type ):
363+ for name , typedef in self .types .items ():
364+ if name in empty .types or not isinstance (typedef , type ):
329365 continue
330366
331367 if not issubclass (typedef , (Structure , Enum , Flag )):
@@ -365,20 +401,13 @@ def resolve(self, name: type[BaseType] | str) -> type[BaseType]:
365401 Raises:
366402 ResolveError: If the type can't be resolved.
367403 """
368- type_name = name
369- if not isinstance (type_name , str ):
370- return type_name
404+ if not isinstance (name , str ):
405+ return name
371406
372- for _ in range (10 ):
373- if type_name not in self .typedefs :
374- raise ResolveError (f"Unknown type { name } " )
375-
376- type_name = self .typedefs [type_name ]
377-
378- if not isinstance (type_name , str ):
379- return type_name
380-
381- raise ResolveError (f"Recursion limit exceeded while resolving type { name } " )
407+ try :
408+ return self .types [name ]
409+ except KeyError :
410+ raise ResolveError (f"Unknown type { name } " ) from None
382411
383412 def copy (self ) -> cstruct :
384413 """Create a copy of this cstruct instance.
@@ -555,6 +584,21 @@ class void(Void): ...
555584 # signed long long: TypeAlias = int64
556585 # unsigned long long: TypeAlias = uint64
557586
587+ u8 : TypeAlias = uint8
588+ u16 : TypeAlias = uint16
589+ u32 : TypeAlias = uint32
590+ u64 : TypeAlias = uint64
591+ u128 : TypeAlias = uint128
592+ __u8 : TypeAlias = uint8
593+ __u16 : TypeAlias = uint16
594+ __u32 : TypeAlias = uint32
595+ __u64 : TypeAlias = uint64
596+ __u128 : TypeAlias = uint128
597+ uchar : TypeAlias = uint8
598+ ushort : TypeAlias = uint16
599+ uint : TypeAlias = uint32
600+ ulong : TypeAlias = uint32
601+
558602 BYTE : TypeAlias = uint8
559603 CHAR : TypeAlias = char
560604 SHORT : TypeAlias = int16
@@ -620,20 +664,17 @@ class void(Void): ...
620664 _QWORD : TypeAlias = uint64
621665 _OWORD : TypeAlias = uint128
622666
623- u8 : TypeAlias = uint8
624- u16 : TypeAlias = uint16
625- u32 : TypeAlias = uint32
626- u64 : TypeAlias = uint64
627- u128 : TypeAlias = uint128
628- __u8 : TypeAlias = uint8
629- __u16 : TypeAlias = uint16
630- __u32 : TypeAlias = uint32
631- __u64 : TypeAlias = uint64
632- __u128 : TypeAlias = uint128
633- uchar : TypeAlias = uint8
634- ushort : TypeAlias = uint16
635- uint : TypeAlias = uint32
636- ulong : TypeAlias = uint32
667+
668+ # Attribute names that types and constants may never shadow: the public API and methods of the cstruct
669+ # class itself, plus the instance attributes created in __init__.
670+ _RESERVED_NAMES = frozenset (dir (cstruct )) | {
671+ "endian" ,
672+ "consts" ,
673+ "types" ,
674+ "includes" ,
675+ "pointer" ,
676+ "_anonymous_count" ,
677+ }
637678
638679
639680def ctypes (structure : type [Structure ]) -> type [_ctypes .Structure ]:
0 commit comments