From 0b1f7cf07020351c3dd386e700957be7130c3e90 Mon Sep 17 00:00:00 2001 From: daiche330-png Date: Sun, 5 Apr 2026 22:13:27 +0900 Subject: [PATCH 1/2] Handle Python 3.11 exception opcodes in AST builder --- ASTree.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ASTree.cpp b/ASTree.cpp index 6635808e1..efa2d9aaa 100644 --- a/ASTree.cpp +++ b/ASTree.cpp @@ -1689,6 +1689,19 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) case Pyc::POP_EXCEPT: /* Do nothing. */ break; + case Pyc::PUSH_EXC_INFO: + /* Python 3.11+: pushes exception info tuple. We ignore here to keep decompilation going. */ + break; + case Pyc::CHECK_EXC_MATCH: + { + /* Python 3.11+: compares exception against handler type. */ + PycRef right = stack.top(); + stack.pop(); + PycRef left = stack.top(); + stack.pop(); + stack.push(new ASTCompare(left, right, ASTCompare::CMP_EXCEPTION)); + } + break; case Pyc::END_FOR: { stack.pop(); @@ -1830,6 +1843,14 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) } } break; + case Pyc::RERAISE: + case Pyc::RERAISE_A: + { + /* Re-raise current exception; treat like 'raise' without args. */ + ASTRaise::param_t paramList; + curblock->append(new ASTRaise(paramList)); + } + break; case Pyc::RETURN_VALUE: case Pyc::INSTRUMENTED_RETURN_VALUE_A: { @@ -1920,6 +1941,9 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) curblock = blocks.top(); } break; + case Pyc::BEFORE_WITH: + /* Python 3.11: setup for with block; ignore. */ + break; case Pyc::WITH_CLEANUP: case Pyc::WITH_CLEANUP_START: { From 413fbd83c968495ba9c8aad03c2cb5649412efdc Mon Sep 17 00:00:00 2001 From: daiche330-png Date: Mon, 6 Apr 2026 09:30:16 +0900 Subject: [PATCH 2/2] Support Python 3.11 exception table try/except flow --- ASTree.cpp | 100 ++++++++++++++++-- .../test_exception_match_py311.3.11.pyc | Bin 0 -> 479 bytes tests/input/test_exception_match_py311.py | 7 ++ .../tokenized/test_exception_match_py311.txt | 14 +++ 4 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 tests/compiled/test_exception_match_py311.3.11.pyc create mode 100644 tests/input/test_exception_match_py311.py create mode 100644 tests/tokenized/test_exception_match_py311.txt diff --git a/ASTree.cpp b/ASTree.cpp index efa2d9aaa..f837152f9 100644 --- a/ASTree.cpp +++ b/ASTree.cpp @@ -93,6 +93,12 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) bool else_pop = false; bool need_try = false; bool variable_annotations = false; + std::vector exception_entries; + size_t next_exception_entry = 0; + + if (mod->verCompare(3, 11) >= 0) { + exception_entries = code->exceptionTableEntries(); + } while (!source.atEof()) { #if defined(BLOCK_DEBUG) || defined(STACK_DEBUG) @@ -108,6 +114,84 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) fprintf(stderr, "\n"); #endif + while (next_exception_entry < exception_entries.size() + && exception_entries[next_exception_entry].start_offset < pos) { + next_exception_entry++; + } + + if (next_exception_entry < exception_entries.size()) { + const auto& entry = exception_entries[next_exception_entry]; + if (entry.start_offset == pos + && entry.stack_depth == 0 + && !entry.push_lasti) { + if (curblock->blktype() == ASTBlock::BLK_CONTAINER) { + curblock.cast()->setExcept(entry.target); + } else { + PycRef next = new ASTContainerBlock(0, entry.target); + blocks.push(next.cast()); + curblock = blocks.top(); + } + + stack_hist.push(stack); + PycRef tryblock = new ASTBlock(ASTBlock::BLK_TRY, entry.target, true); + blocks.push(tryblock.cast()); + curblock = blocks.top(); + next_exception_entry++; + } + } + + if (curblock->blktype() == ASTBlock::BLK_TRY + && curblock->end() == pos + && blocks.size() > 1) { + PycRef prev = curblock; + blocks.pop(); + curblock = blocks.top(); + + if (curblock->blktype() == ASTBlock::BLK_CONTAINER + && curblock.cast()->hasExcept()) { + if (!stack_hist.empty()) { + stack = stack_hist.top(); + stack_hist.pop(); + } + + curblock->append(prev.cast()); + stack_hist.push(stack); + + PycRef except = new ASTCondBlock(ASTBlock::BLK_EXCEPT, 0, NULL, false); + except->init(); + blocks.push(except); + curblock = blocks.top(); + } else { + blocks.push(prev); + curblock = prev; + } + } + + if (curblock->blktype() == ASTBlock::BLK_EXCEPT + && curblock->end() == pos + && blocks.size() > 1) { + PycRef prev = curblock; + blocks.pop(); + curblock = blocks.top(); + + if (!stack_hist.empty()) { + stack = stack_hist.top(); + stack_hist.pop(); + } + + if (prev->size() != 0) { + curblock->append(prev.cast()); + } + + if (curblock->blktype() == ASTBlock::BLK_CONTAINER + && !curblock.cast()->hasFinally()) { + PycRef cont = curblock; + blocks.pop(); + curblock = blocks.top(); + curblock->append(cont.cast()); + } + } + curpos = pos; bc_next(source, mod, opcode, operand, pos); @@ -1093,15 +1177,17 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) if (cond.type() == ASTNode::NODE_COMPARE && cond.cast()->op() == ASTCompare::CMP_EXCEPTION) { + int except_end = offs; if (curblock->blktype() == ASTBlock::BLK_EXCEPT && curblock.cast()->cond() == NULL) { + except_end = curblock->end(); blocks.pop(); curblock = blocks.top(); stack_hist.pop(); } - ifblk = new ASTCondBlock(ASTBlock::BLK_EXCEPT, offs, cond.cast()->right(), false); + ifblk = new ASTCondBlock(ASTBlock::BLK_EXCEPT, except_end, cond.cast()->right(), false); } else if (curblock->blktype() == ASTBlock::BLK_ELSE && curblock->size() == 0) { /* Collapse into elif statement */ @@ -1370,8 +1456,10 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) } else if (prev->blktype() == ASTBlock::BLK_TRY && prev->end() < pos+offs) { /* Need to add an except/finally block */ - stack = stack_hist.top(); - stack.pop(); + if (!stack_hist.empty()) { + stack = stack_hist.top(); + stack_hist.pop(); + } if (blocks.top()->blktype() == ASTBlock::BLK_CONTAINER) { PycRef cont = blocks.top().cast(); @@ -1845,11 +1933,7 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) break; case Pyc::RERAISE: case Pyc::RERAISE_A: - { - /* Re-raise current exception; treat like 'raise' without args. */ - ASTRaise::param_t paramList; - curblock->append(new ASTRaise(paramList)); - } + /* Python 3.11 cleanup opcode. */ break; case Pyc::RETURN_VALUE: case Pyc::INSTRUMENTED_RETURN_VALUE_A: diff --git a/tests/compiled/test_exception_match_py311.3.11.pyc b/tests/compiled/test_exception_match_py311.3.11.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9321f02acbeb7db13fec1e298bd2ef6867014c1b GIT binary patch literal 479 zcmZ3^%ge<81WErcWzGQ7k3k$5V1zP0^8gvs8B!Qp7@`<4SunX@{V$#G^_ZJA3q*&^LLmOgF?1RP=sL$mlE?rMkQuHO-8U)D;Yim tWf+P$fW$8jo80`A(wtPgB9L;hE98I-ph=93ADGw}1wJrf5=GoVB>*)HaP + +try : + +if flag : + +raise ValueError ( 'boom' ) + + +except ValueError : + +return 'value' + +return 'ok'