Debugging Construct¶
Programming data structures in Construct is much easier than writing the equivalent procedural code, both in terms of RAD and correctness. However, sometimes things don’t behave the way you expect them to. Yep, a bug.
Most end-user bugs originate from handling the context wrong. Sometimes you forget what nesting level you are at, or you move things around without taking into account the nesting, thus breaking context-based expressions. The two utilities described below should help you out.
Probe¶
The Probe simply dumps information to the screen. It will help you inspect the context tree, the stream, and partially constructed objects, so you can understand your problem better. It has the same interface as any other field, and you can just stick it into a Struct, near the place you wish to inspect. Do note that the printout happens during the construction, before the final object is ready.
>>> Struct("count"/Byte, "items"/Byte[this.count], Probe()).parse(b"\x05abcde")
================================================================================
Probe <unnamed 3>
path is parsing, func is None
EOF reached
Container:
count = 5
items = ListContainer:
97
98
99
100
101
================================================================================
Container(count=5)(items=[97, 98, 99, 100, 101])
>>> (Byte >> Probe()).parse(b"?")
================================================================================
Probe <unnamed 1>
path is parsing, func is None
EOF reached
Container:
0 = 63
================================================================================
[63, None]
There is also ProbeInto looks inside the context and extracts a part of it using a lambda instead of printing the entire context.
>>> st = "junk"/RepeatUntil(obj_ == 0,Byte) + "num"/Byte + Probe()
>>> st.parse(b"xcnzxmbjskahuiwerhquiehnsdjk\x00\xff")
================================================================================
Probe <unnamed 5>
path is parsing, func is None
EOF reached
Container:
junk = ListContainer:
120
99
110
122
120
109
98
106
115
107
97
104
117
105
119
101
114
104
113
117
105
101
104
110
115
100
106
107
0
num = 255
================================================================================
Container(junk=[120, 99, 110, 122, 120, 109, 98, 106, 115, 107, 97, 104, 117, 105, 119, 101, 114, 104, 113, 117, 105, 101, 104, 110, 115, 100, 106, 107, 0])(num=255)
>>> st = "junk"/RepeatUntil(obj_ == 0,Byte) + "num"/Byte + ProbeInto(this.num)
>>> st.parse(b"xcnzxmbjskahuiwerhquiehnsdjk\x00\xff")
================================================================================
Probe <unnamed 6>
path is parsing, func is this.num
EOF reached
255
================================================================================
Container(junk=[120, 99, 110, 122, 120, 109, 98, 106, 115, 107, 97, 104, 117, 105, 119, 101, 114, 104, 113, 117, 105, 101, 104, 110, 115, 100, 106, 107, 0])(num=255)
Debugger¶
The Debugger is a pdb-based full python debugger. Unlike Probe, Debugger is a subconstruct (it wraps an inner construct), so you simply put it around the problematic construct. If no exception occurs, the return value is passed right through. Otherwise, an interactive debugger pops, letting you tweak around.
When an exception occurs while parsing, you can go up (using u) to the level of the debugger and set self.retval to the desired return value. This allows you to hot-fix the error. Then use q to quit the debugger prompt and resume normal execution with the fixed value. However, if you don’t set self.retval, the exception will propagate up.
>>> Debugger(Byte[3]).build([])
================================================================================
Debugging exception of <Range: None>:
File "/home/arkadiusz/Dokumenty/GitHub/construct/construct/debug.py", line 116, in _build
obj.stack.append(a)
File "/home/arkadiusz/Dokumenty/GitHub/construct/construct/core.py", line 1069, in _build
raise RangeError("expected from %d to %d elements, found %d" % (self.min, self.max, len(obj)))
construct.core.RangeError: expected from 3 to 3 elements, found 0
> /home/arkadiusz/Dokumenty/GitHub/construct/construct/core.py(1069)_build()
-> raise RangeError("expected from %d to %d elements, found %d" % (self.min, self.max, len(obj)))
(Pdb)
================================================================================
>>> format = Struct(
... "spam" / Debugger(Enum(Byte, A=1,B=2,C=3)),
... )
>>> format.parse(b"\xff")
================================================================================
Debugging exception of <Mapping: None>:
File "/home/arkadiusz/Dokumenty/GitHub/construct/construct/core.py", line 2578, in _decode
return self.decoding[obj]
KeyError: 255
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/arkadiusz/Dokumenty/GitHub/construct/construct/debug.py", line 127, in _parse
return self.subcon._parse(stream, context)
File "/home/arkadiusz/Dokumenty/GitHub/construct/construct/core.py", line 308, in _parse
return self._decode(self.subcon._parse(stream, context), context)
File "/home/arkadiusz/Dokumenty/GitHub/construct/construct/core.py", line 2583, in _decode
raise MappingError("no decoding mapping for %r" % (obj,))
construct.core.MappingError: no decoding mapping for 255
(you can set the value of 'self.retval', which will be returned)
> /home/arkadiusz/Dokumenty/GitHub/construct/construct/core.py(2583)_decode()
-> raise MappingError("no decoding mapping for %r" % (obj,))
(Pdb) self.retval = "???"
(Pdb) q
Error¶
Raises an exception when triggered by parse or build. Can be used as a sentinel that blows a whistle when a conditional branch goes the wrong way, or to raise an error explicitly the declarative way.
>>> d = "x"/Int8sb >> IfThenElse(this.x > 0, Int8sb, Error)
>>> d.parse(b"\xff\x05")
construct.core.ExplicitError: Error field was activated during parsing