Consider the following code:
try:
helloworld()
except:
failure()
and its disassembly (this is in Python 2.7):
1 0 SETUP_EXCEPT 11 (to 14)
2 3 LOAD_NAME 0 (helloworld)
6 CALL_FUNCTION 0
9 POP_TOP
10 POP_BLOCK
11 JUMP_FORWARD 14 (to 28)
3 >> 14 POP_TOP
15 POP_TOP
16 POP_TOP
4 17 LOAD_NAME 1 (failure)
20 CALL_FUNCTION 0
23 POP_TOP
24 JUMP_FORWARD 1 (to 28)
27 END_FINALLY
>> 28 LOAD_CONST 0 (None)
31 RETURN_VALUE
Assuming that helloworld() raises an exception, the code follows from address 14 onwards. Because this except handler is generic, it makes sense that three POP_TOPs follow, and the failure() function call. However, afterwards, there is a 24 JUMP_FORWARD which "jumps over" 27 END_FINALLY, so that it doesn't get executed. What's its purpose here?
I noticed similar behaviour in versions 3.5, 3.6, 3.7 and 3.8. In 3.9 it seems like it's renamed to RERAISE: https://godbolt.org/z/YbeqPf3nx
Some context: After simplifying an obfuscated pyc and a lot of debugging I've found that such structure breaks uncompyle6
It resumes propagation of an exception when a
finallyblock ends, or when there is nofinallyblock and none of theexceptblocks match. It also handles resuming areturnorcontinuethat was suspended by afinallyblock.But you don't have a
finally, and you've got a blanketexcept, which always matches, so theEND_FINALLYnever runs. It could be eliminated, but there's no handling in the bytecode compiler to do so.