Skip to content

Commit 65a69b4

Browse files
authored
Fix GH-22671: assert.bail must not unwind with a pending exception (#22679)
zend_throw_unwind_exit() requires !EG(exception), but assert.bail called it right after zend_exception_error(), which can itself re-throw while printing the callback's exception (a throwing __toString, a throwing __destruct when the exception is released, or the call-stack guard re-tripping at the stack limit). Only throw the unwind exit when no exception is pending; otherwise let the re-thrown exception propagate, matching exit() and phar. Fixes GH-22671
1 parent 830bb6c commit 65a69b4

3 files changed

Lines changed: 45 additions & 1 deletion

File tree

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ PHP NEWS
8080
- Standard:
8181
. Fixed sleep() and usleep() to reject values that overflow the underlying
8282
unsigned int timeout. (Weilin Du)
83+
. Fixed bug GH-22671 (assert.bail aborts the process when the assert callback
84+
throws an exception whose reporting re-throws). (iliaal)
8385

8486
- Streams:
8587
. Fixed bug GH-21468 (Segfault in file_get_contents w/ a https URL

ext/standard/assert.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,9 @@ PHP_FUNCTION(assert)
245245
* exception so we can avoid bailout and use unwind_exit. */
246246
zend_exception_error(EG(exception), E_WARNING);
247247
}
248-
zend_throw_unwind_exit();
248+
if (!EG(exception)) {
249+
zend_throw_unwind_exit();
250+
}
249251
RETURN_THROWS();
250252
} else {
251253
RETURN_FALSE;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
GH-22671 (assert.bail must not call zend_throw_unwind_exit with a pending exception)
3+
--INI--
4+
zend.assertions=1
5+
assert.bail=1
6+
assert.exception=0
7+
assert.callback=cb
8+
error_reporting=0
9+
--FILE--
10+
<?php
11+
register_shutdown_function(function () {
12+
echo "shutdown reached\n";
13+
});
14+
15+
class Inner extends Exception {
16+
public function __destruct() {
17+
throw new Exception("thrown from __destruct");
18+
}
19+
}
20+
21+
class Boom extends Exception {
22+
public function __toString(): string {
23+
throw new Inner("inner");
24+
}
25+
}
26+
27+
function cb() {
28+
throw new Boom("boom");
29+
}
30+
31+
// The callback throws Boom; the bail path prints it, which re-throws from
32+
// Boom::__toString(), and releasing that exception re-throws again from
33+
// Inner::__destruct(). The bail path must not reach zend_throw_unwind_exit()
34+
// while an exception is pending, otherwise the process aborts.
35+
assert(false);
36+
37+
echo "not reached\n";
38+
?>
39+
--EXPECT--
40+
shutdown reached

0 commit comments

Comments
 (0)