Add dict and json output to dumpstruct #156
Conversation
6c41df0 to
6d68e98
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #156 +/- ##
=====================================
Coverage 0.00% 0.00%
=====================================
Files 22 23 +1
Lines 2773 2853 +80
=====================================
- Misses 2773 2853 +80
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Merging this PR will improve performance by 11.34%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | test_benchmark_getattr_types |
12.1 µs | 10.8 µs | +12.19% |
| ⚡ | test_benchmark_getattr_constants |
11.9 µs | 10.8 µs | +10.49% |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing dumpstruct-json (341f38f) with main (8fd1506)
79dbeb3 to
42ce45d
Compare
d9cc121 to
d4d4709
Compare
There was a problem hiding this comment.
Pull request overview
Refactors the structure-dumping utilities by moving hexdump/dumpstruct into a new dissect.cstruct.dump module and extending dumpstruct to support machine-consumable outputs (dict + JSON), while updating tests and examples to match the new API surface.
Changes:
- Introduce
dissect.cstruct.dump.dumpstruct(..., output="dict"|"json")and refactor string/print rendering to be driven from the same collected representation. - Move
hexdump/dumpstructout ofdissect.cstruct.utiland re-export them fromdissect.cstruct.__init__. - Restructure and expand tests (new
tests/test_dump.py) and modernize example scripts (PEP 723 headers, typing, safer file handling).
Reviewed changes
Copilot reviewed 24 out of 25 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/util.py | Adds shared test helpers (verify_compiled, path helper). |
| tests/test_utils.py | Removes old combined util/dump tests (split into focused modules). |
| tests/test_util.py | Keeps pack/unpack/swap tests focused on dissect.cstruct.util. |
| tests/test_dump.py | Adds comprehensive tests for hexdump and new dumpstruct dict/JSON outputs and coloring rules. |
| tests/test_types_void.py | Updates helper import path for verify_compiled. |
| tests/test_types_union.py | Updates helper import path for verify_compiled. |
| tests/test_types_structure.py | Updates helper import path for verify_compiled. |
| tests/test_types_pointer.py | Updates helper import path for verify_compiled. |
| tests/test_types_packed.py | Updates helper import path for verify_compiled. |
| tests/test_types_int.py | Updates helper import path for verify_compiled. |
| tests/test_types_flag.py | Updates helper import path for verify_compiled. |
| tests/test_types_enum.py | Updates helper import path for verify_compiled. |
| tests/test_types_base.py | Updates helper import path for verify_compiled. |
| tests/test_parser.py | Updates helper import path for verify_compiled. |
| tests/test_bitfield.py | Updates helper import path for verify_compiled. |
| tests/test_basic.py | Updates helper import path for verify_compiled. |
| tests/test_align.py | Updates helper import path for verify_compiled. |
| examples/secdesc.py | Modernizes example (PEP 723 header, typing, minor cleanups). |
| examples/protobuf.py | Adds PEP 723 script metadata header. |
| examples/pe.py | Modernizes example (PEP 723 header, Path.open, style fixes). |
| examples/mirai.py | Modernizes example (PEP 723 header, typing/import cleanup). |
| examples/disk.py | Modernizes example (PEP 723 header, typing, Path.open, recursion cleanup). |
| dissect/cstruct/util.py | Removes dump-related code, leaving packing/unpacking utilities. |
| dissect/cstruct/dump.py | New dump/hexdump implementation including dict/JSON support. |
| dissect/cstruct/init.py | Re-exports dumpstruct/hexdump from the new dump module. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
a7e44c7 to
22a8216
Compare
Miauwkeru
left a comment
There was a problem hiding this comment.
Some initial things I saw during review, will need to check dump.py a bit more thorough
And I have a question. When I was testing out dumpstruct I noticed something in the output with the following structure:
struct level_1 {
struct level_2 {
struct level_3_1 {
uint8 r;
uint8 g;
uint8 b;
};
struct level_3_2 {
uint8 medicine[3];
};
};
};I expect dumpstruct to return
struct level_1:
- level_2:
- level_3_1:
- r: 0xff
- g: 0x80
- b: 0x40
- level_3_2:
- medicine[3]: [255, 128, 64]
However, what I get is:
struct level_1:
- r: 0xff
- g: 0x80
- b: 0x40
- medicine[3]: [255, 128, 64]
Is this intended or was this some specific behaviour of c like structs that I was not aware of?
22a8216 to
e36fc74
Compare
|
@Miauwkeru none of your nested structures have a field/member name. In C, this would result in a structure with 0 members. You could even argue that cstruct outputs anything at all could be a bug in itself. |
5d578cf to
cfcfac3
Compare
cfcfac3 to
341f38f
Compare
|
|
||
| def hexdump( | ||
| data: bytes, | ||
| *, | ||
| palette: Palette | None = None, | ||
| offset: int = 0, | ||
| prefix: str = "", | ||
| output: str = "print", | ||
| pretty: bool | None = None, | ||
| autoskip: bool = False, | ||
| ) -> Iterator[str] | str | None: |
There was a problem hiding this comment.
| def hexdump( | |
| data: bytes, | |
| *, | |
| palette: Palette | None = None, | |
| offset: int = 0, | |
| prefix: str = "", | |
| output: str = "print", | |
| pretty: bool | None = None, | |
| autoskip: bool = False, | |
| ) -> Iterator[str] | str | None: | |
| AllowedHexdumpOutput: TypeAlias = Literal["print", "generator", "string"] | |
| def hexdump( | |
| data: bytes, | |
| *, | |
| palette: Palette | None = None, | |
| offset: int = 0, | |
| prefix: str = "", | |
| output: AllowedHexdumpOutput = "print", | |
| pretty: bool | None = None, | |
| autoskip: bool = False, | |
| ) -> Iterator[str] | str | None: |
Is it an idea to do something like this for the method signature? And then ofc the same for dumpstruct
| field_type = field_type.type | ||
|
|
||
| if getattr(field_type, "__anonymous__", False): | ||
| type_name = "union" if issubclass(field_type, Union) else "struct" |
There was a problem hiding this comment.
This might cause some confusion when you are declaring an array of anonymous structs/unions. every field declared that way would have the type struct or union but no fields section. Where the structure itself is still within types by its anonymous name.
This makes it harder to understand which field belongs to which specific struct.
As an example, this struct:
struct a {
char magic[4];
};
struct d {
char footer[4];
};
struct structure {
struct {
a yyy;
} b[4];
struct {
d xxx;
} c[2];
};generates the following overview with output=json:
{
"bytes": "000000000000000000000000000000000000000000000000",
"root": "structure",
"types": {
"structure": {
"kind": "struct",
"fields": [
{
"name": "b[4]",
"type": "struct"
},
{
"name": "c[2]",
"type": "struct"
}
]
},
"a": {
"kind": "struct",
"fields": [
{
"name": "magic[4]",
"type": "char"
}
]
},
"__anonymous_0__": {
"kind": "struct",
"fields": [
{
"name": "yyy",
"type": "a"
}
]
},
"d": {
"kind": "struct",
"fields": [
{
"name": "footer[4]",
"type": "char"
}
]
},
"__anonymous_1__": {
"kind": "struct",
"fields": [
{
"name": "xxx",
"type": "d"
}
]
}
},
"sizes": {
"b[4]": 16,
"c[2]": 8
},
"values": {
"b[4]": "[<__anonymous_0__ yyy=<a magic=b'\\x00\\x00\\x00\\x00'>>,\n <__anonymous_0__ yyy=<a magic=b'\\x00\\x00\\x00\\x00'>>,\n <__anonymous_0__ yyy=<a magic=b'\\x00\\x00\\x00\\x00'>>,\n <__anonymous_0__ yyy=<a magic=b'\\x00\\x00\\x00\\x00'>>]",
"c[2]": "[<__anonymous_1__ xxx=<d footer=b'\\x00\\x00\\x00\\x00'>>,\n <__anonymous_1__ xxx=<d footer=b'\\x00\\x00\\x00\\x00'>>]"
}
}Here it isn't clear (except by looking at the values) which types populate b[4] and c[2] in structure
Refactors dumpstruct to add the ability to generate dict and JSON information about a structure. The rendering is also updated to use this dictionary representation.
Also some small maintenance cleanup on the example scripts.
Disclaimer: this change has a fairly high vibe ratio, but it ends up handling quite a few dumpstruct improvements.