php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #80184 Complex expression in while / if statements resolves to false incorrectly
Submitted: 2020-10-04 12:58 UTC Modified: 2020-10-05 09:17 UTC
From: jelly dot legend at gmail dot com Assigned:
Status: Closed Package: opcache
PHP Version: 8.0.0rc1 OS: MacOS 10.15.6
Private report: No CVE-ID: None
 [2020-10-04 12:58 UTC] jelly dot legend at gmail dot com
Description:
------------
If a use a complex expression as an if condition or while condition, it incorrectly resolves to false. While it correctly evaluates it if passed to a variable and printed.

PHP version:
PHP 8.0.0rc1 (cli) (built: Oct  4 2020 13:58:56) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.0rc1, Copyright (c), by Zend Technologies


php.development-ini changes:
zend_extension=opcache
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128


If I disable opcache I get the correct results.
PHP was built from source. With following configuration.

./configure \
  --prefix=.../php-8.0.0rc1 \
  --with-config-file-path=.../php-8.0.0rc1/etc \
  --enable-mbstring \
  --enable-sockets \
  --enable-opcache \
  --with-openssl=/usr/local/openssl \
  --with-curl \
  --with-sodium \
  --with-bz2 \
  --with-zlib \
  --with-zip

I was first noticed the bug on beta-4 but decided to try it out in rc-1 instead.


Test script:
---------------
$factory = function (string $msg): Closure {
    return function () use ($msg): void {
        echo $msg;
    };
};

$callbacks = [
    $factory('First item!'),
    $factory('Second item!'),
    $factory('Third item!'),
    $factory('Fourth item!'),
];

$nl = function (): void {
    echo PHP_EOL;
};


while ((null !== $callback = array_shift($callbacks)) && ($callback() || $nl() || true))
    /**/; // exists after first item, should iterate thought all


// Full test at: https://pastebin.com/br54nGvM

Expected result:
----------------
First item!
Second item!
Third item!
Fourth item!

Actual result:
--------------
First item!

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2020-10-05 09:08 UTC] nikic@php.net
-Status: Open +Status: Verified
 [2020-10-05 09:08 UTC] nikic@php.net
Also happens with opcache.jit=0, so generic optimization issue.

Slightly reduced by Dharman:

<?php
  
$callbacks = [
    function () { echo "First item!\n"; },
    function () { echo "Second item!\n"; },
    function () { echo "Third item!\n"; },
    function () { echo "Fourth item!\n"; },
];

while ($callback = array_shift($callbacks) and $callback() || true); 

This iterates once instead of multiple times.
 [2020-10-05 09:17 UTC] nikic@php.net
Culprit is the block pass:

$_main:
     ; (lines=22, args=0, vars=2, tmps=10)
     ; (before block pass)
     ; /home/nikic/php/php-src/t170.php:1-11
     ; return  [] RANGE[0..0]
BB0:
     ; start lines=[0-9]
     ; to=(BB1)
0000 T2 = DECLARE_LAMBDA_FUNCTION string("")
0001 T3 = INIT_ARRAY 4 (packed) T2 NEXT
0002 T4 = DECLARE_LAMBDA_FUNCTION string("")
0003 T3 = ADD_ARRAY_ELEMENT T4 NEXT
0004 T5 = DECLARE_LAMBDA_FUNCTION string("")
0005 T3 = ADD_ARRAY_ELEMENT T5 NEXT
0006 T6 = DECLARE_LAMBDA_FUNCTION string("")
0007 T3 = ADD_ARRAY_ELEMENT T6 NEXT
0008 ASSIGN CV0($callbacks) T3
0009 NOP

BB1:
     ; follow target lines=[10-14]
     ; to=(BB4, BB2)
0010 INIT_FCALL 1 96 string("array_shift")
0011 SEND_REF CV0($callbacks) 1
0012 V8 = DO_ICALL
0013 T9 = ASSIGN CV1($callback) V8
0014 T9 = JMPZ_EX T9 BB4

BB2:
     ; follow lines=[15-17]
     ; to=(BB1, BB3)
0015 INIT_DYNAMIC_CALL 0 CV1($callback)
0016 V10 = DO_FCALL
0017 T9 = JMPNZ_EX V10 BB1

BB3:
     ; follow lines=[18-20]
     ; to=(BB1, BB4)
0018 T11 = BOOL bool(true)
0019 T9 = BOOL T11
0020 JMPNZ T9 BB1

BB4:
     ; follow target exit lines=[21-21]
0021 RETURN int(1)

$_main:
     ; (lines=17, args=0, vars=2, tmps=10)
     ; (after block pass)
     ; /home/nikic/php/php-src/t170.php:1-11
     ; return  [] RANGE[0..0]
BB0:
     ; start lines=[0-13]
     ; to=(BB4, BB2)
0000 T2 = DECLARE_LAMBDA_FUNCTION string("")
0001 T3 = INIT_ARRAY 4 (packed) T2 NEXT
0002 T4 = DECLARE_LAMBDA_FUNCTION string("")
0003 T3 = ADD_ARRAY_ELEMENT T4 NEXT
0004 T5 = DECLARE_LAMBDA_FUNCTION string("")
0005 T3 = ADD_ARRAY_ELEMENT T5 NEXT
0006 T6 = DECLARE_LAMBDA_FUNCTION string("")
0007 T3 = ADD_ARRAY_ELEMENT T6 NEXT
0008 ASSIGN CV0($callbacks) T3
0009 INIT_FCALL 1 96 string("array_shift")
0010 SEND_REF CV0($callbacks) 1
0011 V8 = DO_ICALL
0012 T9 = ASSIGN CV1($callback) V8
0013 JMPZ T9 BB4

BB1:
     ; follow exit empty

BB2:
     ; follow lines=[14-15]
     ; to=(BB1)
0014 INIT_DYNAMIC_CALL 0 CV1($callback)
0015 DO_FCALL

BB4:
     ; target exit lines=[16-16]
0016 RETURN int(1)

These are clearly not the same.
 [2020-10-05 13:24 UTC] nikic@php.net
Automatic comment on behalf of nikita.ppv@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=6ea870f5fb94c251cf201fb2955a93ba70edaa89
Log: Fix bug #80184
 [2020-10-05 13:24 UTC] nikic@php.net
-Status: Verified +Status: Closed
 
PHP Copyright © 2001-2021 The PHP Group
All rights reserved.
Last updated: Wed Jan 20 14:01:23 2021 UTC