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
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ PHP NEWS
. Fixed bug GH-24321 (ZTS: global_map_ptr_last survives zend_shutdown(),
so an in-process engine restart keeps growing the map_ptr table).
(David Carlier)
. Fixed temporaries leaking when a VM interrupt throws at the op that frees
them, e.g. as a foreach loop over a generator exits.
(C. Scott Ananian)

- Date:
. Fixed bug GH-20503 (Assertion failure with ext/date DateInterval property
Expand Down
82 changes: 82 additions & 0 deletions Zend/tests/generators/foreach_vm_interrupt_on_exit.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
--TEST--
VM interrupt throwing as foreach over a generator exits the loop
--DESCRIPTION--
When the generator finishes, FE_RESET and FE_FETCH jump forward to the loop's
FE_FREE. An interrupt that throws there must not leak the iterator.
--EXTENSIONS--
zend_test
--INI--
zend_test.observer.enabled=1
zend_test.observer.show_output=0
zend_test.observer.observe_function_names=release
zend_test.observer.set_vm_interrupt_on_end=1
--FILE--
<?php
function release() {
echo "release()\n";
}

// The interrupt is still pending: the loop's backward jump throws it.
function after() {
for ($i = 0; $i < 3; $i++) {
echo "after() ", $i, "\n";
}
}

function gen() {
yield 1;
release();
}

function gen_empty() {
release();
return;
yield;
}

function &gen_ref() {
$v = 1;
yield $v;
release();
}

function test(string $name, Closure $fn) {
echo "$name:\n";
try {
$fn();
after();
} catch (Exception $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
}

test('foreach', function () {
foreach (gen() as $v) {
echo "value: $v\n";
}
});

test('foreach, empty', function () {
foreach (gen_empty() as $v) {
echo "value: $v\n";
}
});

test('foreach by reference', function () {
foreach (gen_ref() as &$v) {
echo "value: $v\n";
}
});
?>
--EXPECT--
foreach:
value: 1
release()
Exception: VM interrupt
foreach, empty:
release()
Exception: VM interrupt
foreach by reference:
value: 1
release()
Exception: VM interrupt
93 changes: 93 additions & 0 deletions Zend/tests/vm_interrupt_forward_jump.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
--TEST--
VM interrupt pending at a forward jump onto the op that frees a temporary
--DESCRIPTION--
A jump whose target frees or consumes a temporary that is live across the
jump: the target is the end of that temporary's live range, so an exception
thrown there by an interrupt must not leak the temporary.
--EXTENSIONS--
zend_test
--INI--
zend_test.observer.enabled=1
zend_test.observer.show_output=0
zend_test.observer.observe_function_names=release
zend_test.observer.set_vm_interrupt_on_end=1
--FILE--
<?php
function release() {
echo "release()\n";
}

// Not constant, so that the optimizer keeps the branches, and not interned
$n = 3;
function value() {
return str_repeat('a', $GLOBALS['n']);
}

// The interrupt is still pending: the loop's backward jump throws it.
function after() {
for ($i = 0; $i < 3; $i++) {
echo "after() ", $i, "\n";
}
}

function test(string $name, Closure $fn) {
echo "$name:\n";
try {
$fn();
after();
} catch (Exception $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
}

test('ternary', function () {
$c = value();
$x = $c ? $c . release() : 'c';
});

test('ternary, array element', function () {
$c = value();
$a = [];
$a['k'] = $c ? $c . release() : 'c';
});

// Interrupts are checked on return from a function in the CALL VM
test('interpolated last', function () {
$f = 'release';
$x = "<{$f()}";
});

test('match', function () {
$c = value();
$x = match ($c) {
'aaa' => $c . release(),
default => 'c',
};
});

test('switch', function () {
switch (value()) {
case 'aaa':
release();
break;
default:
echo "default\n";
}
});
?>
--EXPECT--
ternary:
release()
Exception: VM interrupt
ternary, array element:
release()
Exception: VM interrupt
interpolated last:
release()
Exception: VM interrupt
match:
release()
Exception: VM interrupt
switch:
release()
Exception: VM interrupt
40 changes: 40 additions & 0 deletions Zend/tests/vm_interrupt_generator_throw.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
VM interrupt handled while Generator::throw() resumes the generator
--EXTENSIONS--
zend_test
--INI--
zend_test.observer.enabled=1
zend_test.observer.show_output=0
zend_test.observer.observe_function_names=gen
zend_test.observer.set_vm_interrupt_on_begin=1
--FILE--
<?php
class Dtor {
public function __destruct() {
echo "destroyed\n";
}
}

function gen() {
try {
yield new Dtor();
} catch (Exception $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
echo "resumed\n";
yield 2;
}

$g = gen();
var_dump($g->current() instanceof Dtor);
var_dump($g->throw(new Exception('thrown')));
unset($g);
echo "done\n";
?>
--EXPECT--
bool(true)
Exception: thrown
resumed
destroyed
int(2)
done
Loading
Loading