diff --git a/NEWS b/NEWS index 519b0ccaf053..2034934a0f88 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,8 @@ PHP NEWS next() call on the inner generator). (iliaal) . Fixed bug GH-23301 (Nested "yield from" yields a value twice when the middle generator delegates again). (Lazizbek Ergashev) + . Fixed the live range of ZEND_JMP_SET and ZEND_COALESCE covering the + fall-through path. (Mrmaxmeier) - CLI: . Fixed bug GH-23425 (sapi_cli_server_send_headers() does not check the diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 317f68b486bb..1df4f1da3f6f 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -777,6 +777,22 @@ static void emit_live_range( kind = ZEND_LIVE_LOOP; start++; break; + case ZEND_JMP_SET: + case ZEND_COALESCE: + /* These opcodes only write their result on the branch they take. + * The live range must therefore start at the jump target, not + * behind the definition, or it would also cover the fall-through + * path on which the result was never written. */ + if (needs_live_range && !needs_live_range(op_array, orig_def_opline)) { + return; + } + kind = ZEND_LIVE_TMPVAR; + start = OP_JMP_ADDR(def_opline, def_opline->op2) - op_array->opcodes; + if (start >= end) { + /* The result is freed right at the jump target. */ + return; + } + break; /* Objects created via ZEND_NEW are only fully initialized * after the DO_FCALL (constructor call). * We are creating two live-ranges: ZEND_LINE_NEW for uninitialized diff --git a/ext/opcache/tests/fuzzer_function_jit_live_range_coalesce.phpt b/ext/opcache/tests/fuzzer_function_jit_live_range_coalesce.phpt new file mode 100644 index 000000000000..a0a323b04774 --- /dev/null +++ b/ext/opcache/tests/fuzzer_function_jit_live_range_coalesce.phpt @@ -0,0 +1,33 @@ +--TEST-- +Live range of ZEND_COALESCE must not cover the fall-through path +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=disable +--ENV-- +USE_ZEND_ALLOC=0 +USE_TRACKED_ALLOC=1 +--FILE-- + 1 }; + } +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +function coalesce($a, $b) { + return $a ?? $b; +} +var_dump(coalesce("x", "y"), coalesce(null, "y")); +echo "OK\n"; +?> +--EXPECT-- +UnhandledMatchError: Unhandled match case true +string(1) "x" +string(1) "y" +OK diff --git a/ext/opcache/tests/fuzzer_function_jit_live_range_jmp_set.phpt b/ext/opcache/tests/fuzzer_function_jit_live_range_jmp_set.phpt new file mode 100644 index 000000000000..5b8bb9a008c3 --- /dev/null +++ b/ext/opcache/tests/fuzzer_function_jit_live_range_jmp_set.phpt @@ -0,0 +1,32 @@ +--TEST-- +Live range of ZEND_JMP_SET must not cover the fall-through path +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=disable +--ENV-- +USE_ZEND_ALLOC=0 +USE_TRACKED_ALLOC=1 +--FILE-- + 1 }; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +function elvis($a, $b) { + return $a ?: $b; +} +var_dump(elvis("x", "y"), elvis("", "y"), elvis(null, "z")); +echo "OK\n"; +?> +--EXPECTF-- +Warning: Undefined variable $a in %s on line %d +UnhandledMatchError: Unhandled match case true +string(1) "x" +string(1) "y" +string(1) "z" +OK