dissect.cstruct 3.10.dev2__tar.gz → 3.11__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 (60) hide show
  1. {dissect.cstruct-3.10.dev2/dissect.cstruct.egg-info → dissect.cstruct-3.11}/PKG-INFO +1 -1
  2. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/cstruct.py +7 -1
  3. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/exceptions.py +8 -0
  4. dissect.cstruct-3.11/dissect/cstruct/expression.py +301 -0
  5. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/utils.py +4 -1
  6. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11/dissect.cstruct.egg-info}/PKG-INFO +1 -1
  7. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/pyproject.toml +0 -1
  8. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_expression.py +36 -3
  9. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_struct.py +1 -1
  10. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_util.py +36 -0
  11. dissect.cstruct-3.10.dev2/dissect/cstruct/expression.py +0 -84
  12. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/COPYRIGHT +0 -0
  13. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/LICENSE +0 -0
  14. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/MANIFEST.in +0 -0
  15. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/README.md +0 -0
  16. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/__init__.py +0 -0
  17. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/bitbuffer.py +0 -0
  18. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/compiler.py +0 -0
  19. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/parser.py +0 -0
  20. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/__init__.py +0 -0
  21. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/base.py +0 -0
  22. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/bytesinteger.py +0 -0
  23. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/chartype.py +0 -0
  24. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/enum.py +0 -0
  25. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/flag.py +0 -0
  26. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/instance.py +0 -0
  27. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/packedtype.py +0 -0
  28. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/pointer.py +0 -0
  29. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/structure.py +0 -0
  30. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/voidtype.py +0 -0
  31. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/wchartype.py +0 -0
  32. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect.cstruct.egg-info/SOURCES.txt +0 -0
  33. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  34. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect.cstruct.egg-info/top_level.txt +0 -0
  35. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/examples/disk.py +0 -0
  36. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/examples/mirai.py +0 -0
  37. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/examples/pe.py +0 -0
  38. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/examples/secdesc.py +0 -0
  39. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/setup.cfg +0 -0
  40. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/__init__.py +0 -0
  41. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/conftest.py +0 -0
  42. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/data/testdef.txt +0 -0
  43. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/docs/Makefile +0 -0
  44. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/docs/conf.py +0 -0
  45. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/docs/index.rst +0 -0
  46. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_align.py +0 -0
  47. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_basic.py +0 -0
  48. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_bitbuffer.py +0 -0
  49. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_bitfield.py +0 -0
  50. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_bytesinteger.py +0 -0
  51. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_ctypes_type.py +0 -0
  52. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_enum.py +0 -0
  53. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_flag.py +0 -0
  54. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_packedtype.py +0 -0
  55. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_parser.py +0 -0
  56. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_pointer.py +0 -0
  57. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_union.py +0 -0
  58. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_utils.py +0 -0
  59. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/utils.py +0 -0
  60. {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.cstruct
3
- Version: 3.10.dev2
3
+ Version: 3.11
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
@@ -139,6 +139,10 @@ class cstruct:
139
139
  "u4": "uint32",
140
140
  "u8": "uint64",
141
141
  "u16": "uint128",
142
+ "__u8": "uint8",
143
+ "__u16": "uint16",
144
+ "__u32": "uint32",
145
+ "__u64": "uint64",
142
146
  "uchar": "uint8",
143
147
  "ushort": "unsigned short",
144
148
  "uint": "unsigned int",
@@ -184,7 +188,7 @@ class cstruct:
184
188
 
185
189
  self.typedefs[name] = type_
186
190
 
187
- def load(self, definition: str, deftype: int = None, **kwargs) -> None:
191
+ def load(self, definition: str, deftype: int = None, **kwargs) -> "cstruct":
188
192
  """Parse structures from the given definitions using the given definition type.
189
193
 
190
194
  Definitions can be parsed using different parsers. Currently, there's
@@ -208,6 +212,8 @@ class cstruct:
208
212
  elif deftype == cstruct.DEF_LEGACY:
209
213
  CStyleParser(self, **kwargs).parse(definition)
210
214
 
215
+ return self
216
+
211
217
  def loadfile(self, path: str, deftype: int = None, **kwargs) -> None:
212
218
  """Load structure definitions from a file.
213
219
 
@@ -16,3 +16,11 @@ class NullPointerDereference(Error):
16
16
 
17
17
  class ArraySizeError(Error):
18
18
  pass
19
+
20
+
21
+ class ExpressionParserError(Error):
22
+ pass
23
+
24
+
25
+ class ExpressionTokenizerError(Error):
26
+ pass
@@ -0,0 +1,301 @@
1
+ from __future__ import annotations
2
+
3
+ import string
4
+ from typing import TYPE_CHECKING, Callable, Optional, Union
5
+
6
+ from dissect.cstruct.exceptions import ExpressionParserError, ExpressionTokenizerError
7
+
8
+ if TYPE_CHECKING:
9
+ from dissect.cstruct import cstruct
10
+
11
+
12
+ HEXBIN_SUFFIX = {"x", "X", "b", "B"}
13
+
14
+
15
+ class ExpressionTokenizer:
16
+ def __init__(self, expression: str):
17
+ self.expression = expression
18
+ self.pos = 0
19
+ self.tokens = []
20
+
21
+ def equal(self, token: str, expected: Union[str, str[str]]) -> bool:
22
+ if isinstance(expected, set):
23
+ return token in expected
24
+ else:
25
+ return token == expected
26
+
27
+ def alnum(self, token: str) -> bool:
28
+ return token.isalnum()
29
+
30
+ def alpha(self, token: str) -> bool:
31
+ return token.isalpha()
32
+
33
+ def digit(self, token: str) -> bool:
34
+ return token.isdigit()
35
+
36
+ def hexdigit(self, token: str) -> bool:
37
+ return token in string.hexdigits
38
+
39
+ def operator(self, token: str) -> bool:
40
+ return token in {"*", "/", "+", "-", "%", "&", "^", "|", "(", ")", "~"}
41
+
42
+ def match(
43
+ self,
44
+ func: Optional[Callable[[str], bool]] = None,
45
+ expected: Optional[str] = None,
46
+ consume: bool = True,
47
+ append: bool = True,
48
+ ) -> bool:
49
+ if self.eol():
50
+ return False
51
+
52
+ token = self.get_token()
53
+
54
+ if expected and self.equal(token, expected):
55
+ if append:
56
+ self.tokens.append(token)
57
+ if consume:
58
+ self.consume()
59
+ return True
60
+
61
+ if func and func(token):
62
+ if append:
63
+ self.tokens.append(token)
64
+ if consume:
65
+ self.consume()
66
+ return True
67
+
68
+ return False
69
+
70
+ def consume(self) -> None:
71
+ self.pos += 1
72
+
73
+ def eol(self) -> bool:
74
+ return self.pos >= len(self.expression)
75
+
76
+ def get_token(self) -> str:
77
+ if self.eol():
78
+ raise ExpressionTokenizerError(f"Out of bounds index: {self.pos}, length: {len(self.expression)}")
79
+ return self.expression[self.pos]
80
+
81
+ def tokenize(self) -> list[str]:
82
+ token = ""
83
+
84
+ # Loop over expression runs in linear time
85
+ while not self.eol():
86
+ # If token is a single character operand add it to tokens
87
+ if self.match(self.operator):
88
+ continue
89
+
90
+ # If token is a single digit, keep looping over expression and build the number
91
+ elif self.match(self.digit, consume=False, append=False):
92
+ token += self.get_token()
93
+ self.consume()
94
+
95
+ # Support for binary and hexadecimal notation
96
+ if self.match(expected=HEXBIN_SUFFIX, consume=False, append=False):
97
+ token += self.get_token()
98
+ self.consume()
99
+
100
+ while self.match(self.hexdigit, consume=False, append=False):
101
+ token += self.get_token()
102
+ self.consume()
103
+ if self.eol():
104
+ break
105
+
106
+ # Checks for suffixes in numbers
107
+ if self.match(expected={"u", "U"}, consume=False, append=False):
108
+ self.consume()
109
+ self.match(expected={"l", "L"}, append=False)
110
+ self.match(expected={"l", "L"}, append=False)
111
+
112
+ elif self.match(expected={"l", "L"}, append=False):
113
+ self.match(expected={"l", "L"}, append=False)
114
+ self.match(expected={"u", "U"}, append=False)
115
+ else:
116
+ pass
117
+
118
+ # Number cannot end on x or b in the case of binary or hexadecimal notation
119
+ if len(token) == 2 and token[-1] in HEXBIN_SUFFIX:
120
+ raise ExpressionTokenizerError("Invalid binary or hex notation")
121
+
122
+ if len(token) > 1 and token[0] == "0" and token[1] not in HEXBIN_SUFFIX:
123
+ token = token[:1] + "o" + token[1:]
124
+ self.tokens.append(token)
125
+ token = ""
126
+
127
+ # If token is alpha or underscore we need to build the identifier
128
+ elif self.match(self.alpha, consume=False, append=False) or self.match(
129
+ expected="_", consume=False, append=False
130
+ ):
131
+ while self.match(self.alnum, consume=False, append=False) or self.match(
132
+ expected="_", consume=False, append=False
133
+ ):
134
+ token += self.get_token()
135
+ self.consume()
136
+ if self.eol():
137
+ break
138
+ self.tokens.append(token)
139
+ token = ""
140
+ # If token is length 2 operand make sure next character is part of length 2 operand append to tokens
141
+ elif self.match(expected=">", append=False) and self.match(expected=">", append=False):
142
+ self.tokens.append(">>")
143
+ elif self.match(expected="<", append=False) and self.match(expected="<", append=False):
144
+ self.tokens.append("<<")
145
+ elif self.match(expected=" ", append=False):
146
+ continue
147
+ else:
148
+ raise ExpressionTokenizerError(
149
+ f"Tokenizer does not recognize following token '{self.expression[self.pos]}'"
150
+ )
151
+ return self.tokens
152
+
153
+
154
+ class Expression:
155
+ """Expression parser for calculations in definitions."""
156
+
157
+ operators = {
158
+ "|": lambda a, b: a | b,
159
+ "^": lambda a, b: a ^ b,
160
+ "&": lambda a, b: a & b,
161
+ "<<": lambda a, b: a << b,
162
+ ">>": lambda a, b: a >> b,
163
+ "+": lambda a, b: a + b,
164
+ "-": lambda a, b: a - b,
165
+ "*": lambda a, b: a * b,
166
+ "/": lambda a, b: a // b,
167
+ "%": lambda a, b: a % b,
168
+ "u": lambda a: -a,
169
+ "~": lambda a: ~a,
170
+ }
171
+
172
+ precedence_levels = {
173
+ "|": 0,
174
+ "^": 1,
175
+ "&": 2,
176
+ "<<": 3,
177
+ ">>": 3,
178
+ "+": 4,
179
+ "-": 4,
180
+ "*": 5,
181
+ "/": 5,
182
+ "%": 5,
183
+ "u": 6,
184
+ "~": 6,
185
+ "sizeof": 6,
186
+ }
187
+
188
+ def __init__(self, cstruct: cstruct, expression: str):
189
+ self.cstruct = cstruct
190
+ self.expression = expression
191
+ self.tokens = ExpressionTokenizer(expression).tokenize()
192
+ self.stack = []
193
+ self.queue = []
194
+
195
+ def __repr__(self) -> str:
196
+ return self.expression
197
+
198
+ def precedence(self, o1: str, o2: str) -> bool:
199
+ return self.precedence_levels[o1] >= self.precedence_levels[o2]
200
+
201
+ def evaluate_exp(self) -> None:
202
+ operator = self.stack.pop(-1)
203
+ res = 0
204
+
205
+ if len(self.queue) < 1:
206
+ raise ExpressionParserError("Invalid expression: not enough operands")
207
+
208
+ right = self.queue.pop(-1)
209
+ if operator in ("u", "~"):
210
+ res = self.operators[operator](right)
211
+ else:
212
+ if len(self.queue) < 1:
213
+ raise ExpressionParserError("Invalid expression: not enough operands")
214
+
215
+ left = self.queue.pop(-1)
216
+ res = self.operators[operator](left, right)
217
+
218
+ self.queue.append(res)
219
+
220
+ def is_number(self, token: str) -> bool:
221
+ return token.isnumeric() or (len(token) > 2 and token[0] == "0" and token[1] in ("x", "X", "b", "B", "o", "O"))
222
+
223
+ def evaluate(self, context: Optional[dict[str, int]] = None) -> int:
224
+ """Evaluates an expression using a Shunting-Yard implementation."""
225
+
226
+ self.stack = []
227
+ self.queue = []
228
+ operators = set(self.operators.keys())
229
+
230
+ context = context or {}
231
+ tmp_expression = self.tokens
232
+
233
+ # Unary minus tokens; we change the semantic of '-' depending on the previous token
234
+ for i in range(len(self.tokens)):
235
+ if self.tokens[i] == "-":
236
+ if i == 0:
237
+ self.tokens[i] = "u"
238
+ continue
239
+ if self.tokens[i - 1] in operators or self.tokens[i - 1] == "u" or self.tokens[i - 1] == "(":
240
+ self.tokens[i] = "u"
241
+ continue
242
+
243
+ i = 0
244
+ while i < len(tmp_expression):
245
+ current_token = tmp_expression[i]
246
+ if self.is_number(current_token):
247
+ self.queue.append(int(current_token, 0))
248
+ elif current_token in context:
249
+ self.queue.append(int(context[current_token]))
250
+ elif current_token in self.cstruct.consts:
251
+ self.queue.append(int(self.cstruct.consts[current_token]))
252
+ elif current_token == "u":
253
+ self.stack.append(current_token)
254
+ elif current_token == "~":
255
+ self.stack.append(current_token)
256
+ elif current_token == "sizeof":
257
+ if len(tmp_expression) < i + 3 or (tmp_expression[i + 1] != "(" or tmp_expression[i + 3] != ")"):
258
+ raise ExpressionParserError("Invalid sizeof operation")
259
+ self.queue.append(len(self.cstruct.resolve(tmp_expression[i + 2])))
260
+ i += 3
261
+ elif current_token in operators:
262
+ while (
263
+ len(self.stack) != 0 and self.stack[-1] != "(" and (self.precedence(self.stack[-1], current_token))
264
+ ):
265
+ self.evaluate_exp()
266
+ self.stack.append(current_token)
267
+ elif current_token == "(":
268
+ if i > 0:
269
+ previous_token = tmp_expression[i - 1]
270
+ if self.is_number(previous_token):
271
+ raise ExpressionParserError(
272
+ f"Parser expected sizeof or an arethmethic operator instead got: '{previous_token}'"
273
+ )
274
+
275
+ self.stack.append(current_token)
276
+ elif current_token == ")":
277
+ if i > 0:
278
+ previous_token = tmp_expression[i - 1]
279
+ if previous_token == "(":
280
+ raise ExpressionParserError(
281
+ f"Parser expected an expression, instead received empty parenthesis. Index: {i}"
282
+ )
283
+
284
+ if len(self.stack) == 0:
285
+ raise ExpressionParserError("Invalid expression")
286
+
287
+ while self.stack[-1] != "(":
288
+ self.evaluate_exp()
289
+
290
+ self.stack.pop(-1)
291
+ else:
292
+ raise ExpressionParserError(f"Unmatched token: '{current_token}'")
293
+ i += 1
294
+
295
+ while len(self.stack) != 0:
296
+ if self.stack[-1] == "(":
297
+ raise ExpressionParserError("Invalid expression")
298
+
299
+ self.evaluate_exp()
300
+
301
+ return self.queue[0]
@@ -135,7 +135,10 @@ def _dumpstruct(
135
135
  ci = 0
136
136
  out = [f"struct {structure.name}:"]
137
137
  foreground, background = None, None
138
- for field in instance._type.fields:
138
+ for field in instance._type.lookup.values():
139
+ if getattr(field.type, "anonymous", False):
140
+ continue
141
+
139
142
  if color:
140
143
  foreground, background = colors[ci % len(colors)]
141
144
  palette.append((instance._size(field.name), background))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.cstruct
3
- Version: 3.10.dev2
3
+ Version: 3.11
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
@@ -46,4 +46,3 @@ license-files = ["LICENSE", "COPYRIGHT"]
46
46
  include = ["dissect.*"]
47
47
 
48
48
  [tool.setuptools_scm]
49
- local_scheme = "no-local-version"
@@ -1,6 +1,7 @@
1
1
  import pytest
2
2
  from dissect import cstruct
3
3
 
4
+ from dissect.cstruct.exceptions import ExpressionParserError, ExpressionTokenizerError
4
5
  from dissect.cstruct.expression import Expression
5
6
 
6
7
  testdata = [
@@ -44,13 +45,24 @@ testdata = [
44
45
  ("0 | 1", 1),
45
46
  ("1 | 1", 1),
46
47
  ("1 | 2", 3),
47
- # This type of expression is not supported by the parser and will fail.
48
- # ('4 * 1 + 1', 5),
48
+ ("1 | 2 | 4", 7),
49
+ ("1 & 1 * 4", 0),
50
+ ("(1 & 1) * 4", 4),
51
+ ("4 * 1 + 1", 5),
49
52
  ("-42", -42),
50
53
  ("42 + (-42)", 0),
51
54
  ("A + 5", 13),
52
55
  ("21 - B", 8),
53
56
  ("A + B", 21),
57
+ ("~1", -2),
58
+ ("~(A + 5)", ~13),
59
+ ("10l", 10),
60
+ ("10ll", 10),
61
+ ("10ull", 10),
62
+ ("010ULL", 8),
63
+ ("0Xf0 >> 4", 0xF),
64
+ ("0x1B", 0x1B),
65
+ ("0x1b", 0x1B),
54
66
  ]
55
67
 
56
68
 
@@ -67,11 +79,32 @@ def id_fn(val):
67
79
 
68
80
 
69
81
  @pytest.mark.parametrize("expression, answer", testdata, ids=id_fn)
70
- def test_expression(expression, answer):
82
+ def test_expression(expression: str, answer: int) -> None:
71
83
  parser = Expression(Consts(), expression)
72
84
  assert parser.evaluate() == answer
73
85
 
74
86
 
87
+ @pytest.mark.parametrize(
88
+ "expression, exception, message",
89
+ [
90
+ ("0b", ExpressionTokenizerError, "Invalid binary or hex notation"),
91
+ ("0x", ExpressionTokenizerError, "Invalid binary or hex notation"),
92
+ ("$", ExpressionTokenizerError, "Tokenizer does not recognize following token '\\$'"),
93
+ ("-", ExpressionParserError, "Invalid expression: not enough operands"),
94
+ ("(", ExpressionParserError, "Invalid expression"),
95
+ (")", ExpressionParserError, "Invalid expression"),
96
+ ("()", ExpressionParserError, "Parser expected an expression, instead received empty parenthesis. Index: 1"),
97
+ ("0()", ExpressionParserError, "Parser expected sizeof or an arethmethic operator instead got: '0'"),
98
+ ("sizeof)", ExpressionParserError, "Invalid sizeof operation"),
99
+ ("sizeof(0 +)", ExpressionParserError, "Invalid sizeof operation"),
100
+ ],
101
+ )
102
+ def test_expression_failure(expression: str, exception: type, message: str) -> None:
103
+ with pytest.raises(exception, match=message):
104
+ parser = Expression(Consts(), expression)
105
+ parser.evaluate()
106
+
107
+
75
108
  def test_sizeof():
76
109
  d = """
77
110
  struct test {
@@ -114,7 +114,7 @@ def test_struct_expressions(compiled):
114
114
  #define const 1
115
115
  struct test {
116
116
  uint8 flag;
117
- uint8 data_1[flag & 1 * 4];
117
+ uint8 data_1[(flag & 1) * 4];
118
118
  uint8 data_2[flag & (1 << 2)];
119
119
  uint8 data_3[const];
120
120
  };
@@ -28,6 +28,42 @@ def test_dumpstruct(capsys, compiled):
28
28
  uint32 testval;
29
29
  };
30
30
  """
31
+
32
+ cs = cstruct.cstruct()
33
+ cs.load(cdef, compiled=compiled)
34
+
35
+ assert verify_compiled(cs.test, compiled)
36
+
37
+ buf = b"\x39\x05\x00\x00"
38
+ obj = cs.test(buf)
39
+
40
+ dumpstruct(cs.test, buf)
41
+ captured_1 = capsys.readouterr()
42
+
43
+ dumpstruct(obj)
44
+ captured_2 = capsys.readouterr()
45
+
46
+ assert captured_1.out == captured_2.out
47
+
48
+ out_1 = dumpstruct(cs.test, buf, output="string")
49
+ out_2 = dumpstruct(obj, output="string")
50
+
51
+ assert out_1 == out_2
52
+
53
+ with pytest.raises(ValueError) as excinfo:
54
+ dumpstruct(obj, output="generator")
55
+ assert str(excinfo.value) == "Invalid output argument: 'generator' (should be 'print' or 'string')."
56
+
57
+
58
+ def test_dumpstruct_anonymous(capsys, compiled):
59
+ cdef = """
60
+ struct test {
61
+ struct {
62
+ uint32 testval;
63
+ };
64
+ };
65
+ """
66
+
31
67
  cs = cstruct.cstruct()
32
68
  cs.load(cdef, compiled=compiled)
33
69
 
@@ -1,84 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING, Dict
4
-
5
- if TYPE_CHECKING:
6
- from dissect.cstruct import cstruct
7
-
8
-
9
- class Expression:
10
- """Expression parser for simple calculations in definitions."""
11
-
12
- operators = [
13
- ("*", lambda a, b: a * b),
14
- ("/", lambda a, b: a // b),
15
- ("%", lambda a, b: a % b),
16
- ("+", lambda a, b: a + b),
17
- ("-", lambda a, b: a - b),
18
- (">>", lambda a, b: a >> b),
19
- ("<<", lambda a, b: a << b),
20
- ("&", lambda a, b: a & b),
21
- ("^", lambda a, b: a ^ b),
22
- ("|", lambda a, b: a | b),
23
- ]
24
-
25
- def __init__(self, cstruct: cstruct, expression: str):
26
- self.cstruct = cstruct
27
- self.expression = expression
28
-
29
- def __repr__(self) -> str:
30
- return self.expression
31
-
32
- def evaluate(self, context: Dict[str, int] = None) -> int:
33
- context = context or {}
34
- levels = []
35
- buf = ""
36
-
37
- for i in range(len(self.expression)):
38
- if self.expression[i] == "(":
39
- levels.append(buf)
40
- buf = ""
41
- continue
42
-
43
- if self.expression[i] == ")":
44
- if levels[-1] == "sizeof":
45
- value = len(self.cstruct.resolve(buf))
46
- levels[-1] = ""
47
- else:
48
- value = self.evaluate_part(buf, context)
49
- buf = levels.pop()
50
- buf += str(value)
51
- continue
52
-
53
- buf += self.expression[i]
54
-
55
- return self.evaluate_part(buf, context)
56
-
57
- def evaluate_part(self, buf: str, context: Dict[str, int]) -> int:
58
- buf = buf.strip()
59
-
60
- # Very simple way to support an expression(part) that is a single,
61
- # negative value. To use negative values in more complex expressions,
62
- # they must be wrapped in brackets, e.g.: 2 * (-5).
63
- #
64
- # To have full support for the negation operator a proper expression
65
- # parser must be build.
66
- if buf.startswith("-") and buf[1:].isnumeric():
67
- return int(buf)
68
-
69
- for operator in self.operators:
70
- if operator[0] in buf:
71
- a, b = buf.rsplit(operator[0], 1)
72
-
73
- return operator[1](self.evaluate_part(a, context), self.evaluate_part(b, context))
74
-
75
- if buf in context:
76
- return context[buf]
77
-
78
- if buf.startswith("0x"):
79
- return int(buf, 16)
80
-
81
- if buf in self.cstruct.consts:
82
- return int(self.cstruct.consts[buf])
83
-
84
- return int(buf)