Skip to content

Commit d8577d9

Browse files
Deprecate returning non-string values from a user output handler (php#18932)
https://wiki.php.net/rfc/deprecations_php_8_4
1 parent 6cc21c4 commit d8577d9

22 files changed

+603
-17
lines changed

UPGRADING

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,13 @@ PHP 8.5 UPGRADE NOTES
259259
4. Deprecated Functionality
260260
========================================
261261

262+
- Core:
263+
. Returning a non-string from a user output handler is deprecated. The
264+
deprecation warning will bypass the handler with the bad return to ensure
265+
it is visible; if there are nested output handlers the next one will still
266+
be used.
267+
RFC: https://wiki.php.net/rfc/deprecations_php_8_4
268+
262269
- Hash:
263270
. The MHASH_* constants have been deprecated. These have been overlooked
264271
when the mhash*() function family has been deprecated per

Zend/tests/concat/bug79836.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ $counter = 0;
88
ob_start(function ($buffer) use (&$c, &$counter) {
99
$c = 0;
1010
++$counter;
11+
return '';
1112
}, 1);
1213
$c .= [];
1314
$c .= [];

Zend/tests/concat/bug79836_1.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ opcache.optimization_level = 0x7FFEBFFF & ~0x400
77
$x = 'non-empty';
88
ob_start(function () use (&$c) {
99
$c = 0;
10+
return '';
1011
}, 1);
1112
$c = [];
1213
$x = $c . $x;

Zend/tests/concat/bug79836_2.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ $c = str_repeat("abcd", 10);
66

77
ob_start(function () use (&$c) {
88
$c = 0;
9+
return '';
910
}, 1);
1011

1112
class X {

Zend/tests/declare/gh18033_2.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ ob_start(function() {
1111
register_tick_function(
1212
function() { }
1313
);
14+
return '';
1415
});
1516
?>
1617
--EXPECT--

Zend/tests/gh11189.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ ob_start(function() {
1818
$a[] = 2;
1919
}
2020
fwrite(STDOUT, "Success");
21+
return '';
2122
});
2223

2324
$a = [];

Zend/tests/gh11189_1.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ ob_start(function() {
1818
$a[] = 2;
1919
}
2020
fwrite(STDOUT, "Success");
21+
return '';
2122
});
2223

2324
$a = ["not packed" => 1];

Zend/tests/gh16408.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ $counter = 0;
66
ob_start(function ($buffer) use (&$c, &$counter) {
77
$c = 0;
88
++$counter;
9+
return '';
910
}, 1);
1011
$c .= [];
1112
$c .= [];

ext/session/tests/user_session_module/bug61728.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ session
55
--FILE--
66
<?php
77
function output_html($ext) {
8-
return strlen($ext);
8+
return (string)strlen($ext);
99
}
1010

1111
class MySessionHandler implements SessionHandlerInterface {

main/output.c

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,7 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
934934
return PHP_OUTPUT_HANDLER_FAILURE;
935935
}
936936

937+
bool still_have_handler = true;
937938
/* storable? */
938939
if (php_output_handler_append(handler, &context->in) && !context->op) {
939940
context->op = original_op;
@@ -948,6 +949,7 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
948949
if (handler->flags & PHP_OUTPUT_HANDLER_USER) {
949950
zval ob_args[2];
950951
zval retval;
952+
ZVAL_UNDEF(&retval);
951953

952954
/* ob_data */
953955
ZVAL_STRINGL(&ob_args[0], handler->buffer.data, handler->buffer.used);
@@ -959,17 +961,48 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
959961
handler->func.user->fci.params = ob_args;
960962
handler->func.user->fci.retval = &retval;
961963

962-
#define PHP_OUTPUT_USER_SUCCESS(retval) ((Z_TYPE(retval) != IS_UNDEF) && !(Z_TYPE(retval) == IS_FALSE))
963-
if (SUCCESS == zend_call_function(&handler->func.user->fci, &handler->func.user->fcc) && PHP_OUTPUT_USER_SUCCESS(retval)) {
964-
/* user handler may have returned TRUE */
965-
status = PHP_OUTPUT_HANDLER_NO_DATA;
966-
if (Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) {
967-
convert_to_string(&retval);
968-
if (Z_STRLEN(retval)) {
969-
context->out.data = estrndup(Z_STRVAL(retval), Z_STRLEN(retval));
970-
context->out.used = Z_STRLEN(retval);
971-
context->out.free = 1;
972-
status = PHP_OUTPUT_HANDLER_SUCCESS;
964+
if (SUCCESS == zend_call_function(&handler->func.user->fci, &handler->func.user->fcc) && Z_TYPE(retval) != IS_UNDEF) {
965+
if (Z_TYPE(retval) != IS_STRING) {
966+
// Make sure that we don't get lost in the current output buffer
967+
// by disabling it
968+
handler->flags |= PHP_OUTPUT_HANDLER_DISABLED;
969+
php_error_docref(
970+
NULL,
971+
E_DEPRECATED,
972+
"Returning a non-string result from user output handler %s is deprecated",
973+
ZSTR_VAL(handler->name)
974+
);
975+
// Check if the handler is still in the list of handlers to
976+
// determine if the PHP_OUTPUT_HANDLER_DISABLED flag can
977+
// be removed
978+
still_have_handler = false;
979+
int handler_count = php_output_get_level();
980+
if (handler_count) {
981+
php_output_handler **handlers = (php_output_handler **) zend_stack_base(&OG(handlers));
982+
for (int handler_num = 0; handler_num < handler_count; ++handler_num) {
983+
php_output_handler *curr_handler = handlers[handler_num];
984+
if (curr_handler == handler) {
985+
handler->flags &= (~PHP_OUTPUT_HANDLER_DISABLED);
986+
still_have_handler = true;
987+
break;
988+
}
989+
}
990+
}
991+
}
992+
if (Z_TYPE(retval) == IS_FALSE) {
993+
/* call failed, pass internal buffer along */
994+
status = PHP_OUTPUT_HANDLER_FAILURE;
995+
} else {
996+
/* user handler may have returned TRUE */
997+
status = PHP_OUTPUT_HANDLER_NO_DATA;
998+
if (Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) {
999+
convert_to_string(&retval);
1000+
if (Z_STRLEN(retval)) {
1001+
context->out.data = estrndup(Z_STRVAL(retval), Z_STRLEN(retval));
1002+
context->out.used = Z_STRLEN(retval);
1003+
context->out.free = 1;
1004+
status = PHP_OUTPUT_HANDLER_SUCCESS;
1005+
}
9731006
}
9741007
}
9751008
} else {
@@ -996,10 +1029,17 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
9961029
status = PHP_OUTPUT_HANDLER_FAILURE;
9971030
}
9981031
}
999-
handler->flags |= PHP_OUTPUT_HANDLER_STARTED;
1032+
if (still_have_handler) {
1033+
handler->flags |= PHP_OUTPUT_HANDLER_STARTED;
1034+
}
10001035
OG(running) = NULL;
10011036
}
10021037

1038+
if (!still_have_handler) {
1039+
// Handler and context will have both already been freed
1040+
return status;
1041+
}
1042+
10031043
switch (status) {
10041044
case PHP_OUTPUT_HANDLER_FAILURE:
10051045
/* disable this handler */
@@ -1225,6 +1265,19 @@ static int php_output_stack_pop(int flags)
12251265
}
12261266
php_output_handler_op(orphan, &context);
12271267
}
1268+
// If it isn't still in the stack, cannot free it
1269+
bool still_have_handler = false;
1270+
int handler_count = php_output_get_level();
1271+
if (handler_count) {
1272+
php_output_handler **handlers = (php_output_handler **) zend_stack_base(&OG(handlers));
1273+
for (int handler_num = 0; handler_num < handler_count; ++handler_num) {
1274+
php_output_handler *curr_handler = handlers[handler_num];
1275+
if (curr_handler == orphan) {
1276+
still_have_handler = true;
1277+
break;
1278+
}
1279+
}
1280+
}
12281281

12291282
/* pop it off the stack */
12301283
zend_stack_del_top(&OG(handlers));
@@ -1240,7 +1293,9 @@ static int php_output_stack_pop(int flags)
12401293
}
12411294

12421295
/* destroy the handler (after write!) */
1243-
php_output_handler_free(&orphan);
1296+
if (still_have_handler) {
1297+
php_output_handler_free(&orphan);
1298+
}
12441299
php_output_context_dtor(&context);
12451300

12461301
return 1;

0 commit comments

Comments
 (0)