A simple package that provide limited supports of goto
keyword like C/C++.
Important
This package is only tested locally on Windows amd64
on CPython 3.12.2. Use this package with caution!
from goto import GOTO, LABEL, with_goto
@with_goto
def example_01() -> None:
print("Hello, ")
GOTO.label_01
print("Skipped!")
LABEL.label_01
print("world!")
if __name__ == "__main__":
example_01()
- Disassemble the raw function.
- Iterate over the disassembled contents.
- If there‘re
LOAD_GLOBAL (GOTO)
,LOAD_ATTR
andPOP_TOP
in continuing 3 lines, note down the byte position and the label, it's where we jump from. - Hack full byte range of above 3 operations to
NOP
, except the first 2 bytes ofLOAD_GLOBAL
. - If there're
LOAD_GLOBAL (LABEL)
,LOAD_ATTR
andPOP_TOP
in continuing 3 lines, note down the byte position the next line ofPOP_TOP
and the label, it's where we jump to. - Hack full byte range of above 3 operations to
NOP
. - Iterate over all
(label, from_, to)
pair, check iffrom_
is smaller thanto
, to determine there should beJUMP_FORWARD
orJUMP_BACKWARD
. - Hack the
from_
byte position toJUMP_FORWARD
orJUMP_BACKWARD
according to step 7. Iffrom_
is far away fromto
(jump more than 256 operations), add necessaryEXTENDED_ARG
.
Source code
from dis import dis
from goto import GOTO, LABEL, with_goto
@with_goto
def example_01() -> None:
print("Hello, ")
GOTO.label_01
print("Skipped!")
LABEL.label_01
print("world!")
if __name__ == "__main__":
dis(example_01)
Raw disassemble content
7 0 RESUME 0
8 2 LOAD_GLOBAL 1 (NULL + print)
12 LOAD_CONST 1 ('Hello, ')
14 CALL 1
22 POP_TOP
9 24 LOAD_GLOBAL 2 (GOTO)
34 LOAD_ATTR 4 (label_01)
54 POP_TOP
10 56 LOAD_GLOBAL 1 (NULL + print)
66 LOAD_CONST 2 ('Skipped!')
68 CALL 1
76 POP_TOP
11 78 LOAD_GLOBAL 6 (LABEL)
88 LOAD_ATTR 4 (label_01)
108 POP_TOP
12 110 LOAD_GLOBAL 1 (NULL + print)
120 LOAD_CONST 3 ('world!')
122 CALL 1
130 POP_TOP
132 RETURN_CONST 0 (None)
Hacked disassemble content
6 0 RESUME 0
8 2 LOAD_GLOBAL 1 (NULL + print)
12 LOAD_CONST 1 ('Hello, ')
14 CALL 1
22 POP_TOP
9 24 JUMP_FORWARD 42 (to 110)
26 NOP
28 NOP
30 NOP
32 NOP
34 NOP
36 NOP
38 NOP
40 NOP
42 NOP
44 NOP
46 NOP
48 NOP
50 NOP
52 NOP
54 NOP
10 56 LOAD_GLOBAL 1 (NULL + print)
66 LOAD_CONST 2 ('Skipped!')
68 CALL 1
76 POP_TOP
11 78 NOP
80 NOP
82 NOP
84 NOP
86 NOP
88 NOP
90 NOP
92 NOP
94 NOP
96 NOP
98 NOP
100 NOP
102 NOP
104 NOP
106 NOP
108 NOP
12 >> 110 LOAD_GLOBAL 1 (NULL + print)
120 LOAD_CONST 3 ('world!')
122 CALL 1
130 POP_TOP
132 RETURN_CONST 0 (None)
- It's able to jump out from nested loop (
for
orwhile
), but you can't jump into them. - There're 3
EXTENDED_ARG
operation before other operations at most, means that the jumping gap is no more than 4G. You can't jump such that HUGE distance. (This limitation has no chance to reach for human-written source code) - You can't jump to somewhere if they are "dead code" (Python intepreter will remove them directly), such as ...
- Jump out from a
while True
loop, as the following snippets is unreachable
- Jump out from a