php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #81015 Opcache optimization assumes wrong part of ternary operator in if-condition
Submitted: 2021-05-06 04:14 UTC Modified: 2021-05-06 08:27 UTC
Votes:1
Avg. Score:5.0 ± 0.0
Reproduced:1 of 1 (100.0%)
Same Version:0 (0.0%)
Same OS:0 (0.0%)
From: redwormik at gmail dot com Assigned:
Status: Closed Package: opcache
PHP Version: 8.0.5 OS: Alpine 3.13 (Docker)
Private report: No CVE-ID: None
 [2021-05-06 04:14 UTC] redwormik at gmail dot com
Description:
------------
I have opcache enabled and opcache.optimization_level at least 0x000000b0 (bits 4, 5 and 8 all set, other irrelevant).

When I compare a variable to NULL in a "false" part of a ternary operator in an if-condition, the variable changes to NULL in the respective branch of the if-condition (i.e. if comparison is === NULL, the variable changes to NULL in the if-branch; if comparison is !== NULL, the variable changes to NULL in the else-branch), even though the ternary operator condition is true.

Returning the value, changing the condition so it does not use ternary operator, moving the NULL-equality out of the condition to a local variable or comparing to anything else other than NULL all work as expected.

Test script:
---------------
<?php
declare(strict_types=1);

function ternary(bool $enabled, ?string $value): void
{
	// the "true" part is not as trivial in the real case
	if ($enabled ? true : $value === null) {
		echo ($value ?? 'NULL') . "\n";
	} else {
		echo "INVALID\n";
	}
}

ternary(true, 'value');
ternary(true, null);
ternary(false, 'value');
ternary(false, null);


Expected result:
----------------
value
NULL
INVALID value
NULL


Actual result:
--------------
NULL
NULL
INVALID value
NULL

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-05-06 04:17 UTC] redwormik at gmail dot com
Sorry, changed the output at last minute, but did not change the script. The condition should be (the else-branch output changed):

	if ($enabled ? true : $value === null) {
		echo ($value ?? 'NULL') . "\n";
	} else {
		echo "INVALID " . ($value ?? 'NULL') . "\n";
	}
 [2021-05-06 08:03 UTC] nikic@php.net
-Status: Open +Status: Verified
 [2021-05-06 08:27 UTC] nikic@php.net
-Status: Verified +Status: Analyzed
 [2021-05-06 08:27 UTC] nikic@php.net
This happens due to an incorrect pi-node placement:

0000 #2.CV0($enabled) [bool] RANGE[0..1] = RECV 1
0001 #3.CV1($value) [null, string] = RECV 2
0002 JMPZ #2.CV0($enabled) [bool] RANGE[0..1] BB2

BB1:
     ; follow lines=[3-4]
     ; from=(BB0)
     ; to=(BB3)
     ; idom=BB0
     ; level=1
0003 #4.T2 [true] RANGE[1..1] = QM_ASSIGN bool(true)
0004 JMP BB3

BB2:
     ; target lines=[5-5]
     ; from=(BB0)
     ; to=(BB3)
     ; idom=BB0
     ; level=1
0005 #5.T2 [bool] = TYPE_CHECK (null) #3.CV1($value) [null, string]

BB3:
     ; follow target lines=[6-6]
     ; from=(BB1, BB2)
     ; to=(BB7, BB4)
     ; idom=BB0
     ; level=1
     ; children=(BB4, BB7)
     #6.X2 [bool] = Phi(#4.X2 [true] RANGE[1..1], #5.X2 [bool])
0006 JMPZ #6.T2 [bool] BB7

BB4:
     ; follow lines=[7-7]
     ; from=(BB3)
     ; to=(BB6, BB5)
     ; idom=BB3
     ; level=2
     ; children=(BB5, BB6)
     #7.CV1($value) [null] = Pi<BB3>(#3.CV1($value) [null, string] & TYPE [undef, ref, null])
0007 #8.T4 [] = COALESCE #7.CV1($value) [null] BB6

A pi node was placed in BB4 based on the JMPZ of TYPE_CHECK, but what that didn't account for is that this TYPE_CHECK is only one of the possible values of T2 at that point.
 [2021-05-06 08:48 UTC] git@php.net
Automatic comment on behalf of nikic
Revision: https://github.com/php/php-src/commit/178bbe3478a3aa6aeb6eeae62950d8a5203d794b
Log: Fixed bug #81015
 [2021-05-06 08:48 UTC] git@php.net
-Status: Analyzed +Status: Closed
 
PHP Copyright © 2001-2021 The PHP Group
All rights reserved.
Last updated: Fri Jun 18 02:01:23 2021 UTC