Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Zend/zend_opcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,23 @@ static void emit_live_range(
kind = ZEND_LIVE_LOOP;
start++;
break;
case ZEND_JMP_SET:
case ZEND_COALESCE:
case ZEND_JMP_NULL:
/* 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
Expand Down
29 changes: 29 additions & 0 deletions ext/opcache/tests/live_range_coalesce.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
Live range of ZEND_COALESCE must not cover the fall-through path
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
--FILE--
<?php
$s = "b";
try {
if ("a" . $s) {
$item ??= match (true) { 1 => 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
49 changes: 49 additions & 0 deletions ext/opcache/tests/live_range_jmp_null.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
--TEST--
Live range of ZEND_JMP_NULL must not cover the fall-through path
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
--FILE--
<?php
class C {
public $p = "p";
function m($x) { return $x; }
}

/* The match always throws, so the optimizer removes the DO_FCALL that would
* define the result on the non-null path. The JMP_NULL result then shares its
* temporary slot with the CONCAT result, which JMPZ has already freed. */
function test($a, $s) {
try {
if ("a" . $s) {
echo $a?->m(match (true) { 1 => 1 });
}
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}
}
test(new C, "b");

function nullsafe($a) {
return [$a?->p, $a?->m("m")];
}
var_dump(nullsafe(new C), nullsafe(null));
echo "OK\n";
?>
--EXPECT--
UnhandledMatchError: Unhandled match case true
array(2) {
[0]=>
string(1) "p"
[1]=>
string(1) "m"
}
array(2) {
[0]=>
NULL
[1]=>
NULL
}
OK
35 changes: 35 additions & 0 deletions ext/opcache/tests/live_range_jmp_set.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--TEST--
Live range of ZEND_JMP_SET must not cover the fall-through path
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
--FILE--
<?php
/* The match always throws, so the optimizer removes the QM_ASSIGN of the else
* branch. The JMP_SET result then shares its temporary slot with the CONCAT
* result, which JMPZ has already freed. */
function test($a, $s) {
try {
if ("a" . $s) {
$a ?: match (true) { 1 => 1 };
}
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}
}
test(null, "b");

function elvis($a, $b) {
return $a ?: $b;
}
var_dump(elvis("x", "y"), elvis("", "y"), elvis(null, "z"));
echo "OK\n";
?>
--EXPECT--
UnhandledMatchError: Unhandled match case true
string(1) "x"
string(1) "y"
string(1) "z"
OK
Loading