Skip to content

Commit e0424d4

Browse files
committed
Fix stream wrapper stream_close() not being called on exception unwind
When EG(exception) is set, zend_call_function() will skip the call. Fix this anologously to zend_objects_destroy_object(), by backing up the exception and temporarily setting it to NULL. Fixes GH-19525
1 parent 5d5305d commit e0424d4

File tree

3 files changed

+57
-0
lines changed

3 files changed

+57
-0
lines changed

Zend/tests/exception_stream_wrapper.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ try {
2929
stream_set_option
3030
stream_stat
3131
stream_read
32+
stream_close
3233
Message

Zend/tests/gh19525.phpt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
GH-19525: Stream wrapper stream_close() not called on exception unwind
3+
--FILE--
4+
<?php
5+
6+
class Loader {
7+
public $context;
8+
9+
public function stream_open() {
10+
return true;
11+
}
12+
function stream_read() {}
13+
function stream_eof() {}
14+
function stream_flush() {}
15+
function stream_close() {
16+
echo __METHOD__, "\n";
17+
}
18+
}
19+
20+
function foo() {
21+
stream_wrapper_register('Loader', 'Loader');
22+
$fp = fopen('Loader://qqq.php', 'r');
23+
throw new Exception();
24+
}
25+
foo();
26+
27+
?>
28+
--EXPECTF--
29+
Loader::stream_close
30+
31+
Fatal error: Uncaught Exception in %s:%d
32+
Stack trace:
33+
#0 %s(%d): foo()
34+
#1 {main}
35+
thrown in %s on line %d

main/streams/userspace.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
# endif
3333
#endif
3434
#include "userspace_arginfo.h"
35+
#include "zend_exceptions.h"
3536

3637
static int le_protocols;
3738

@@ -680,7 +681,27 @@ static int php_userstreamop_close(php_stream *stream, int close_handle)
680681
assert(us != NULL);
681682

682683
zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_CLOSE, false);
684+
685+
/* Reset currently thrown exception during call of close method. */
686+
zend_object *old_exception = NULL;
687+
const zend_op *old_opline_before_exception = NULL;
688+
if (UNEXPECTED(EG(exception))) {
689+
old_exception = EG(exception);
690+
old_opline_before_exception = EG(opline_before_exception);
691+
EG(exception) = NULL;
692+
}
693+
683694
zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL);
695+
696+
if (UNEXPECTED(old_exception)) {
697+
EG(opline_before_exception) = old_opline_before_exception;
698+
if (EG(exception)) {
699+
zend_exception_set_previous(EG(exception), old_exception);
700+
} else {
701+
EG(exception) = old_exception;
702+
}
703+
}
704+
684705
zend_string_release_ex(func_name, false);
685706

686707
zval_ptr_dtor(&retval);

0 commit comments

Comments
 (0)