dissect.cstruct 4.6.dev6__tar.gz → 4.6.dev8__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.
Files changed (76) hide show
  1. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/PKG-INFO +1 -1
  2. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/parser.py +80 -3
  3. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect.cstruct.egg-info/PKG-INFO +1 -1
  4. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_parser.py +134 -4
  5. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/.git-blame-ignore-revs +0 -0
  6. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/.gitattributes +0 -0
  7. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/CHANGELOG.md +0 -0
  8. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/COPYRIGHT +0 -0
  9. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/LICENSE +0 -0
  10. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/MANIFEST.in +0 -0
  11. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/README.md +0 -0
  12. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/__init__.py +0 -0
  13. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/bitbuffer.py +0 -0
  14. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/compiler.py +0 -0
  15. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/cstruct.py +0 -0
  16. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/exceptions.py +0 -0
  17. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/expression.py +0 -0
  18. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/tools/__init__.py +0 -0
  19. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/tools/stubgen.py +0 -0
  20. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/types/__init__.py +0 -0
  21. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/types/base.py +0 -0
  22. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/types/char.py +0 -0
  23. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/types/enum.py +0 -0
  24. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/types/flag.py +0 -0
  25. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/types/int.py +0 -0
  26. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/types/leb128.py +0 -0
  27. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/types/packed.py +0 -0
  28. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/types/pointer.py +0 -0
  29. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/types/structure.py +0 -0
  30. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/types/void.py +0 -0
  31. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/types/wchar.py +0 -0
  32. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect/cstruct/utils.py +0 -0
  33. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect.cstruct.egg-info/SOURCES.txt +0 -0
  34. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  35. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect.cstruct.egg-info/entry_points.txt +0 -0
  36. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect.cstruct.egg-info/requires.txt +0 -0
  37. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/dissect.cstruct.egg-info/top_level.txt +0 -0
  38. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/examples/disk.py +0 -0
  39. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/examples/mirai.py +0 -0
  40. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/examples/pe.py +0 -0
  41. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/examples/protobuf.py +0 -0
  42. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/examples/secdesc.py +0 -0
  43. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/pyproject.toml +0 -0
  44. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/setup.cfg +0 -0
  45. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/__init__.py +0 -0
  46. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/_data/testdef.txt +0 -0
  47. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/_docs/Makefile +0 -0
  48. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/_docs/__init__.py +0 -0
  49. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/_docs/conf.py +0 -0
  50. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/_docs/index.rst +0 -0
  51. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/conftest.py +0 -0
  52. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_align.py +0 -0
  53. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_annotations.py +0 -0
  54. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_basic.py +0 -0
  55. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_bitbuffer.py +0 -0
  56. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_bitfield.py +0 -0
  57. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_compiler.py +0 -0
  58. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_ctypes.py +0 -0
  59. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_expression.py +0 -0
  60. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_tools_stubgen.py +0 -0
  61. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_base.py +0 -0
  62. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_char.py +0 -0
  63. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_custom.py +0 -0
  64. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_enum.py +0 -0
  65. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_flag.py +0 -0
  66. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_int.py +0 -0
  67. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_leb128.py +0 -0
  68. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_packed.py +0 -0
  69. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_pointer.py +0 -0
  70. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_structure.py +0 -0
  71. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_union.py +0 -0
  72. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_void.py +0 -0
  73. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_types_wchar.py +0 -0
  74. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/test_utils.py +0 -0
  75. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tests/utils.py +0 -0
  76. {dissect_cstruct-4.6.dev6 → dissect_cstruct-4.6.dev8}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.cstruct
3
- Version: 4.6.dev6
3
+ Version: 4.6.dev8
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
@@ -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]+)\s+(?P<value>[^\r\n]+)\s*", "DEFINE")
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(
@@ -80,12 +87,61 @@ class TokenParser(Parser):
80
87
  idents.append(tokens.consume())
81
88
  return " ".join([i.value for i in idents])
82
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
+
83
139
  def _constant(self, tokens: TokenConsumer) -> None:
84
140
  const = tokens.consume()
85
141
  pattern = self.TOK.patterns[self.TOK.DEFINE]
86
142
  match = pattern.match(const.value).groupdict()
87
143
 
88
- value = match["value"]
144
+ value = match["value"].strip()
89
145
  try:
90
146
  value = ast.literal_eval(value)
91
147
  except (ValueError, SyntaxError):
@@ -99,6 +155,16 @@ class TokenParser(Parser):
99
155
 
100
156
  self.cstruct.consts[match["name"]] = value
101
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
+
102
168
  def _enum(self, tokens: TokenConsumer) -> None:
103
169
  # We cheat with enums because the entire enum is in the token
104
170
  etok = tokens.consume()
@@ -208,6 +274,9 @@ class TokenParser(Parser):
208
274
  tokens.consume()
209
275
  break
210
276
 
277
+ if self._check_conditional(tokens):
278
+ continue
279
+
211
280
  field = self._parse_field(tokens)
212
281
  fields.append(field)
213
282
 
@@ -266,7 +335,7 @@ class TokenParser(Parser):
266
335
  return Field(None, type_, None)
267
336
 
268
337
  if tokens.next != self.TOK.NAME:
269
- 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}")
270
339
  nametok = tokens.consume()
271
340
 
272
341
  type_, name, bits = self._parse_field_type(type_, nametok.value)
@@ -378,10 +447,15 @@ class TokenParser(Parser):
378
447
  if token is None:
379
448
  break
380
449
 
450
+ if self._check_conditional(tokens):
451
+ continue
452
+
381
453
  if token == self.TOK.CONFIG_FLAG:
382
454
  self._config_flag(tokens)
383
455
  elif token == self.TOK.DEFINE:
384
456
  self._constant(tokens)
457
+ elif token == self.TOK.UNDEF:
458
+ self._undef(tokens)
385
459
  elif token == self.TOK.TYPEDEF:
386
460
  self._typedef(tokens)
387
461
  elif token == self.TOK.STRUCT:
@@ -395,6 +469,9 @@ class TokenParser(Parser):
395
469
  else:
396
470
  raise ParserError(f"line {self._lineno(token)}: unexpected token {token!r}")
397
471
 
472
+ if self._conditionals:
473
+ raise ParserError(f"line {self._lineno(tokens.previous)}: unclosed conditional statement")
474
+
398
475
 
399
476
  class CStyleParser(Parser):
400
477
  """Definition parser for C-like structure syntax.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.cstruct
3
- Version: 4.6.dev6
3
+ Version: 4.6.dev8
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
@@ -1,18 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
4
3
  from unittest.mock import Mock
5
4
 
6
5
  import pytest
7
6
 
7
+ from dissect.cstruct import cstruct
8
8
  from dissect.cstruct.exceptions import ParserError
9
9
  from dissect.cstruct.parser import TokenParser
10
10
  from dissect.cstruct.types import BaseArray, Pointer, Structure
11
11
  from tests.utils import verify_compiled
12
12
 
13
- if TYPE_CHECKING:
14
- from dissect.cstruct import cstruct
15
-
16
13
 
17
14
  def test_nested_structs(cs: cstruct, compiled: bool) -> None:
18
15
  cdef = """
@@ -164,3 +161,136 @@ def test_typedef_pointer(cs: cstruct) -> None:
164
161
  assert cs.IMAGE_DATA_DIRECTORY is cs._IMAGE_DATA_DIRECTORY
165
162
  assert issubclass(cs.PIMAGE_DATA_DIRECTORY, Pointer)
166
163
  assert cs.PIMAGE_DATA_DIRECTORY.type == cs._IMAGE_DATA_DIRECTORY
164
+
165
+
166
+ def test_undef(cs: cstruct) -> None:
167
+ cdef = """
168
+ #define MY_CONST 42
169
+ #undef MY_CONST
170
+ """
171
+ cs.load(cdef)
172
+
173
+ assert "MY_CONST" not in cs.consts
174
+
175
+ with pytest.raises(ParserError, match="line 1: constant 'MY_CONST' not defined"):
176
+ cs.load("#undef MY_CONST") # This should raise an error since MY_CONST is not defined
177
+
178
+
179
+ def test_conditional_ifdef(cs: cstruct) -> None:
180
+ cdef = """
181
+ #define MY_CONST 42
182
+
183
+ #ifdef MY_CONST
184
+ struct test {
185
+ uint32 a;
186
+ };
187
+ #endif
188
+ """
189
+ cs.load(cdef)
190
+
191
+ assert "test" in cs.typedefs
192
+
193
+
194
+ def test_conditional_ifndef(cs: cstruct) -> None:
195
+ cdef = """
196
+ #ifndef MYVAR
197
+ #define MYVAR (1)
198
+ #endif
199
+ """
200
+ cs.load(cdef)
201
+
202
+ assert "MYVAR" in cs.consts
203
+ assert cs.consts["MYVAR"] == 1
204
+
205
+
206
+ def test_conditional_ifndef_guard(cs: cstruct) -> None:
207
+ cdef = """
208
+ /* Define Guard */
209
+ #ifndef __MYGUARD
210
+ #define __MYGUARD
211
+
212
+ typedef struct myStruct
213
+ {
214
+ char charVal[16];
215
+ }
216
+ #endif // __MYGUARD
217
+ """
218
+ cs.load(cdef)
219
+
220
+ assert "__MYGUARD" in cs.consts
221
+ assert "myStruct" in cs.typedefs
222
+
223
+
224
+ def test_conditional_nested() -> None:
225
+ cdef = """
226
+ #ifndef MYSWITCH1
227
+ #define MYVAR1 (1)
228
+ #else
229
+ #ifdef MYSWITCH2
230
+ #define MYVAR1 (2)
231
+ #else
232
+ #define MYVAR1 (3)
233
+ #endif
234
+ #endif
235
+ """
236
+ cs = cstruct().load(cdef)
237
+
238
+ assert "MYVAR1" in cs.consts
239
+ assert cs.consts["MYVAR1"] == 1
240
+
241
+ cs = cstruct().load("#define MYSWITCH1")
242
+
243
+ assert "MYSWITCH1" in cs.consts
244
+
245
+ cs.load(cdef)
246
+
247
+ assert "MYVAR1" in cs.consts
248
+ assert cs.consts["MYVAR1"] == 3
249
+
250
+
251
+ def test_conditional_in_struct(cs: cstruct) -> None:
252
+ cdef = """
253
+ struct t_bitfield {
254
+ union {
255
+ struct {
256
+ uint32_t bit0:1;
257
+ uint32_t bit1:1;
258
+ #ifdef MYSWT
259
+ uint32_t bit2:1;
260
+ #endif
261
+ } fval;
262
+ uint32_t bits;
263
+ };
264
+ };
265
+ """
266
+ cs.load(cdef)
267
+
268
+ assert "t_bitfield" in cs.typedefs
269
+ assert "fval" in cs.t_bitfield.fields
270
+ assert "bit0" in cs.t_bitfield.fields["fval"].type.fields
271
+ assert "bit1" in cs.t_bitfield.fields["fval"].type.fields
272
+ assert "bit2" not in cs.t_bitfield.fields["fval"].type.fields
273
+
274
+
275
+ def test_conditional_parsing_error(cs: cstruct) -> None:
276
+ cdef = """
277
+ #ifndef __HELP
278
+ #define __HELP
279
+ #endif
280
+ struct test {
281
+ uint32 a;
282
+ };
283
+ #endif
284
+ """
285
+ with pytest.raises(ParserError, match="line 8: unexpected token .+ENDIF"):
286
+ cs.load(cdef)
287
+
288
+ cdef = """
289
+ #ifndef __HELP
290
+ #define __HELP
291
+ struct test {
292
+ uint32 a;
293
+ };
294
+ """
295
+ with pytest.raises(ParserError, match="line 6: unclosed conditional statement"):
296
+ cs.load(cdef)