dissect.cstruct 4.5.dev4__tar.gz → 4.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dissect_cstruct-4.6/CHANGELOG.md +54 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/PKG-INFO +2 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/compiler.py +8 -8
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/cstruct.py +8 -4
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/expression.py +6 -7
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/parser.py +101 -14
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/tools/stubgen.py +3 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/__init__.py +2 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/base.py +5 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/structure.py +104 -24
- dissect_cstruct-4.6/dissect/cstruct/types/void.py +44 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/utils.py +4 -5
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect.cstruct.egg-info/PKG-INFO +2 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect.cstruct.egg-info/SOURCES.txt +1 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/pyproject.toml +3 -3
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_align.py +1 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_annotations.py +1 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_basic.py +46 -2
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_compiler.py +3 -23
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_expression.py +3 -3
- dissect_cstruct-4.6/tests/test_parser.py +296 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_tools_stubgen.py +20 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_base.py +9 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_structure.py +69 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_void.py +9 -7
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tox.ini +4 -2
- dissect_cstruct-4.5.dev4/dissect/cstruct/types/void.py +0 -27
- dissect_cstruct-4.5.dev4/tests/test_parser.py +0 -131
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/.git-blame-ignore-revs +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/.gitattributes +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/COPYRIGHT +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/LICENSE +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/MANIFEST.in +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/README.md +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/__init__.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/bitbuffer.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/exceptions.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/tools/__init__.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/char.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/enum.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/flag.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/int.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/leb128.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/packed.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/pointer.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/wchar.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect.cstruct.egg-info/entry_points.txt +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect.cstruct.egg-info/requires.txt +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect.cstruct.egg-info/top_level.txt +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/examples/disk.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/examples/mirai.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/examples/pe.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/examples/protobuf.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/examples/secdesc.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/setup.cfg +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/__init__.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/_data/testdef.txt +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/_docs/Makefile +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/_docs/__init__.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/_docs/conf.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/_docs/index.rst +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/conftest.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_bitbuffer.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_bitfield.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_ctypes.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_char.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_custom.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_enum.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_flag.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_int.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_leb128.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_packed.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_pointer.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_union.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_wchar.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_utils.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/utils.py +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Optimize storage of field sizes.
|
|
12
|
+
- Rename `_sizes` property of `Structure` to `__sizes__`.
|
|
13
|
+
- Rename `_values` property of `Structure` to `__values__`.
|
|
14
|
+
- Added `load` argument to `cstruct` class, allowing direct initialization with a definition (i.e. `cstruct(cdef)` instead of `cstruct().load(cdef)`. Other arguments to `cstruct` are now keyword only.
|
|
15
|
+
|
|
16
|
+
## [4.5] - 20-05-2025
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Introduce experimental tool `cstruct-stubgen` to generate type stubs for cstruct definitions.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Generated classes are now hashable.
|
|
25
|
+
- Suppress spurious `TypeError: Dynamic size` errors when using cstruct interactively.
|
|
26
|
+
|
|
27
|
+
## [4.4] - 03-10-2025
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Resolve documentation warnings.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Use the Ruff linter.
|
|
36
|
+
|
|
37
|
+
## [4.3] - 11-18-2024
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- All cstruct types are now correctly default-initialized using the `__default__` member.
|
|
42
|
+
|
|
43
|
+
## [4.2] - 10-10-2024
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- The string representation of enums now outputs the name of the constants.
|
|
48
|
+
|
|
49
|
+
## [4.1] - 10-09-2024
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- Declaring an array of a nested struct type now works as intended.
|
|
54
|
+
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.6
|
|
4
4
|
Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License: Apache License 2.0
|
|
7
7
|
Project-URL: homepage, https://dissect.tools
|
|
8
8
|
Project-URL: documentation, https://docs.dissect.tools/en/latest/projects/dissect.cstruct
|
|
9
9
|
Project-URL: repository, https://github.com/fox-it/dissect.cstruct
|
|
10
|
+
Project-URL: changelog, https://github.com/fox-it/dissect.cstruct/blob/main/CHANGELOG.md
|
|
10
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
12
|
Classifier: Environment :: Console
|
|
12
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -21,6 +21,7 @@ from dissect.cstruct.types import (
|
|
|
21
21
|
Structure,
|
|
22
22
|
Union,
|
|
23
23
|
Void,
|
|
24
|
+
VoidArray,
|
|
24
25
|
Wchar,
|
|
25
26
|
WcharArray,
|
|
26
27
|
)
|
|
@@ -48,6 +49,7 @@ SUPPORTED_TYPES = (
|
|
|
48
49
|
Void,
|
|
49
50
|
Wchar,
|
|
50
51
|
WcharArray,
|
|
52
|
+
VoidArray,
|
|
51
53
|
)
|
|
52
54
|
|
|
53
55
|
log = logging.getLogger(__name__)
|
|
@@ -121,8 +123,7 @@ class _ReadSourceGenerator:
|
|
|
121
123
|
|
|
122
124
|
outro = """
|
|
123
125
|
obj = type.__call__(cls, **r)
|
|
124
|
-
obj.
|
|
125
|
-
obj._values = r
|
|
126
|
+
obj.__dynamic_sizes__ = s
|
|
126
127
|
|
|
127
128
|
return obj
|
|
128
129
|
"""
|
|
@@ -225,18 +226,18 @@ class _ReadSourceGenerator:
|
|
|
225
226
|
|
|
226
227
|
def _generate_structure(self, field: Field) -> Iterator[str]:
|
|
227
228
|
template = f"""
|
|
228
|
-
_s = stream.tell()
|
|
229
|
+
{"_s = stream.tell()" if field.type.dynamic else ""}
|
|
229
230
|
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
|
|
230
|
-
s["{field._name}"] = stream.tell() - _s
|
|
231
|
+
{f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ""}
|
|
231
232
|
"""
|
|
232
233
|
|
|
233
234
|
yield dedent(template)
|
|
234
235
|
|
|
235
236
|
def _generate_array(self, field: Field) -> Iterator[str]:
|
|
236
237
|
template = f"""
|
|
237
|
-
_s = stream.tell()
|
|
238
|
+
{"_s = stream.tell()" if field.type.dynamic else ""}
|
|
238
239
|
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
|
|
239
|
-
s["{field._name}"] = stream.tell() - _s
|
|
240
|
+
{f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ""}
|
|
240
241
|
"""
|
|
241
242
|
|
|
242
243
|
yield dedent(template)
|
|
@@ -324,7 +325,6 @@ class _ReadSourceGenerator:
|
|
|
324
325
|
parser = parser_template.format(type=self._map_field(field), getter=getter)
|
|
325
326
|
|
|
326
327
|
reads.append(f'r["{field._name}"] = {parser}')
|
|
327
|
-
reads.append(f's["{field._name}"] = {field_type.size}')
|
|
328
328
|
reads.append("") # Generates a newline in the resulting code
|
|
329
329
|
|
|
330
330
|
size += field_type.size
|
|
@@ -365,7 +365,7 @@ def _generate_struct_info(cs: cstruct, fields: list[Field], align: bool = False)
|
|
|
365
365
|
read_type = _get_read_type(cs, field.type)
|
|
366
366
|
|
|
367
367
|
# Drop voids
|
|
368
|
-
if issubclass(read_type, Void):
|
|
368
|
+
if issubclass(read_type, (Void, VoidArray)):
|
|
369
369
|
continue
|
|
370
370
|
|
|
371
371
|
# Array of more complex types are handled elsewhere
|
|
@@ -48,11 +48,12 @@ class cstruct:
|
|
|
48
48
|
DEF_CSTYLE = 1
|
|
49
49
|
DEF_LEGACY = 2
|
|
50
50
|
|
|
51
|
-
def __init__(self, endian: str = "<", pointer: str | None = None):
|
|
51
|
+
def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = None):
|
|
52
52
|
self.endian = endian
|
|
53
53
|
|
|
54
54
|
self.consts = {}
|
|
55
55
|
self.lookups = {}
|
|
56
|
+
self.includes = []
|
|
56
57
|
# fmt: off
|
|
57
58
|
self.typedefs = {
|
|
58
59
|
# Internal types
|
|
@@ -187,6 +188,9 @@ class cstruct:
|
|
|
187
188
|
self.pointer: type[BaseType] = self.resolve(pointer)
|
|
188
189
|
self._anonymous_count = 0
|
|
189
190
|
|
|
191
|
+
if load:
|
|
192
|
+
self.load(load)
|
|
193
|
+
|
|
190
194
|
def __getattr__(self, attr: str) -> Any:
|
|
191
195
|
try:
|
|
192
196
|
return self.consts[attr]
|
|
@@ -368,14 +372,14 @@ class cstruct:
|
|
|
368
372
|
"null_terminated": null_terminated,
|
|
369
373
|
}
|
|
370
374
|
|
|
371
|
-
return cast(type[Array], self._make_type(name, bases, size, alignment=type_.alignment, attrs=attrs))
|
|
375
|
+
return cast("type[Array]", self._make_type(name, bases, size, alignment=type_.alignment, attrs=attrs))
|
|
372
376
|
|
|
373
377
|
def _make_int_type(self, name: str, size: int, signed: bool, *, alignment: int | None = None) -> type[Int]:
|
|
374
|
-
return cast(type[Int], self._make_type(name, (Int,), size, alignment=alignment, attrs={"signed": signed}))
|
|
378
|
+
return cast("type[Int]", self._make_type(name, (Int,), size, alignment=alignment, attrs={"signed": signed}))
|
|
375
379
|
|
|
376
380
|
def _make_packed_type(self, name: str, packchar: str, base: type, *, alignment: int | None = None) -> type[Packed]:
|
|
377
381
|
return cast(
|
|
378
|
-
type[Packed],
|
|
382
|
+
"type[Packed]",
|
|
379
383
|
self._make_type(
|
|
380
384
|
name,
|
|
381
385
|
(base, Packed),
|
|
@@ -141,7 +141,7 @@ class ExpressionTokenizer:
|
|
|
141
141
|
self.tokens.append(">>")
|
|
142
142
|
elif self.match(expected="<", append=False) and self.match(expected="<", append=False):
|
|
143
143
|
self.tokens.append("<<")
|
|
144
|
-
elif self.match(expected={" ", "\t"}, append=False):
|
|
144
|
+
elif self.match(expected={" ", "\n", "\t"}, append=False):
|
|
145
145
|
continue
|
|
146
146
|
else:
|
|
147
147
|
raise ExpressionTokenizerError(
|
|
@@ -187,8 +187,7 @@ class Expression:
|
|
|
187
187
|
"sizeof": 6,
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
def __init__(self,
|
|
191
|
-
self.cstruct = cstruct
|
|
190
|
+
def __init__(self, expression: str):
|
|
192
191
|
self.expression = expression
|
|
193
192
|
self.tokens = ExpressionTokenizer(expression).tokenize()
|
|
194
193
|
self.stack = []
|
|
@@ -222,7 +221,7 @@ class Expression:
|
|
|
222
221
|
def is_number(self, token: str) -> bool:
|
|
223
222
|
return token.isnumeric() or (len(token) > 2 and token[0] == "0" and token[1] in ("x", "X", "b", "B", "o", "O"))
|
|
224
223
|
|
|
225
|
-
def evaluate(self, context: dict[str, int] | None = None) -> int:
|
|
224
|
+
def evaluate(self, cs: cstruct, context: dict[str, int] | None = None) -> int:
|
|
226
225
|
"""Evaluates an expression using a Shunting-Yard implementation."""
|
|
227
226
|
|
|
228
227
|
self.stack = []
|
|
@@ -249,14 +248,14 @@ class Expression:
|
|
|
249
248
|
self.queue.append(int(current_token, 0))
|
|
250
249
|
elif current_token in context:
|
|
251
250
|
self.queue.append(int(context[current_token]))
|
|
252
|
-
elif current_token in
|
|
253
|
-
self.queue.append(int(
|
|
251
|
+
elif current_token in cs.consts:
|
|
252
|
+
self.queue.append(int(cs.consts[current_token]))
|
|
254
253
|
elif current_token in self.unary_operators:
|
|
255
254
|
self.stack.append(current_token)
|
|
256
255
|
elif current_token == "sizeof":
|
|
257
256
|
if len(tmp_expression) < i + 3 or (tmp_expression[i + 1] != "(" or tmp_expression[i + 3] != ")"):
|
|
258
257
|
raise ExpressionParserError("Invalid sizeof operation")
|
|
259
|
-
self.queue.append(len(
|
|
258
|
+
self.queue.append(len(cs.resolve(tmp_expression[i + 2])))
|
|
260
259
|
i += 3
|
|
261
260
|
elif current_token in operators:
|
|
262
261
|
while (
|
|
@@ -49,12 +49,19 @@ class TokenParser(Parser):
|
|
|
49
49
|
self.compiled = compiled
|
|
50
50
|
self.align = align
|
|
51
51
|
self.TOK = self._tokencollection()
|
|
52
|
+
self._conditionals = []
|
|
53
|
+
self._conditionals_depth = 0
|
|
52
54
|
|
|
53
55
|
@staticmethod
|
|
54
56
|
def _tokencollection() -> TokenCollection:
|
|
55
57
|
TOK = TokenCollection()
|
|
56
58
|
TOK.add(r"#\[(?P<values>[^\]]+)\](?=\s*)", "CONFIG_FLAG")
|
|
57
|
-
TOK.add(r"#define\s+(?P<name>[^\s]+)
|
|
59
|
+
TOK.add(r"#define\s+(?P<name>[^\s]+)(?P<value>[^\r\n]*)", "DEFINE")
|
|
60
|
+
TOK.add(r"#undef\s+(?P<name>[^\s]+)\s*", "UNDEF")
|
|
61
|
+
TOK.add(r"#ifdef\s+(?P<name>[^\s]+)\s*", "IFDEF")
|
|
62
|
+
TOK.add(r"#ifndef\s+(?P<name>[^\s]+)\s*", "IFNDEF")
|
|
63
|
+
TOK.add(r"#else\s*", "ELSE")
|
|
64
|
+
TOK.add(r"#endif\s*", "ENDIF")
|
|
58
65
|
TOK.add(r"typedef(?=\s)", "TYPEDEF")
|
|
59
66
|
TOK.add(r"(?:struct|union)(?=\s|{)", "STRUCT")
|
|
60
67
|
TOK.add(
|
|
@@ -63,7 +70,8 @@ class TokenParser(Parser):
|
|
|
63
70
|
"ENUM",
|
|
64
71
|
)
|
|
65
72
|
TOK.add(r"(?<=})\s*(?P<defs>(?:[a-zA-Z0-9_]+\s*,\s*)+[a-zA-Z0-9_]+)\s*(?=;)", "DEFS")
|
|
66
|
-
TOK.add(r"(?P<name>\**?\s*[a-zA-Z0-9_]+)(?:\s*:\s*(?P<bits>\d+))?(?:\[(?P<count>[
|
|
73
|
+
TOK.add(r"(?P<name>\**?\s*[a-zA-Z0-9_]+)(?:\s*:\s*(?P<bits>\d+))?(?:\[(?P<count>[^;]*)\])?\s*(?=;)", "NAME")
|
|
74
|
+
TOK.add(r"#include\s+(?P<name>[^\s]+)\s*", "INCLUDE")
|
|
67
75
|
TOK.add(r"[a-zA-Z_][a-zA-Z0-9_]*", "IDENTIFIER")
|
|
68
76
|
TOK.add(r"[{}]", "BLOCK")
|
|
69
77
|
TOK.add(r"\$(?P<name>[^\s]+) = (?P<value>{[^}]+})\w*[\r\n]+", "LOOKUP")
|
|
@@ -79,12 +87,61 @@ class TokenParser(Parser):
|
|
|
79
87
|
idents.append(tokens.consume())
|
|
80
88
|
return " ".join([i.value for i in idents])
|
|
81
89
|
|
|
90
|
+
def _conditional(self, tokens: TokenConsumer) -> None:
|
|
91
|
+
token = tokens.consume()
|
|
92
|
+
pattern = self.TOK.patterns[token.token]
|
|
93
|
+
match = pattern.match(token.value).groupdict()
|
|
94
|
+
|
|
95
|
+
value = match["name"]
|
|
96
|
+
|
|
97
|
+
if token.token == self.TOK.IFDEF:
|
|
98
|
+
self._conditionals.append(value in self.cstruct.consts)
|
|
99
|
+
elif token.token == self.TOK.IFNDEF:
|
|
100
|
+
self._conditionals.append(value not in self.cstruct.consts)
|
|
101
|
+
|
|
102
|
+
def _check_conditional(self, tokens: TokenConsumer) -> bool:
|
|
103
|
+
"""Check and handle conditionals. Return a boolean indicating if we need to continue to the next token."""
|
|
104
|
+
if self._conditionals and self._conditionals_depth == len(self._conditionals):
|
|
105
|
+
# If we have a conditional and the depth matches, handle it accordingly
|
|
106
|
+
if tokens.next == self.TOK.ELSE:
|
|
107
|
+
# Flip the last conditional
|
|
108
|
+
tokens.consume()
|
|
109
|
+
self._conditionals[-1] = not self._conditionals[-1]
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
if tokens.next == self.TOK.ENDIF:
|
|
113
|
+
# Pop the last conditional
|
|
114
|
+
tokens.consume()
|
|
115
|
+
self._conditionals.pop()
|
|
116
|
+
self._conditionals_depth -= 1
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
if tokens.next in (self.TOK.IFDEF, self.TOK.IFNDEF):
|
|
120
|
+
# If we encounter a new conditional, increase the depth
|
|
121
|
+
self._conditionals_depth += 1
|
|
122
|
+
|
|
123
|
+
if tokens.next == self.TOK.ENDIF:
|
|
124
|
+
# Similarly, decrease the depth if needed
|
|
125
|
+
self._conditionals_depth -= 1
|
|
126
|
+
|
|
127
|
+
if self._conditionals and not self._conditionals[-1]:
|
|
128
|
+
# If the last conditional evaluated to False, skip the next token
|
|
129
|
+
tokens.consume()
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
if tokens.next in (self.TOK.IFDEF, self.TOK.IFNDEF):
|
|
133
|
+
# If the next token is a conditional, process it
|
|
134
|
+
self._conditional(tokens)
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
return False
|
|
138
|
+
|
|
82
139
|
def _constant(self, tokens: TokenConsumer) -> None:
|
|
83
140
|
const = tokens.consume()
|
|
84
141
|
pattern = self.TOK.patterns[self.TOK.DEFINE]
|
|
85
142
|
match = pattern.match(const.value).groupdict()
|
|
86
143
|
|
|
87
|
-
value = match["value"]
|
|
144
|
+
value = match["value"].strip()
|
|
88
145
|
try:
|
|
89
146
|
value = ast.literal_eval(value)
|
|
90
147
|
except (ValueError, SyntaxError):
|
|
@@ -92,12 +149,22 @@ class TokenParser(Parser):
|
|
|
92
149
|
|
|
93
150
|
if isinstance(value, str):
|
|
94
151
|
try:
|
|
95
|
-
value = Expression(
|
|
152
|
+
value = Expression(value).evaluate(self.cstruct)
|
|
96
153
|
except (ExpressionParserError, ExpressionTokenizerError):
|
|
97
154
|
pass
|
|
98
155
|
|
|
99
156
|
self.cstruct.consts[match["name"]] = value
|
|
100
157
|
|
|
158
|
+
def _undef(self, tokens: TokenConsumer) -> None:
|
|
159
|
+
const = tokens.consume()
|
|
160
|
+
pattern = self.TOK.patterns[self.TOK.UNDEF]
|
|
161
|
+
match = pattern.match(const.value).groupdict()
|
|
162
|
+
|
|
163
|
+
if match["name"] in self.cstruct.consts:
|
|
164
|
+
del self.cstruct.consts[match["name"]]
|
|
165
|
+
else:
|
|
166
|
+
raise ParserError(f"line {self._lineno(const)}: constant {match['name']!r} not defined")
|
|
167
|
+
|
|
101
168
|
def _enum(self, tokens: TokenConsumer) -> None:
|
|
102
169
|
# We cheat with enums because the entire enum is in the token
|
|
103
170
|
etok = tokens.consume()
|
|
@@ -120,7 +187,7 @@ class TokenParser(Parser):
|
|
|
120
187
|
if not key:
|
|
121
188
|
continue
|
|
122
189
|
|
|
123
|
-
val = nextval if not val else Expression(self.cstruct,
|
|
190
|
+
val = nextval if not val else Expression(val).evaluate(self.cstruct, values)
|
|
124
191
|
|
|
125
192
|
if enumtype == "flag":
|
|
126
193
|
high_bit = val.bit_length() - 1
|
|
@@ -193,7 +260,7 @@ class TokenParser(Parser):
|
|
|
193
260
|
if tokens.next == self.TOK.NAME:
|
|
194
261
|
# As part of a struct field
|
|
195
262
|
# struct type_name field_name;
|
|
196
|
-
if not
|
|
263
|
+
if not names:
|
|
197
264
|
raise ParserError(f"line {self._lineno(tokens.next)}: unexpected anonymous struct")
|
|
198
265
|
return self.cstruct.resolve(names[0])
|
|
199
266
|
|
|
@@ -207,6 +274,9 @@ class TokenParser(Parser):
|
|
|
207
274
|
tokens.consume()
|
|
208
275
|
break
|
|
209
276
|
|
|
277
|
+
if self._check_conditional(tokens):
|
|
278
|
+
continue
|
|
279
|
+
|
|
210
280
|
field = self._parse_field(tokens)
|
|
211
281
|
fields.append(field)
|
|
212
282
|
|
|
@@ -265,7 +335,7 @@ class TokenParser(Parser):
|
|
|
265
335
|
return Field(None, type_, None)
|
|
266
336
|
|
|
267
337
|
if tokens.next != self.TOK.NAME:
|
|
268
|
-
raise ParserError(f"line {self._lineno(tokens.next)}: expected name")
|
|
338
|
+
raise ParserError(f"line {self._lineno(tokens.next)}: expected name, got {tokens.next!r}")
|
|
269
339
|
nametok = tokens.consume()
|
|
270
340
|
|
|
271
341
|
type_, name, bits = self._parse_field_type(type_, nametok.value)
|
|
@@ -293,9 +363,9 @@ class TokenParser(Parser):
|
|
|
293
363
|
if count == "":
|
|
294
364
|
count = None
|
|
295
365
|
else:
|
|
296
|
-
count = Expression(
|
|
366
|
+
count = Expression(count)
|
|
297
367
|
try:
|
|
298
|
-
count = count.evaluate()
|
|
368
|
+
count = count.evaluate(self.cstruct)
|
|
299
369
|
except Exception:
|
|
300
370
|
pass
|
|
301
371
|
|
|
@@ -313,17 +383,24 @@ class TokenParser(Parser):
|
|
|
313
383
|
tokens.eol()
|
|
314
384
|
break
|
|
315
385
|
|
|
316
|
-
if tokens.next not in (self.TOK.NAME, self.TOK.DEFS):
|
|
386
|
+
if tokens.next not in (self.TOK.NAME, self.TOK.DEFS, self.TOK.IDENTIFIER):
|
|
317
387
|
break
|
|
318
388
|
|
|
319
389
|
ntoken = tokens.consume()
|
|
320
|
-
if ntoken
|
|
390
|
+
if ntoken in (self.TOK.NAME, self.TOK.IDENTIFIER):
|
|
321
391
|
names.append(ntoken.value.strip())
|
|
322
392
|
elif ntoken == self.TOK.DEFS:
|
|
323
393
|
names.extend([name.strip() for name in ntoken.value.strip().split(",")])
|
|
324
394
|
|
|
325
395
|
return names
|
|
326
396
|
|
|
397
|
+
def _include(self, tokens: TokenConsumer) -> None:
|
|
398
|
+
include = tokens.consume()
|
|
399
|
+
pattern = self.TOK.patterns[self.TOK.INCLUDE]
|
|
400
|
+
match = pattern.match(include.value).groupdict()
|
|
401
|
+
|
|
402
|
+
self.cstruct.includes.append(match["name"].strip().strip("'\""))
|
|
403
|
+
|
|
327
404
|
@staticmethod
|
|
328
405
|
def _remove_comments(string: str) -> str:
|
|
329
406
|
# https://stackoverflow.com/a/18381470
|
|
@@ -370,10 +447,15 @@ class TokenParser(Parser):
|
|
|
370
447
|
if token is None:
|
|
371
448
|
break
|
|
372
449
|
|
|
450
|
+
if self._check_conditional(tokens):
|
|
451
|
+
continue
|
|
452
|
+
|
|
373
453
|
if token == self.TOK.CONFIG_FLAG:
|
|
374
454
|
self._config_flag(tokens)
|
|
375
455
|
elif token == self.TOK.DEFINE:
|
|
376
456
|
self._constant(tokens)
|
|
457
|
+
elif token == self.TOK.UNDEF:
|
|
458
|
+
self._undef(tokens)
|
|
377
459
|
elif token == self.TOK.TYPEDEF:
|
|
378
460
|
self._typedef(tokens)
|
|
379
461
|
elif token == self.TOK.STRUCT:
|
|
@@ -382,9 +464,14 @@ class TokenParser(Parser):
|
|
|
382
464
|
self._enum(tokens)
|
|
383
465
|
elif token == self.TOK.LOOKUP:
|
|
384
466
|
self._lookup(tokens)
|
|
467
|
+
elif token == self.TOK.INCLUDE:
|
|
468
|
+
self._include(tokens)
|
|
385
469
|
else:
|
|
386
470
|
raise ParserError(f"line {self._lineno(token)}: unexpected token {token!r}")
|
|
387
471
|
|
|
472
|
+
if self._conditionals:
|
|
473
|
+
raise ParserError(f"line {self._lineno(tokens.previous)}: unclosed conditional statement")
|
|
474
|
+
|
|
388
475
|
|
|
389
476
|
class CStyleParser(Parser):
|
|
390
477
|
"""Definition parser for C-like structure syntax.
|
|
@@ -434,7 +521,7 @@ class CStyleParser(Parser):
|
|
|
434
521
|
if not key:
|
|
435
522
|
continue
|
|
436
523
|
|
|
437
|
-
val = nextval if not val else Expression(
|
|
524
|
+
val = nextval if not val else Expression(val).evaluate(self.cstruct)
|
|
438
525
|
|
|
439
526
|
if enumtype == "flag":
|
|
440
527
|
high_bit = val.bit_length() - 1
|
|
@@ -515,9 +602,9 @@ class CStyleParser(Parser):
|
|
|
515
602
|
if d["count"] == "":
|
|
516
603
|
count = None
|
|
517
604
|
else:
|
|
518
|
-
count = Expression(
|
|
605
|
+
count = Expression(d["count"])
|
|
519
606
|
try:
|
|
520
|
-
count = count.evaluate()
|
|
607
|
+
count = count.evaluate(self.cstruct)
|
|
521
608
|
except Exception:
|
|
522
609
|
pass
|
|
523
610
|
|
|
@@ -92,6 +92,9 @@ def generate_cstruct_stub(cs: cstruct, module_prefix: str = "", cls_name: str =
|
|
|
92
92
|
stub = f"{name}: TypeAlias = {typedef.__name__}"
|
|
93
93
|
elif issubclass(typedef, (types.Enum, types.Flag)):
|
|
94
94
|
stub = generate_enum_stub(typedef, cs_prefix=cs_prefix, module_prefix=module_prefix)
|
|
95
|
+
elif issubclass(typedef, types.Pointer):
|
|
96
|
+
typehint = generate_typehint(typedef, prefix=cs_prefix, module_prefix=module_prefix)
|
|
97
|
+
stub = f"{name}: TypeAlias = {typehint}"
|
|
95
98
|
elif issubclass(typedef, types.Structure):
|
|
96
99
|
stub = generate_structure_stub(typedef, cs_prefix=cs_prefix, module_prefix=module_prefix)
|
|
97
100
|
elif issubclass(typedef, types.BaseType):
|
|
@@ -7,7 +7,7 @@ from dissect.cstruct.types.leb128 import LEB128
|
|
|
7
7
|
from dissect.cstruct.types.packed import Packed
|
|
8
8
|
from dissect.cstruct.types.pointer import Pointer
|
|
9
9
|
from dissect.cstruct.types.structure import Field, Structure, Union
|
|
10
|
-
from dissect.cstruct.types.void import Void
|
|
10
|
+
from dissect.cstruct.types.void import Void, VoidArray
|
|
11
11
|
from dissect.cstruct.types.wchar import Wchar, WcharArray
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
@@ -27,6 +27,7 @@ __all__ = [
|
|
|
27
27
|
"Structure",
|
|
28
28
|
"Union",
|
|
29
29
|
"Void",
|
|
30
|
+
"VoidArray",
|
|
30
31
|
"Wchar",
|
|
31
32
|
"WcharArray",
|
|
32
33
|
]
|
|
@@ -54,6 +54,10 @@ class MetaType(type):
|
|
|
54
54
|
"""Create a new array with the given number of entries."""
|
|
55
55
|
return cls.cs._make_array(cls, num_entries)
|
|
56
56
|
|
|
57
|
+
def __bool__(cls) -> bool:
|
|
58
|
+
"""Type class is always truthy."""
|
|
59
|
+
return True
|
|
60
|
+
|
|
57
61
|
def __len__(cls) -> int:
|
|
58
62
|
"""Return the byte size of the type."""
|
|
59
63
|
# Python 3.9 compat thing for bound type vars
|
|
@@ -258,7 +262,7 @@ class BaseArray(BaseType):
|
|
|
258
262
|
num = EOF
|
|
259
263
|
elif isinstance(cls.num_entries, Expression):
|
|
260
264
|
try:
|
|
261
|
-
num = max(0, cls.num_entries.evaluate(context))
|
|
265
|
+
num = max(0, cls.num_entries.evaluate(cls.cs, context))
|
|
262
266
|
except Exception:
|
|
263
267
|
if cls.num_entries.expression != "EOF":
|
|
264
268
|
raise
|