Skip to content

Unexpected results with output buffering in JIT #16408

Closed

Description

@YuanchengJiang

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

Activity

  1. YuanchengJiang commented on Oct 13, 2024

    @YuanchengJiang
    Author

    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

  2. iluuu1994 commented on Oct 13, 2024

    @iluuu1994
    Member

    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.

  3. added 2 commits that reference this issue on Oct 13, 2024
    03e3c2e
    c5745f4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions