Skip to content

Deprecate returning non-string values from a user output handler #18932

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
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
7 changes: 7 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,13 @@ PHP 8.5 UPGRADE NOTES
4. Deprecated Functionality
========================================

- Core:
. Returning a non-string from a user output handler is deprecated. The
deprecation warning will bypass the handler with the bad return to ensure
it is visible; if there are nested output handlers the next one will still
be used.
RFC: https://wiki.php.net/rfc/deprecations_php_8_4

- Hash:
. The MHASH_* constants have been deprecated. These have been overlooked
when the mhash*() function family has been deprecated per
Expand Down
7 changes: 6 additions & 1 deletion Zend/tests/concat/bug79836.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ $c .= [];
ob_end_clean();
echo $counter . "\n";
?>
--EXPECT--
--EXPECTF--
Deprecated: main(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d

Deprecated: main(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d

Deprecated: ob_end_clean(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d
3
5 changes: 4 additions & 1 deletion Zend/tests/concat/bug79836_1.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ $x = $c . $x;
ob_end_clean();
echo "Done\n";
?>
--EXPECT--
--EXPECTF--
Deprecated: main(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d

Deprecated: ob_end_clean(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d
Done
5 changes: 4 additions & 1 deletion Zend/tests/concat/bug79836_2.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ $x = $c . $xxx;
ob_end_clean();
echo $x . "\n";
?>
--EXPECT--
--EXPECTF--
Deprecated: X::__toString(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d

Deprecated: ob_end_clean(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d
abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabc
3 changes: 2 additions & 1 deletion Zend/tests/declare/gh18033_2.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ ob_start(function() {
);
});
?>
--EXPECT--
--EXPECTF--
Deprecated: PHP Request Shutdown: Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d
2 changes: 2 additions & 0 deletions Zend/tests/gh11189.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ while (1) {
?>
--EXPECTF--
Success
Deprecated: main(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d

Fatal error: Allowed memory size of %s bytes exhausted%s(tried to allocate %s bytes) in %s on line %d
2 changes: 2 additions & 0 deletions Zend/tests/gh11189_1.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ while (1) {
?>
--EXPECTF--
Success
Deprecated: main(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d

Fatal error: Allowed memory size of %s bytes exhausted%s(tried to allocate %s bytes) in %s on line %d
7 changes: 6 additions & 1 deletion Zend/tests/gh16408.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@ $c .= [];
ob_end_clean();
echo $counter . "\n";
?>
--EXPECT--
--EXPECTF--
Deprecated: main(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d

Deprecated: main(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d

Deprecated: ob_end_clean(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d
3
3 changes: 2 additions & 1 deletion ext/session/tests/user_session_module/bug61728.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ class MySessionHandler implements SessionHandlerInterface {
session_set_save_handler(new MySessionHandler());
session_start();
?>
--EXPECT--
--EXPECTF--
Deprecated: ob_end_flush(): Returning a non-string result from user output handler output_html is deprecated in %s on line %d
8
81 changes: 68 additions & 13 deletions main/output.c
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,7 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
return PHP_OUTPUT_HANDLER_FAILURE;
}

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

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

#define PHP_OUTPUT_USER_SUCCESS(retval) ((Z_TYPE(retval) != IS_UNDEF) && !(Z_TYPE(retval) == IS_FALSE))
if (SUCCESS == zend_call_function(&handler->func.user->fci, &handler->func.user->fcc) && PHP_OUTPUT_USER_SUCCESS(retval)) {
/* user handler may have returned TRUE */
status = PHP_OUTPUT_HANDLER_NO_DATA;
if (Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) {
convert_to_string(&retval);
if (Z_STRLEN(retval)) {
context->out.data = estrndup(Z_STRVAL(retval), Z_STRLEN(retval));
context->out.used = Z_STRLEN(retval);
context->out.free = 1;
status = PHP_OUTPUT_HANDLER_SUCCESS;
if (SUCCESS == zend_call_function(&handler->func.user->fci, &handler->func.user->fcc) && Z_TYPE(retval) != IS_UNDEF) {
if (Z_TYPE(retval) != IS_STRING) {
// Make sure that we don't get lost in the current output buffer
// by disabling it
handler->flags |= PHP_OUTPUT_HANDLER_DISABLED;
php_error_docref(
NULL,
E_DEPRECATED,
"Returning a non-string result from user output handler %s is deprecated",
ZSTR_VAL(handler->name)
);
// Check if the handler is still in the list of handlers to
// determine if the PHP_OUTPUT_HANDLER_DISABLED flag can
// be removed
still_have_handler = false;
int handler_count = php_output_get_level();
if (handler_count) {
php_output_handler **handlers = (php_output_handler **) zend_stack_base(&OG(handlers));
for (int iii = 0; iii < handler_count; ++iii) {
php_output_handler *curr_handler = handlers[iii];
if (curr_handler == handler) {
handler->flags &= (~PHP_OUTPUT_HANDLER_DISABLED);
still_have_handler = true;
break;
}
}
}
}
if (Z_TYPE(retval) == IS_FALSE) {
/* call failed, pass internal buffer along */
status = PHP_OUTPUT_HANDLER_FAILURE;
} else {
/* user handler may have returned TRUE */
status = PHP_OUTPUT_HANDLER_NO_DATA;
if (Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) {
convert_to_string(&retval);
if (Z_STRLEN(retval)) {
context->out.data = estrndup(Z_STRVAL(retval), Z_STRLEN(retval));
context->out.used = Z_STRLEN(retval);
context->out.free = 1;
status = PHP_OUTPUT_HANDLER_SUCCESS;
}
}
}
} else {
Expand All @@ -996,10 +1029,17 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
status = PHP_OUTPUT_HANDLER_FAILURE;
}
}
handler->flags |= PHP_OUTPUT_HANDLER_STARTED;
if (still_have_handler) {
handler->flags |= PHP_OUTPUT_HANDLER_STARTED;
}
OG(running) = NULL;
}

if (!still_have_handler) {
// Handler and context will have both already been freed
return status;
}

switch (status) {
case PHP_OUTPUT_HANDLER_FAILURE:
/* disable this handler */
Expand Down Expand Up @@ -1225,6 +1265,19 @@ static int php_output_stack_pop(int flags)
}
php_output_handler_op(orphan, &context);
}
// If it isn't still in the stack, cannot free it
bool still_have_handler = false;
int handler_count = php_output_get_level();
if (handler_count) {
php_output_handler **handlers = (php_output_handler **) zend_stack_base(&OG(handlers));
for (int iii = 0; iii < handler_count; ++iii) {
php_output_handler *curr_handler = handlers[iii];
if (curr_handler == orphan) {
still_have_handler = true;
break;
}
}
}

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

/* destroy the handler (after write!) */
php_output_handler_free(&orphan);
if (still_have_handler) {
php_output_handler_free(&orphan);
}
php_output_context_dtor(&context);

return 1;
Expand Down
12 changes: 11 additions & 1 deletion sapi/cli/tests/gh8827-002.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,20 @@ print "STDOUT:\n";
fclose(STDOUT);
var_dump(@fopen('php://stdout', 'a'));
?>
--EXPECT--
--EXPECTF--
STDIN:

Deprecated: main(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d
bool(false)

Deprecated: var_dump(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d
STDERR:

Deprecated: main(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d
bool(false)

Deprecated: var_dump(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d
STDOUT:

Deprecated: main(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d
bool(false)
1 change: 1 addition & 0 deletions sapi/cli/tests/gh8827-003.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ file_put_contents('php://fd/2', "Goes to stderrFile\n");

ob_start(function ($buffer) use ($stdoutStream) {
fwrite($stdoutStream, $buffer);
return '';
}, 1);

print "stdoutFile:\n";
Expand Down
2 changes: 1 addition & 1 deletion tests/output/bug60768.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Bug #60768 Output buffer not discarded

global $storage;

ob_start(function($buffer) use (&$storage) { $storage .= $buffer; }, 20);
ob_start(function($buffer) use (&$storage) { $storage .= $buffer; return ''; }, 20);

echo str_repeat("0", 20); // fill in the buffer

Expand Down
9 changes: 7 additions & 2 deletions tests/output/ob_start_basic_002.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,24 @@ foreach ($callbacks as $callback) {
}

?>
--EXPECT--
--EXPECTF--
--> Use callback 'return_empty_string':


--> Use callback 'return_false':

Deprecated: ob_end_flush(): Returning a non-string result from user output handler return_false is deprecated in %s on line %d
My output.

--> Use callback 'return_null':

Deprecated: ob_end_flush(): Returning a non-string result from user output handler return_null is deprecated in %s on line %d


--> Use callback 'return_string':
I stole your output.

--> Use callback 'return_zero':
0

Deprecated: ob_end_flush(): Returning a non-string result from user output handler return_zero is deprecated in %s on line %d
0
96 changes: 96 additions & 0 deletions tests/output/ob_start_callback_bad_return/exception_handler.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
--TEST--
ob_start(): Check behaviour with deprecation converted to exception
--FILE--
<?php

$log = [];

set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) {
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
});

function return_null($string) {
global $log;
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
return null;
}

function return_false($string) {
global $log;
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
return false;
}

function return_true($string) {
global $log;
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
return true;
}

function return_zero($string) {
global $log;
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
return 0;
}

$cases = ['return_null', 'return_false', 'return_true', 'return_zero'];
foreach ($cases as $case) {
$log = [];
echo "\n\nTesting: $case\n";
ob_start($case);
echo "Inside of $case\n";
try {
ob_end_flush();
} catch (\ErrorException $e) {
echo $e . "\n";
}
echo "\nEnd of $case, log was:\n";
echo implode("\n", $log);
}

?>
--EXPECTF--
Testing: return_null
ErrorException: ob_end_flush(): Returning a non-string result from user output handler return_null is deprecated in %s:%d
Stack trace:
#0 [internal function]: {closure:%s:%d}(8192, 'ob_end_flush():...', %s, %d)
#1 %s(%d): ob_end_flush()
#2 {main}

End of return_null, log was:
return_null: <<<Inside of return_null
>>>

Testing: return_false
Inside of return_false
ErrorException: ob_end_flush(): Returning a non-string result from user output handler return_false is deprecated in %s:%d
Stack trace:
#0 [internal function]: {closure:%s:%d}(8192, 'ob_end_flush():...', %s, %d)
#1 %s(%d): ob_end_flush()
#2 {main}

End of return_false, log was:
return_false: <<<Inside of return_false
>>>

Testing: return_true
ErrorException: ob_end_flush(): Returning a non-string result from user output handler return_true is deprecated in %s:%d
Stack trace:
#0 [internal function]: {closure:%s:%d}(8192, 'ob_end_flush():...', %s, %d)
#1 %s(%d): ob_end_flush()
#2 {main}

End of return_true, log was:
return_true: <<<Inside of return_true
>>>

Testing: return_zero
0ErrorException: ob_end_flush(): Returning a non-string result from user output handler return_zero is deprecated in %s:%d
Stack trace:
#0 [internal function]: {closure:%s:%d}(8192, 'ob_end_flush():...', %s, %d)
#1 %s(%d): ob_end_flush()
#2 {main}

End of return_zero, log was:
return_zero: <<<Inside of return_zero
>>>
Loading