php / php-src

The PHP Interpreter
https://www.php.net
Other
38.15k stars 7.74k forks source link

Unexpected results with output buffering in JIT #16408

Closed YuanchengJiang closed 1 week ago

YuanchengJiang commented 1 week ago

Description

The following code:

<?php
$statuses = array();
function oh($str) {
    global $statuses;
    $statuses[] = "$str";
    return $str;
}
ob_start("oh", 3);
ob_flush();
echo "no";
ob_clean();
echo "yes!\n";
echo "no";
ob_end_clean();
print_r($statuses);
?>

Resulted in this output:

yes!
noArray
(
    [0] => 
    [1] => no
    [2] => yes!
no
    [3] => 
)

But I expected this output instead:

yes!
Array
(
    [0] => 
    [1] => no
    [2] => yes!

    [3] => no
)

To reproduce: enable JIT

PHP Version

nightly

Operating System

ubuntu 22.04

YuanchengJiang commented 1 week ago

Another case:

<?php
$counter = 0;
ob_start(function ($buffer) use (&$c, &$counter) {
        $c = 0;
        ++$counter;
}, 1);
$c .= [];
$c .= [];
ob_end_clean();
echo $counter . "\n";
?>

Expected: 3 Actual: 1

iluuu1994 commented 1 week ago

The first case is caused not by the JIT but by the optimizer:

diff --git a/Zend/tests/gh16408_1.txt b/Zend/tests/gh16408_2.txt
index 13d78202a3..d4b1e2d0b3 100644
--- a/Zend/tests/gh16408_1.txt
+++ b/Zend/tests/gh16408_2.txt
@@ -1,8 +1,7 @@
 $_main:
-     ; (lines=18, args=0, vars=1, tmps=6)
-     ; (before optimizer)
+     ; (lines=17, args=0, vars=1, tmps=0)
+     ; (after optimizer)
      ; /home/ilutov/Developer/php-src/Zend/tests/gh16408.php:1-17
-     ; return  [] RANGE[0..0]
 0000 ASSIGN CV0($statuses) array(...)
 0001 INIT_FCALL 2 112 string("ob_start")
 0002 SEND_VAL string("oh") 1
@@ -14,11 +13,10 @@ $_main:
 0008 INIT_FCALL 0 80 string("ob_clean")
 0009 DO_ICALL
 0010 ECHO string("yes!
-")
-0011 ECHO string("no")
-0012 INIT_FCALL 0 80 string("ob_end_clean")
-0013 DO_ICALL
-0014 INIT_FCALL 1 96 string("print_r")
-0015 SEND_VAR CV0($statuses) 1
-0016 DO_ICALL
-0017 RETURN int(1)
+no")
+0011 INIT_FCALL 0 80 string("ob_end_clean")
+0012 DO_ICALL
+0013 INIT_FCALL 1 96 string("print_r")
+0014 SEND_VAR CV0($statuses) 1
+0015 DO_ICALL
+0016 RETURN int(1)

Notably, the two ECHO opcodes get merged. Tbh, I don't think you should be relying on precisely when output buffering is flushed automatically. That seems extremely volatile. If you do need the output to be flushed at a certain point in time, then there's always ob_flush(). So, IMO this is a #wontfix.

The second report may be worth fixing.

001- 3
001+ Warning: Array to string conversion in Unknown on line 0
002+ 
003+ Warning: Array to string conversion in Unknown on line 0
004+ 1

Opcache is converting some constant operands to strings using convert_to_string() at optimization time. However, this 1. prevents the warnings from being printed at runtime 2. prints the warning at optimization time, at which point the handler isn't installed yet. We should probably only convert operands that don't emit errors.