From 2ec8d37eb47681b547437cad77fd31b5ca9b3f3d Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 18 Mar 2025 23:23:36 +0100 Subject: [PATCH] Fix GH-18107: Opcache CFG jmp optimization with try-finally breaks the exception table If there's a try-finally where the try_op starts on a basic block with a single JMP, and the JMP optimization causes that basic block to become unreachable, then we update try_op. In this case, there is no catch_op, so try_op is erroneously set to 0, we should instead set it to `b->start`. Closes GH-18110. --- NEWS | 2 ++ Zend/Optimizer/zend_cfg.c | 6 +++- ext/opcache/tests/opt/gh18107_1.phpt | 45 ++++++++++++++++++++++++ ext/opcache/tests/opt/gh18107_2.phpt | 52 ++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 ext/opcache/tests/opt/gh18107_1.phpt create mode 100644 ext/opcache/tests/opt/gh18107_2.phpt diff --git a/NEWS b/NEWS index 1e426e1f4c9..caad04f9d08 100644 --- a/NEWS +++ b/NEWS @@ -38,6 +38,8 @@ PHP NEWS - Opcache: . Fixed bug GH-18112 (NULL access with preloading and INI option). (nielsdos) + . Fixed bug GH-18107 (Opcache CFG jmp optimization with try-finally breaks + the exception table). (nielsdos) - PDO: . Fix memory leak when destroying PDORow. (nielsdos) diff --git a/Zend/Optimizer/zend_cfg.c b/Zend/Optimizer/zend_cfg.c index ce7d078bb95..05cb36dd344 100644 --- a/Zend/Optimizer/zend_cfg.c +++ b/Zend/Optimizer/zend_cfg.c @@ -144,7 +144,11 @@ static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg * end = blocks + block_map[op_array->try_catch_array[j].finally_op]; while (b != end) { if (b->flags & ZEND_BB_REACHABLE) { - op_array->try_catch_array[j].try_op = op_array->try_catch_array[j].catch_op; + /* In case we get here, there is no live try block but there is a live finally block. + * If we do have catch_op set, we need to set it to the first catch block to satisfy + * the constraint try_op <= catch_op <= finally_op */ + op_array->try_catch_array[j].try_op = + op_array->try_catch_array[j].catch_op ? op_array->try_catch_array[j].catch_op : b->start; changed = 1; zend_mark_reachable(op_array->opcodes, cfg, blocks + block_map[op_array->try_catch_array[j].try_op]); break; diff --git a/ext/opcache/tests/opt/gh18107_1.phpt b/ext/opcache/tests/opt/gh18107_1.phpt new file mode 100644 index 00000000000..0bb5b3201f2 --- /dev/null +++ b/ext/opcache/tests/opt/gh18107_1.phpt @@ -0,0 +1,45 @@ +--TEST-- +GH-18107 (Opcache CFG jmp optimization with try-finally breaks the exception table) +--CREDITS-- +SpencerMalone +--EXTENSIONS-- +opcache +--INI-- +opcache.optimization_level=0x10 +opcache.opt_debug_level=0x20000 +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=%d, args=0, vars=%d, tmps=%d) + ; (after optimizer) + ; %s +0000 T1 = ISSET_ISEMPTY_CV (isset) CV0($badvar) +0001 JMPNZ T1 0006 +0002 V3 = NEW 1 string("Exception") +0003 SEND_VAL_EX string("Should happen") 1 +0004 DO_FCALL +0005 THROW V3 +0006 JMP 0006 +0007 V6 = NEW 1 string("Exception") +0008 SEND_VAL_EX string("Should not happen") 1 +0009 DO_FCALL +0010 THROW V6 +0011 FAST_RET T5 +EXCEPTION TABLE: + 0006, -, 0007, 0011 +Fatal error: Uncaught Exception: Should happen in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/ext/opcache/tests/opt/gh18107_2.phpt b/ext/opcache/tests/opt/gh18107_2.phpt new file mode 100644 index 00000000000..0f4f66a4591 --- /dev/null +++ b/ext/opcache/tests/opt/gh18107_2.phpt @@ -0,0 +1,52 @@ +--TEST-- +GH-18107 (Opcache CFG jmp optimization with try-finally breaks the exception table) +--CREDITS-- +SpencerMalone +--EXTENSIONS-- +opcache +--INI-- +opcache.optimization_level=0x10 +opcache.opt_debug_level=0x20000 +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=%d, args=0, vars=%d, tmps=%d) + ; (after optimizer) + ; %s +0000 T2 = ISSET_ISEMPTY_CV (isset) CV0($badvar) +0001 JMPNZ T2 0008 +0002 V4 = NEW 1 string("Exception") +0003 SEND_VAL_EX string("Should happen") 1 +0004 DO_FCALL +0005 THROW V4 +0006 CV1($e) = CATCH string("Throwable") +0007 ECHO string("foo") +0008 T6 = FAST_CALL 0010 +0009 JMP 0015 +0010 V7 = NEW 1 string("Exception") +0011 SEND_VAL_EX string("Should not happen") 1 +0012 DO_FCALL +0013 THROW V7 +0014 FAST_RET T6 +0015 RETURN int(1) +EXCEPTION TABLE: + 0006, 0006, 0010, 0014 +Fatal error: Uncaught Exception: Should happen in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d