Skip to content

Commit 28b4ee1

Browse files
committed
Fix inline exception handler deopt stack mismatch
The JIT's inline exception handler skips PUSH_EXC_INFO as an optimization, but handler body bytecodes (SWAP, POP_EXCEPT) still expect the previous exception on the stack. When the handler body hit an unsupported opcode and deopted, the interpreter found garbage where prev_exc should be. POP_EXCEPT then wrote garbage into tstate->exc_info->exc_value, corrupting the exception context chain. Fix: push Py_None as a prev_exc placeholder before handler body bytecodes. This ensures all deopts have a valid stack layout. Also handle SWAP inline and make POP_EXCEPT pop the placeholder. Fixes both emitInlineExceptionMatch and emitCallExceptionHandler.
1 parent d98c7f9 commit 28b4ee1

1 file changed

Lines changed: 35 additions & 1 deletion

File tree

Python/jit/hir/builder.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,19 +713,41 @@ void HIRBuilder::emitInlineExceptionMatch(
713713
TranslationContext match_tc{match_block, exc_tc.frame};
714714
match_tc.frame.cur_instr_offs = info.except_body;
715715

716+
// Push Py_None as the "previous exception" placeholder.
717+
// In CPython's interpreter, PUSH_EXC_INFO pushes the old
718+
// exc_info->exc_value onto the stack so POP_EXCEPT can restore
719+
// it. The JIT skips PUSH_EXC_INFO but the handler body bytecodes
720+
// (SWAP, POP_EXCEPT) still expect prev_exc on the stack. If the
721+
// handler body hits an unsupported opcode and deopts, the
722+
// interpreter must find a valid prev_exc here — otherwise
723+
// POP_EXCEPT writes a garbage pointer into exc_info->exc_value,
724+
// corrupting the exception context chain.
725+
Register* prev_exc_reg = temps_.AllocateStack();
726+
match_tc.emit<LoadConst>(prev_exc_reg, TNoneType);
727+
match_tc.frame.stack.push(prev_exc_reg);
728+
716729
BytecodeInstruction ebc{code_, info.except_body};
717730
bool emitted_terminator = false;
718731

719732
while (!emitted_terminator) {
720733
switch (ebc.opcode()) {
721734
case POP_EXCEPT:
722-
// No-op for B2: we never pushed exc_info in the JIT.
735+
// Pop the prev_exc placeholder we pushed above.
736+
// In CPython, POP_EXCEPT pops the saved exception and
737+
// restores it to exc_info->exc_value. Our placeholder is
738+
// Py_None, which is correct: there was no active exception
739+
// before the JIT's inline handler.
740+
match_tc.frame.stack.pop();
723741
break;
724742

725743
case POP_TOP:
726744
match_tc.frame.stack.pop();
727745
break;
728746

747+
case SWAP:
748+
emitSwap(match_tc, ebc.oparg());
749+
break;
750+
729751
case LOAD_FAST:
730752
case LOAD_FAST_CHECK:
731753
case LOAD_FAST_AND_CLEAR:
@@ -872,18 +894,30 @@ void HIRBuilder::emitCallExceptionHandler(
872894
TranslationContext match_tc{match_block, exc_tc.frame};
873895
match_tc.frame.cur_instr_offs = info.except_body;
874896

897+
// Push Py_None as "previous exception" placeholder — see comment
898+
// in the first emitInlineExceptionMatch for full rationale.
899+
Register* prev_exc_reg = temps_.AllocateStack();
900+
match_tc.emit<LoadConst>(prev_exc_reg, TNoneType);
901+
match_tc.frame.stack.push(prev_exc_reg);
902+
875903
BytecodeInstruction ebc{code_, info.except_body};
876904
bool emitted_terminator = false;
877905

878906
while (!emitted_terminator) {
879907
switch (ebc.opcode()) {
880908
case POP_EXCEPT:
909+
// Pop the prev_exc placeholder.
910+
match_tc.frame.stack.pop();
881911
break;
882912

883913
case POP_TOP:
884914
match_tc.frame.stack.pop();
885915
break;
886916

917+
case SWAP:
918+
emitSwap(match_tc, ebc.oparg());
919+
break;
920+
887921
case LOAD_FAST:
888922
case LOAD_FAST_CHECK:
889923
case LOAD_FAST_AND_CLEAR:

0 commit comments

Comments
 (0)