diff --git a/NEWS b/NEWS index 608bacc29109..33aa3e946bfd 100644 --- a/NEWS +++ b/NEWS @@ -54,6 +54,8 @@ PHP NEWS . Fixed a tracing JIT crash when compiling a side trace for a method of a class that could not be stored in the inheritance cache. (GH-21710) (Arnaud, iliaal) + . Fixed a ZEND_JMP being removed across an unreachable block that is kept + alive for its loop variable frees. (Mrmaxmeier) - PDO: . Fixed a leak when a persistent connection failed a liveness check diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index ee70d021f4a9..70e9eb142ac9 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -950,7 +950,12 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op if (opline->opcode == ZEND_JMP) { zend_basic_block *next = b + 1; - while (next < end && !(next->flags & ZEND_BB_REACHABLE)) { + /* Unreachable blocks that are kept alive for their loop var + * frees are emitted as well, so they still separate this block + * from its successor. */ + while (next < end + && !(next->flags & ZEND_BB_REACHABLE) + && !((next->flags & ZEND_BB_UNREACHABLE_FREE) && next->len != 0)) { next++; } if (next < end && next == blocks + b->successors[0]) { @@ -1149,6 +1154,11 @@ static zend_always_inline zend_basic_block *get_next_block(const zend_cfg *cfg, return NULL; } else if (next_block->flags & ZEND_BB_REACHABLE) { break; + } else if ((next_block->flags & ZEND_BB_UNREACHABLE_FREE) && next_block->len != 0) { + /* This block is unreachable, but it is still emitted to keep the + * live range of a loop var alive, so it separates the block from + * whatever follows it. */ + return NULL; } next_block++; } diff --git a/ext/opcache/tests/fuzzer_function_jit_unreachable_free_jmp.phpt b/ext/opcache/tests/fuzzer_function_jit_unreachable_free_jmp.phpt new file mode 100644 index 000000000000..b46475c5d82c --- /dev/null +++ b/ext/opcache/tests/fuzzer_function_jit_unreachable_free_jmp.phpt @@ -0,0 +1,43 @@ +--TEST-- +Block pass must not strip a JMP across an unreachable block that is kept for its loop var frees +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=disable +--ENV-- +USE_ZEND_ALLOC=0 +USE_TRACKED_ALLOC=1 +--FILE-- + +--EXPECT-- +bool(true) +string(8) "returned" +string(1) "y" +OK