dissect.cstruct 5.0.dev14__tar.gz → 5.0.dev15__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-5.0.dev14 → dissect_cstruct-5.0.dev15}/PKG-INFO +1 -1
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/lexer.py +23 -10
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/parser.py +35 -19
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/PKG-INFO +1 -1
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/scm_version.json +2 -2
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_lexer.py +8 -8
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_parser.py +124 -16
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/.git-blame-ignore-revs +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/.gitattributes +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/CHANGELOG.md +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/COPYRIGHT +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/LICENSE +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/MANIFEST.in +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/README.md +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/__init__.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/bitbuffer.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/compiler.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/cstruct.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/exception.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/expression.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/tools/__init__.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/tools/stubgen.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/types/__init__.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/types/base.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/types/char.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/types/enum.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/types/flag.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/types/int.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/types/leb128.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/types/packed.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/types/pointer.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/types/structure.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/types/void.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/types/wchar.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect/cstruct/util.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/SOURCES.txt +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/entry_points.txt +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/requires.txt +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/scm_file_list.json +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/top_level.txt +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/examples/disk.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/examples/mirai.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/examples/pe.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/examples/protobuf.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/examples/secdesc.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/pyproject.toml +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/setup.cfg +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/__init__.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/_data/testdef.txt +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/_docs/Makefile +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/_docs/__init__.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/_docs/conf.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/_docs/index.rst +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/conftest.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_align.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_annotations.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_basic.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_benchmark.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_bitbuffer.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_bitfield.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_compiler.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_ctypes.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_expression.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_tools_stubgen.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_base.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_char.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_custom.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_enum.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_flag.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_int.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_leb128.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_packed.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_pointer.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_structure.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_union.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_void.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_types_wchar.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/test_utils.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tests/utils.py +0 -0
- {dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.dev15
|
|
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-Expression: Apache-2.0
|
|
@@ -125,6 +125,7 @@ _SINGLE_CHARS = {
|
|
|
125
125
|
|
|
126
126
|
_RE_IDENTIFIER = re.compile(r"[a-zA-Z_.][a-zA-Z0-9_.]*")
|
|
127
127
|
_RE_WHITESPACE = re.compile(r"[ \t\r\n]+")
|
|
128
|
+
_RE_DEFINE_LITERAL = re.compile(r"""(["'])(?:\\.|(?!\1)[^\\\n])*\1""")
|
|
128
129
|
|
|
129
130
|
IDENTIFIER_TYPES = set(_C_KEYWORDS.values()) | {TokenType.IDENTIFIER}
|
|
130
131
|
|
|
@@ -292,10 +293,10 @@ class Lexer:
|
|
|
292
293
|
if peek == "*":
|
|
293
294
|
self._take(2) # Consume /*
|
|
294
295
|
end = self.data.find("*/", self._pos)
|
|
295
|
-
if end
|
|
296
|
-
self.
|
|
297
|
-
|
|
298
|
-
|
|
296
|
+
if end == -1:
|
|
297
|
+
raise self._error("unterminated comment")
|
|
298
|
+
|
|
299
|
+
self._take(end - self._pos + 2)
|
|
299
300
|
|
|
300
301
|
elif peek == "/":
|
|
301
302
|
self._take(2) # Consume //
|
|
@@ -390,6 +391,23 @@ class Lexer:
|
|
|
390
391
|
|
|
391
392
|
return result
|
|
392
393
|
|
|
394
|
+
def _read_define_value(self) -> str:
|
|
395
|
+
"""Read the value of a ``#define`` up to the end of the line."""
|
|
396
|
+
value = []
|
|
397
|
+
|
|
398
|
+
while not self.eof and (ch := self._current()) != "\n":
|
|
399
|
+
if ch == "/" and self._peek() in ("/", "*"):
|
|
400
|
+
self._skip_comment()
|
|
401
|
+
value.append(" ")
|
|
402
|
+
elif ch in ('"', "'"):
|
|
403
|
+
if not (match := _RE_DEFINE_LITERAL.match(self.data, self._pos)):
|
|
404
|
+
raise self._error("unterminated string literal")
|
|
405
|
+
value.append(self._take(match.end() - self._pos))
|
|
406
|
+
else:
|
|
407
|
+
value.append(self._read_until("\n\"'/") or self._take())
|
|
408
|
+
|
|
409
|
+
return "".join(value).strip()
|
|
410
|
+
|
|
393
411
|
def _read_preprocessor(self) -> None:
|
|
394
412
|
"""Read a preprocessor directive starting with ``#``."""
|
|
395
413
|
line = self._line
|
|
@@ -423,12 +441,7 @@ class Lexer:
|
|
|
423
441
|
raise self._error("expected identifier after '#define'", lineno=line)
|
|
424
442
|
self._emit(TokenType.IDENTIFIER, name, line)
|
|
425
443
|
|
|
426
|
-
self.
|
|
427
|
-
if self.eof or self._line != line:
|
|
428
|
-
# No value, just a simple macro definition
|
|
429
|
-
return
|
|
430
|
-
|
|
431
|
-
if (value := self._read_until("\n")).strip():
|
|
444
|
+
if value := self._read_define_value():
|
|
432
445
|
self._emit(TokenType.STRING, value, line)
|
|
433
446
|
|
|
434
447
|
elif token_type == TokenType.PP_INCLUDE:
|
|
@@ -20,6 +20,10 @@ if TYPE_CHECKING:
|
|
|
20
20
|
from dissect.cstruct.types import BaseType, Enum, Flag
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
# A string or the name of a constant in the value of a define
|
|
24
|
+
_RE_DEFINE_STRING = re.compile(r"""\s*(?:([bB]?)(["'])((?:\\.|(?!\2)[^\\\n])*)\2|([a-zA-Z_.][a-zA-Z0-9_.]*))""")
|
|
25
|
+
|
|
26
|
+
|
|
23
27
|
class Parser(TokenCursor):
|
|
24
28
|
"""Base class for definition parsers.
|
|
25
29
|
|
|
@@ -42,7 +46,7 @@ class Parser(TokenCursor):
|
|
|
42
46
|
|
|
43
47
|
def _join_line_continuations(string: str) -> str:
|
|
44
48
|
# Join lines ending with backslash
|
|
45
|
-
return re.sub(r"\\\n", "", string)
|
|
49
|
+
return re.sub(r"\\\r?\n", "", string)
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
class CStyleParser(Parser):
|
|
@@ -138,11 +142,16 @@ class CStyleParser(Parser):
|
|
|
138
142
|
elif token.type == TokenType.TYPEDEF:
|
|
139
143
|
self._parse_typedef()
|
|
140
144
|
elif token.type in (TokenType.STRUCT, TokenType.UNION):
|
|
141
|
-
self._parse_struct_or_union()
|
|
145
|
+
type_ = self._parse_struct_or_union()
|
|
142
146
|
|
|
143
147
|
# Skip variable declarations after struct/union definitions
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
has_variable = self._at(TokenType.IDENTIFIER, TokenType.STAR)
|
|
149
|
+
while has_variable:
|
|
150
|
+
_, _, bits = self._parse_field_name(type_)
|
|
151
|
+
if bits is not None:
|
|
152
|
+
raise self._error("variables cannot have bitfields")
|
|
153
|
+
|
|
154
|
+
has_variable = self._match(TokenType.COMMA)
|
|
146
155
|
|
|
147
156
|
self._expect(TokenType.SEMICOLON)
|
|
148
157
|
elif token.type in (TokenType.ENUM, TokenType.FLAG):
|
|
@@ -174,21 +183,28 @@ class CStyleParser(Parser):
|
|
|
174
183
|
value = self._take().value
|
|
175
184
|
|
|
176
185
|
if value:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
186
|
+
# Adjacent strings and constants that are strings are concatenated like in C
|
|
187
|
+
strings = []
|
|
188
|
+
pos = 0
|
|
189
|
+
if '"' in value or "'" in value:
|
|
190
|
+
while pos < len(value) and (match := _RE_DEFINE_STRING.match(value, pos)):
|
|
191
|
+
is_bytes, _, string, name = match.groups()
|
|
192
|
+
if name is None:
|
|
193
|
+
# Flatten escape sequences
|
|
194
|
+
string = string.encode().decode("unicode_escape")
|
|
195
|
+
strings.append(string.encode("latin-1") if is_bytes else string)
|
|
196
|
+
elif isinstance(constant := self.cs.consts.get(name), (str, bytes)):
|
|
197
|
+
strings.append(constant)
|
|
198
|
+
else:
|
|
199
|
+
break
|
|
200
|
+
|
|
201
|
+
pos = match.end()
|
|
202
|
+
|
|
203
|
+
if strings and pos == len(value):
|
|
204
|
+
try:
|
|
205
|
+
value = strings[0][:0].join(strings)
|
|
206
|
+
except TypeError:
|
|
207
|
+
raise self._error("cannot concatenate strings and bytes", token=token) from None
|
|
192
208
|
else:
|
|
193
209
|
try:
|
|
194
210
|
# Lazy mode, try to evaluate as a Python literal first (for simple constants)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.dev15
|
|
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-Expression: Apache-2.0
|
|
@@ -90,14 +90,6 @@ from dissect.cstruct.lexer import TokenType, tokenize
|
|
|
90
90
|
[TokenType.PP_DEFINE, TokenType.IDENTIFIER, TokenType.STRING],
|
|
91
91
|
["define", "FOO", "ADCRYPT\00"],
|
|
92
92
|
),
|
|
93
|
-
(
|
|
94
|
-
"""
|
|
95
|
-
#define FOO (1 \
|
|
96
|
-
+ 2)
|
|
97
|
-
""",
|
|
98
|
-
[TokenType.PP_DEFINE, TokenType.IDENTIFIER, TokenType.STRING],
|
|
99
|
-
["define", "FOO", "(1 + 2)"],
|
|
100
|
-
),
|
|
101
93
|
("#undef", [TokenType.PP_UNDEF], ["undef"]),
|
|
102
94
|
("#ifdef", [TokenType.PP_IFDEF], ["ifdef"]),
|
|
103
95
|
("#ifndef", [TokenType.PP_IFNDEF], ["ifndef"]),
|
|
@@ -174,6 +166,14 @@ def test_token(src: str, types: list[TokenType], values: list[str]) -> None:
|
|
|
174
166
|
("#include", "unexpected end of input"),
|
|
175
167
|
("#include <missing_end", "unexpected end of input"),
|
|
176
168
|
('#include "missing_end', "unexpected end of input"),
|
|
169
|
+
('#define A "x\ny"\n#define B 2\n', "line 1: unterminated string literal"),
|
|
170
|
+
('#define A "x\n#define B "y"\n', "line 1: unterminated string literal"),
|
|
171
|
+
('#define A "x', "line 1: unterminated string literal"),
|
|
172
|
+
("#define A 'x\n", "line 1: unterminated string literal"),
|
|
173
|
+
("#define A it's\n", "line 1: unterminated string literal"),
|
|
174
|
+
("/* unterminated", "line 1: unterminated comment"),
|
|
175
|
+
("struct a {\n uint8 b; /* c\n};\n", "line 2: unterminated comment"),
|
|
176
|
+
("#define A 1 /* c\n#define B 2\n", "line 1: unterminated comment"),
|
|
177
177
|
("@", "unexpected character"),
|
|
178
178
|
("`", "unexpected character"),
|
|
179
179
|
],
|
|
@@ -41,6 +41,65 @@ def test_struct(cs: cstruct, compiled: bool) -> None:
|
|
|
41
41
|
cs.resolve(name)
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def test_struct_variables(cs: cstruct) -> None:
|
|
45
|
+
"""Test that all forms of variable declarations after a struct or union definition are silently ignored."""
|
|
46
|
+
cdef = """
|
|
47
|
+
#define SIZE 2
|
|
48
|
+
|
|
49
|
+
struct test {
|
|
50
|
+
uint8 a;
|
|
51
|
+
} b[4], *c[SIZE], d[2][3], e[];
|
|
52
|
+
|
|
53
|
+
union test1 {
|
|
54
|
+
uint8 a;
|
|
55
|
+
} f;
|
|
56
|
+
|
|
57
|
+
struct test g, *h;
|
|
58
|
+
|
|
59
|
+
typedef uint8 test2;
|
|
60
|
+
"""
|
|
61
|
+
cs.load(cdef)
|
|
62
|
+
|
|
63
|
+
assert cs.resolve("test") is cs.test
|
|
64
|
+
assert cs.resolve("test1") is cs.test1
|
|
65
|
+
assert cs.resolve("test2") is cs.uint8
|
|
66
|
+
|
|
67
|
+
for name in ("b", "c", "d", "e", "f", "g", "h"):
|
|
68
|
+
with pytest.raises(ResolveError, match=f"Unknown type {name}"):
|
|
69
|
+
cs.resolve(name)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@pytest.mark.parametrize(
|
|
73
|
+
("cdef", "match"),
|
|
74
|
+
[
|
|
75
|
+
# A missing semicolon before the next definition
|
|
76
|
+
("struct a { uint8 x; }\nstruct b { uint8 x; };", "line 2: expected ;, got struct"),
|
|
77
|
+
("struct a { uint8 x; }\nunion b { uint8 x; };", "line 2: expected ;, got union"),
|
|
78
|
+
("struct a { uint8 x; }\nenum b : uint8 { C };", "line 2: expected ;, got enum"),
|
|
79
|
+
("struct a { uint8 x; }\nflag b : uint8 { C };", "line 2: expected ;, got flag"),
|
|
80
|
+
("struct a { uint8 x; }\ntypedef uint8 b;", "line 2: expected ;, got typedef"),
|
|
81
|
+
("struct a { uint8 x; }\n#define B 1", "line 2: expected ;, got define"),
|
|
82
|
+
("struct a { uint8 x; }\n#[nocompile]\nstruct b { uint8 x; };", "line 2: expected ;, got nocompile"),
|
|
83
|
+
("union a { uint8 x; }\ntypedef uint8 b;", "line 2: expected ;, got typedef"),
|
|
84
|
+
("struct a { uint8 x; } b\ntypedef uint8 c;", "line 2: expected ;, got typedef"),
|
|
85
|
+
("struct a { uint8 x; }", "line 1: expected ;, got end of input"),
|
|
86
|
+
("struct a { uint8 x; } b", "line 1: expected ;, got end of input"),
|
|
87
|
+
# Invalid variable declarations
|
|
88
|
+
("struct a { uint8 x; } b c;", "line 1: expected ;, got c"),
|
|
89
|
+
("struct a { uint8 x; } 1;", "line 1: expected ;, got 1"),
|
|
90
|
+
("struct a { uint8 x; } , b;", "line 1: expected ;, got ,"),
|
|
91
|
+
("struct a { uint8 x; } b,;", "line 1: expected one of .*, got ;"),
|
|
92
|
+
("struct a { uint8 x; } *;", "line 1: expected one of .*, got ;"),
|
|
93
|
+
("struct a { uint8 x; } b[2;", "line 1: expected ], got end of input"),
|
|
94
|
+
("struct a { uint8 x; } b : 4;", "line 1: variables cannot have bitfields"),
|
|
95
|
+
],
|
|
96
|
+
)
|
|
97
|
+
def test_struct_invalid_variables(cs: cstruct, cdef: str, match: str) -> None:
|
|
98
|
+
"""Test that nothing other than variable declarations and a semicolon can follow a struct or union definition."""
|
|
99
|
+
with pytest.raises(ParserError, match=match):
|
|
100
|
+
cs.load(cdef)
|
|
101
|
+
|
|
102
|
+
|
|
44
103
|
def test_nested_structs(cs: cstruct, compiled: bool) -> None:
|
|
45
104
|
"""Test parsing of nested structs, including anonymous ones."""
|
|
46
105
|
cdef = """
|
|
@@ -248,14 +307,18 @@ def test_define(cs: cstruct) -> None:
|
|
|
248
307
|
#define NULLSTR "ADCRYPT\00"
|
|
249
308
|
#define NULLBYTES b"ADCRYPT\00"
|
|
250
309
|
#define ARBITRARYBYTES b"\x00\x01\x02"
|
|
251
|
-
#define MULTILINE (1 +
|
|
252
|
-
2 +
|
|
310
|
+
#define MULTILINE (1 + \\
|
|
311
|
+
2 + \\
|
|
253
312
|
3)
|
|
254
|
-
#define QUOTES "
|
|
313
|
+
#define QUOTES "'\\"a'b\\""
|
|
255
314
|
#define ESCAPE "\\'\\"a'b\\"\\n"
|
|
256
315
|
#define BYTES_ESCAPE b"`\\n"
|
|
257
316
|
#define FUNC(x) ( x == 0 )
|
|
258
317
|
#define TERNARY(x) ( x ? 1 : 0 )
|
|
318
|
+
#define STR_CONCAT "say " STR
|
|
319
|
+
#define BYTES_CONCAT BYTES b"!"
|
|
320
|
+
#define STR_AND_NUMBER "a" CONST
|
|
321
|
+
#define RAW_AND_RAW RAW RAW
|
|
259
322
|
"""
|
|
260
323
|
cs.load(cdef)
|
|
261
324
|
|
|
@@ -275,6 +338,16 @@ def test_define(cs: cstruct) -> None:
|
|
|
275
338
|
# We don't evaluate function-like macros yet, so they should be stored as their raw string representation
|
|
276
339
|
assert cs.consts["FUNC"] == "(x) ( x == 0 )"
|
|
277
340
|
assert cs.consts["TERNARY"] == "(x) ( x ? 1 : 0 )"
|
|
341
|
+
# Adjacent strings and constants that are strings are concatenated like in C
|
|
342
|
+
assert cs.consts["STR_CONCAT"] == "say hello"
|
|
343
|
+
assert cs.consts["BYTES_CONCAT"] == b"world!"
|
|
344
|
+
# A constant that isn't a string can't be concatenated, so the value is kept as it is
|
|
345
|
+
assert cs.consts["STR_AND_NUMBER"] == '"a" CONST'
|
|
346
|
+
# Without a string in the value we don't know whether these are strings, they could also just be raw values
|
|
347
|
+
assert cs.consts["RAW_AND_RAW"] == "RAW RAW"
|
|
348
|
+
|
|
349
|
+
with pytest.raises(ParserError, match="line 1: cannot concatenate strings and bytes"):
|
|
350
|
+
cs.load('#define MIXED "a" b"b"')
|
|
278
351
|
|
|
279
352
|
|
|
280
353
|
def test_define_flag_value(cs: cstruct) -> None:
|
|
@@ -427,19 +500,6 @@ def test_conditional_parsing_error(cs: cstruct) -> None:
|
|
|
427
500
|
cs.load(cdef)
|
|
428
501
|
|
|
429
502
|
|
|
430
|
-
def test_multiline_define(cs: cstruct) -> None:
|
|
431
|
-
"""Test parsing of multi-line ``#define`` directives."""
|
|
432
|
-
cdef = """
|
|
433
|
-
#define MULTILINE_DEF (1 + \\
|
|
434
|
-
2 + \\
|
|
435
|
-
3)
|
|
436
|
-
"""
|
|
437
|
-
cs.load(cdef)
|
|
438
|
-
|
|
439
|
-
assert "MULTILINE_DEF" in cs.consts
|
|
440
|
-
assert cs.consts["MULTILINE_DEF"] == 6
|
|
441
|
-
|
|
442
|
-
|
|
443
503
|
def test_multiple_declarators(cs: cstruct) -> None:
|
|
444
504
|
"""Test parsing of multiple declarators in a single struct field declaration."""
|
|
445
505
|
cdef = """
|
|
@@ -595,3 +655,51 @@ def test_error_context(cs: cstruct) -> None:
|
|
|
595
655
|
3: 69
|
|
596
656
|
""".rstrip()
|
|
597
657
|
)
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
@pytest.mark.parametrize(
|
|
661
|
+
("value", "expected"),
|
|
662
|
+
[
|
|
663
|
+
# A comment is replaced by a space, after which the value continues
|
|
664
|
+
("data /* c */", "data"),
|
|
665
|
+
("a/* c */b", "a b"),
|
|
666
|
+
("data /* c\n#define C 3\n d */", "data"),
|
|
667
|
+
("data // c", "data"),
|
|
668
|
+
("1 // it's", 1),
|
|
669
|
+
("http://x", "http:"),
|
|
670
|
+
("1 /* c\n d */ + 2", 3),
|
|
671
|
+
("/* c\n d */ 1", 1),
|
|
672
|
+
("/* c */", ""),
|
|
673
|
+
# Comments after and in strings
|
|
674
|
+
('"a" /* c\n d */', "a"),
|
|
675
|
+
('"a" // it\'s "b"', "a"),
|
|
676
|
+
("'b' /* c\n d */", "b"),
|
|
677
|
+
('b"c" // c', b"c"),
|
|
678
|
+
("b'd' /* c */", b"d"),
|
|
679
|
+
('"\\"f\\"" // c', '"f"'),
|
|
680
|
+
('"it\'s /* c */ // d"', "it's /* c */ // d"),
|
|
681
|
+
# Escapes and spaces in strings
|
|
682
|
+
('"a\\\\"', "a\\"),
|
|
683
|
+
('"a b"', "a b"),
|
|
684
|
+
('b"\\xff"', b"\xff"),
|
|
685
|
+
# Line continuations are joined first
|
|
686
|
+
('"a\\\nb"', "ab"),
|
|
687
|
+
("1 /* c */ + \\\n 2", 3),
|
|
688
|
+
('"a" // c \\\n "b"', "a"),
|
|
689
|
+
("1 + \\\r\n 2\r", 3),
|
|
690
|
+
# Adjacent strings are concatenated
|
|
691
|
+
('"bar" /* c */ \\\n "zomg"', "barzomg"),
|
|
692
|
+
# CRLF line endings
|
|
693
|
+
('"a"\r', "a"),
|
|
694
|
+
("data\r", "data"),
|
|
695
|
+
# Anything that isn't only strings is kept as it is, like other macro-like constants
|
|
696
|
+
('"a" + 1', '"a" + 1'),
|
|
697
|
+
],
|
|
698
|
+
)
|
|
699
|
+
def test_define_value(cs: cstruct, value: str, expected: int | str | bytes) -> None:
|
|
700
|
+
"""Test that the value of a ``#define`` follows the rules of C for comments, continuations and strings."""
|
|
701
|
+
cs.load(f"#define A {value}\n#define B 2\n")
|
|
702
|
+
|
|
703
|
+
assert cs.consts["A"] == expected
|
|
704
|
+
assert cs.consts["B"] == 2
|
|
705
|
+
assert "C" not in cs.consts
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/requires.txt
RENAMED
|
File without changes
|
{dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/scm_file_list.json
RENAMED
|
File without changes
|
{dissect_cstruct-5.0.dev14 → dissect_cstruct-5.0.dev15}/dissect.cstruct.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|