php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #69038 switch(SOMECONSTANT) misbehaves
Submitted: 2015-02-12 08:43 UTC Modified: 2015-02-27 15:41 UTC
Votes:1
Avg. Score:4.0 ± 0.0
Reproduced:1 of 1 (100.0%)
Same Version:1 (100.0%)
Same OS:1 (100.0%)
From: php at bof dot de Assigned: laruence
Status: Closed Package: opcache
PHP Version: 5.6.5 OS: Linux
Private report: No CVE-ID:
 [2015-02-12 08:43 UTC] php at bof dot de
Description:
------------
Using a self built PHP 5.6.5, opcache enabled:

switch(ARBITRARYCONSTANTS) misbehaves: goes to the default case, unless the matching thing is the first case.

opcache is enabled like this:
zend_extension=/opt/php/extensions/opcache.so
opcache.enable_cli=1

Also happens under mod_php without opcache.enable_cli. No other opcache settings made, but happens also with our usual production settings, so that should not matter

The result is usually what I show under "actual result", below, but on some runs I also see a segmentation fault, or the correct behaviour.

I can reproduce the problem when using global "const MYCONST = 'foo';", but apparently _not_ with a class constant (class foo { const MYCONST = 'foo'; }

Will create a debug build of PHP soon and try to capture a backtrace for a test run where it runs into a segmentation violation. Might take some time, though...

Test script:
---------------
<?php

define('MYCONST', 'foo');
function goodswitch() {
	switch(MYCONST) {
	case 'foo':	return 'foo';
	case 'bar':	return 'bar';
	default:	return 'default';
	}
}
function badswitch() {
	switch(MYCONST) {
	case 'bar':	return 'bar';
	case 'foo':	return 'foo';
	default:	return 'default';
	}
}
var_dump(goodswitch());
var_dump(badswitch());

?>

Expected result:
----------------
string(3) "foo"
string(3) "foo"


Actual result:
--------------
string(3) "foo"
string(7) "default"

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-02-12 09:20 UTC] php at bof dot de
I cannot get it to segfault / dump core on an --enable-debug build. Weird. Tried it a few hundred times. However the issue is still there (outputs foo and default).

With the production build, identical in all other regards, I can get it to segfault in one of about 10 runs. Here is a backtrace of such a run, for whatever that's worth:

Core was generated by `php -d opcache.enable_cli=1 /home/patrick/webdev/switch_const_myconst.php'.
Program terminated with signal 11, Segmentation fault.
#0  0x0000000000000021 in ?? ()
(gdb) bt
#0  0x0000000000000021 in ?? ()
#1  0x0000000000853d3b in zend_hash_destroy (ht=0x7f941c441e18)
    at /usr/src/phb/build/release-5.6.5/php-src/Zend/zend_hash.c:548
#2  0x0000000000844893 in _zval_dtor_func (zvalue=0x7f941c4401b0)
    at /usr/src/phb/build/release-5.6.5/php-src/Zend/zend_variables.c:45
#3  0x000000000087368a in _zval_dtor (execute_data=0x7f941c4401d0)
    at /usr/src/phb/build/release-5.6.5/php-src/Zend/zend_variables.h:35
#4  ZEND_FREE_SPEC_TMP_HANDLER (execute_data=0x7f941c4401d0)
    at /usr/src/phb/build/release-5.6.5/php-src/Zend/zend_vm_execute.h:7956
#5  0x00000000008b07c9 in execute_ex (execute_data=0x7f941c4401d0)
    at /usr/src/phb/build/release-5.6.5/php-src/Zend/zend_vm_execute.h:363
#6  0x00007f941467947b in xdebug_execute_ex (execute_data=0x7f941c4401d0)
    at /usr/src/phb/build/release-5.6.5/xdebug/xdebug.c:1437
#7  0x00000000008e7ac2 in zend_do_fcall_common_helper_SPEC (
    execute_data=<value optimized out>)
    at /usr/src/phb/build/release-5.6.5/php-src/Zend/zend_vm_execute.h:592
#8  0x00000000008b07c9 in execute_ex (execute_data=0x7f941c4400e0)
    at /usr/src/phb/build/release-5.6.5/php-src/Zend/zend_vm_execute.h:363
#9  0x00007f941467947b in xdebug_execute_ex (execute_data=0x7f941c4400e0)
    at /usr/src/phb/build/release-5.6.5/xdebug/xdebug.c:1437
#10 0x00000000008472d4 in zend_execute_scripts (type=8, retval=0x0, 
    file_count=3) at /usr/src/phb/build/release-5.6.5/php-src/Zend/zend.c:1341
#11 0x00000000007e3bfe in php_execute_script (primary_file=0x7fffaabc56f0)
    at /usr/src/phb/build/release-5.6.5/php-src/main/main.c:2584
#12 0x00000000008eaf89 in do_cli (argc=4, argv=0x1f00e90)
    at /usr/src/phb/build/release-5.6.5/php-src/sapi/cli/php_cli.c:994
#13 0x00000000008eb91c in main (argc=4, argv=0x1f00e90)
    at /usr/src/phb/build/release-5.6.5/php-src/sapi/cli/php_cli.c:1378
 [2015-02-12 09:23 UTC] php at bof dot de
valgrind has something to say on the issue!

patrick@dev0:{dbg-5.6.5}> valgrind php -d opcache.enable_cli=1 ~/webdev/switch_const_myconst.php
==30622== Memcheck, a memory error detector
==30622== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==30622== Using Valgrind-3.6.1 and LibVEX; rerun with -h for copyright info
==30622== Command: php -d opcache.enable_cli=1 /home/patrick/webdev/switch_const_myconst.php
==30622== 
Thu Feb 12 10:22:35 2015 (30622): Debug Loading blacklist file:  '/opt/php/ini.d/opcache.blacklist'
Thu Feb 12 10:22:35 2015 (30622): Message Cached script '/home/patrick/webdev/switch_const_myconst.php'
string(3) "foo"
==30622== Conditional jump or move depends on uninitialised value(s)
==30622==    at 0xA8A33C: compare_function (zend_operators.c:1602)
==30622==    by 0xA8BE21: is_equal_function (zend_operators.c:1837)
==30622==    by 0xAEA4E1: ZEND_CASE_SPEC_TMP_CONST_HANDLER (zend_vm_execute.h:9364)
==30622==    by 0xAD67D9: execute_ex (zend_vm_execute.h:363)
==30622==    by 0xCC31BE3: xdebug_execute_ex (xdebug.c:1437)
==30622==    by 0xAD685E: zend_execute (zend_vm_execute.h:388)
==30622==    by 0xAD7390: zend_do_fcall_common_helper_SPEC (zend_vm_execute.h:592)
==30622==    by 0xADCB02: ZEND_DO_FCALL_SPEC_CONST_HANDLER (zend_vm_execute.h:2595)
==30622==    by 0xAD67D9: execute_ex (zend_vm_execute.h:363)
==30622==    by 0xCC31BE3: xdebug_execute_ex (xdebug.c:1437)
==30622==    by 0xAD685E: zend_execute (zend_vm_execute.h:388)
==30622==    by 0xA91EB9: zend_execute_scripts (zend.c:1341)
==30622== 
==30622== Use of uninitialised value of size 8
==30622==    at 0xA8A353: compare_function (zend_operators.c:1602)
==30622==    by 0xA8BE21: is_equal_function (zend_operators.c:1837)
==30622==    by 0xAEA4E1: ZEND_CASE_SPEC_TMP_CONST_HANDLER (zend_vm_execute.h:9364)
==30622==    by 0xAD67D9: execute_ex (zend_vm_execute.h:363)
==30622==    by 0xCC31BE3: xdebug_execute_ex (xdebug.c:1437)
==30622==    by 0xAD685E: zend_execute (zend_vm_execute.h:388)
==30622==    by 0xAD7390: zend_do_fcall_common_helper_SPEC (zend_vm_execute.h:592)
==30622==    by 0xADCB02: ZEND_DO_FCALL_SPEC_CONST_HANDLER (zend_vm_execute.h:2595)
==30622==    by 0xAD67D9: execute_ex (zend_vm_execute.h:363)
==30622==    by 0xCC31BE3: xdebug_execute_ex (xdebug.c:1437)
==30622==    by 0xAD685E: zend_execute (zend_vm_execute.h:388)
==30622==    by 0xA91EB9: zend_execute_scripts (zend.c:1341)
==30622== 
==30622== Conditional jump or move depends on uninitialised value(s)
==30622==    at 0xAD0D9C: _zval_dtor (zend_variables.h:32)
==30622==    by 0xAE6F4E: ZEND_FREE_SPEC_TMP_HANDLER (zend_vm_execute.h:7956)
==30622==    by 0xAD67D9: execute_ex (zend_vm_execute.h:363)
==30622==    by 0xCC31BE3: xdebug_execute_ex (xdebug.c:1437)
==30622==    by 0xAD685E: zend_execute (zend_vm_execute.h:388)
==30622==    by 0xAD7390: zend_do_fcall_common_helper_SPEC (zend_vm_execute.h:592)
==30622==    by 0xADCB02: ZEND_DO_FCALL_SPEC_CONST_HANDLER (zend_vm_execute.h:2595)
==30622==    by 0xAD67D9: execute_ex (zend_vm_execute.h:363)
==30622==    by 0xCC31BE3: xdebug_execute_ex (xdebug.c:1437)
==30622==    by 0xAD685E: zend_execute (zend_vm_execute.h:388)
==30622==    by 0xA91EB9: zend_execute_scripts (zend.c:1341)
==30622==    by 0x9FBC55: php_execute_script (main.c:2584)
==30622== 
string(7) "default"
==30622== 
==30622== HEAP SUMMARY:
==30622==     in use at exit: 5,556 bytes in 45 blocks
==30622==   total heap usage: 34,708 allocs, 34,663 frees, 5,475,889 bytes allocated
==30622== 
==30622== LEAK SUMMARY:
==30622==    definitely lost: 733 bytes in 26 blocks
==30622==    indirectly lost: 0 bytes in 0 blocks
==30622==      possibly lost: 0 bytes in 0 blocks
==30622==    still reachable: 4,823 bytes in 19 blocks
==30622==         suppressed: 0 bytes in 0 blocks
==30622== Rerun with --leak-check=full to see details of leaked memory
==30622== 
==30622== For counts of detected and suppressed errors, rerun with: -v
==30622== Use --track-origins=yes to see where uninitialised values come from
==30622== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 6 from 6)
 [2015-02-12 09:29 UTC] php at bof dot de
BTW, same valgrind result (a bit shorter backtrace) after disabling xdebug and any extension other than opcache.

Disabling opcache also, removes the issue, shows foo+foo and no valgrind noise.
 [2015-02-12 09:46 UTC] php at bof dot de
The issue remains when I change the case "label" from a constant string to a variable; the valgrind output is slightly different then:

Code:

define('MYCONST', 'foo');
function goodswitch() {
	$foo = 'foo';
	switch(MYCONST) {
	case $foo:	return 'foo';
	case 'bar':	return 'bar';
	default:	return 'default';
	}
}
function badswitch() {
	$foo = 'foo';
	switch(MYCONST) {
	case 'bar':	return 'bar';
	case $foo:	return 'foo';
	default:	return 'default';
	}
}
var_dump(goodswitch());
var_dump(badswitch());

Valgrind:

Thu Feb 12 10:44:21 2015 (4062): Debug Loading blacklist file:  '/opt/php/ini.d/opcache.blacklist'
Thu Feb 12 10:44:21 2015 (4062): Message Cached script '/home/patrick/webdev/switch_const_myconst.php'
string(3) "foo"
==4062== Conditional jump or move depends on uninitialised value(s)
==4062==    at 0xA8A33C: compare_function (zend_operators.c:1602)
==4062==    by 0xA8BE21: is_equal_function (zend_operators.c:1837)
==4062==    by 0xAF1508: ZEND_CASE_SPEC_TMP_CV_HANDLER (zend_vm_execute.h:12541)
==4062==    by 0xAD67D9: execute_ex (zend_vm_execute.h:363)
==4062==    by 0xAD685E: zend_execute (zend_vm_execute.h:388)
==4062==    by 0xA91EB9: zend_execute_scripts (zend.c:1341)
==4062==    by 0x9FBC55: php_execute_script (main.c:2584)
==4062==    by 0xB4618E: do_cli (php_cli.c:994)
==4062==    by 0xB47239: main (php_cli.c:1378)
==4062== 
==4062== Use of uninitialised value of size 8
==4062==    at 0xA8A353: compare_function (zend_operators.c:1602)
==4062==    by 0xA8BE21: is_equal_function (zend_operators.c:1837)
==4062==    by 0xAF1508: ZEND_CASE_SPEC_TMP_CV_HANDLER (zend_vm_execute.h:12541)
==4062==    by 0xAD67D9: execute_ex (zend_vm_execute.h:363)
==4062==    by 0xAD685E: zend_execute (zend_vm_execute.h:388)
==4062==    by 0xA91EB9: zend_execute_scripts (zend.c:1341)
==4062==    by 0x9FBC55: php_execute_script (main.c:2584)
==4062==    by 0xB4618E: do_cli (php_cli.c:994)
==4062==    by 0xB47239: main (php_cli.c:1378)
==4062== 
==4062== Conditional jump or move depends on uninitialised value(s)
==4062==    at 0xAD0D9C: _zval_dtor (zend_variables.h:32)
==4062==    by 0xAE6F4E: ZEND_FREE_SPEC_TMP_HANDLER (zend_vm_execute.h:7956)
==4062==    by 0xAD67D9: execute_ex (zend_vm_execute.h:363)
==4062==    by 0xAD685E: zend_execute (zend_vm_execute.h:388)
==4062==    by 0xA91EB9: zend_execute_scripts (zend.c:1341)
==4062==    by 0x9FBC55: php_execute_script (main.c:2584)
==4062==    by 0xB4618E: do_cli (php_cli.c:994)
==4062==    by 0xB47239: main (php_cli.c:1378)
==4062== 
string(7) "default"
 [2015-02-12 09:57 UTC] php at bof dot de
Playing around with the ordering of the cases:

- leaving out default: altogether does not remove the issue
- ordering the problematic case 'foo': behind the default does not remove the issue
- putting the default: as the first thing does not remove the issue AND changes valgrind output a bit (two instead of three blocks of backtraces)

This gives me a more minimal reproducer:

define('OK', 'ok');
function badswitch() {
        switch(OK) {
        default:        return 'bad';
        case 'ok':      return 'ok';
        }
}
var_dump(badswitch());

with valgrind output
Thu Feb 12 10:55:44 2015 (6761): Debug Loading blacklist file:  '/opt/php/ini.d/opcache.blacklist'
Thu Feb 12 10:55:44 2015 (6761): Message Cached script '/home/patrick/webdev/switch_const_myconst.php'
==6761== Conditional jump or move depends on uninitialised value(s)
==6761==    at 0xA8A33C: compare_function (zend_operators.c:1602)
==6761==    by 0xA8BE21: is_equal_function (zend_operators.c:1837)
==6761==    by 0xAEA4E1: ZEND_CASE_SPEC_TMP_CONST_HANDLER (zend_vm_execute.h:9364)
==6761==    by 0xAD67D9: execute_ex (zend_vm_execute.h:363)
==6761==    by 0xAD685E: zend_execute (zend_vm_execute.h:388)
==6761==    by 0xA91EB9: zend_execute_scripts (zend.c:1341)
==6761==    by 0x9FBC55: php_execute_script (main.c:2584)
==6761==    by 0xB4618E: do_cli (php_cli.c:994)
==6761==    by 0xB47239: main (php_cli.c:1378)
==6761== 
==6761== Use of uninitialised value of size 8
==6761==    at 0xA8A353: compare_function (zend_operators.c:1602)
==6761==    by 0xA8BE21: is_equal_function (zend_operators.c:1837)
==6761==    by 0xAEA4E1: ZEND_CASE_SPEC_TMP_CONST_HANDLER (zend_vm_execute.h:9364)
==6761==    by 0xAD67D9: execute_ex (zend_vm_execute.h:363)
==6761==    by 0xAD685E: zend_execute (zend_vm_execute.h:388)
==6761==    by 0xA91EB9: zend_execute_scripts (zend.c:1341)
==6761==    by 0x9FBC55: php_execute_script (main.c:2584)
==6761==    by 0xB4618E: do_cli (php_cli.c:994)
==6761==    by 0xB47239: main (php_cli.c:1378)
==6761== 
string(3) "bad"
 [2015-02-12 10:03 UTC] php at bof dot de
Final words, for now:

the issue also exists for the 5.6.2 and 5.6.4 builds I still have lying around.

It does NOT exist for my last 5.5 build, version 5.5.18 !
 [2015-02-12 11:41 UTC] php at bof dot de
Two more tests:

issue exists in PHP-5.6.6RC1

issue does NOT exist in current MASTER
 [2015-02-14 14:47 UTC] laruence@php.net
-Assigned To: +Assigned To: laruence
 [2015-02-15 03:36 UTC] laruence@php.net
a fix could be https://gist.github.com/laruence/14a5c7388a50ff716ada

anyway, I needs verify it carefully
 [2015-02-16 10:42 UTC] php at bof dot de
Thanks laruence for having a go at it.

I tried your gist with 5.6.6RC1 - the issue is still the same, although I don't get a valgrind error any more (which was sporadic before, too)

No other negative impact seen with your gist applied, though - no new test suite failures seen.
 [2015-02-17 14:06 UTC] laruence@php.net
Automatic comment on behalf of laruence
Revision: http://git.php.net/?p=php-src.git;a=commit;h=66d30959935da6ad3df800f45a2d23d09637054c
Log: Fixed bug #69038 (switch(SOMECONSTANT) misbehaves)
 [2015-02-17 14:06 UTC] laruence@php.net
-Status: Assigned +Status: Closed
 [2015-02-17 14:06 UTC] laruence@php.net
Automatic comment on behalf of laruence
Revision: http://git.php.net/?p=php-src.git;a=commit;h=7299d435362b8ae623f3897656b895e10cee1e52
Log: Fixed bug #69038 (switch(SOMECONSTANT) misbehaves) for master
 [2015-02-17 15:15 UTC] php at bof dot de
-Status: Closed +Status: Assigned
 [2015-02-17 15:15 UTC] php at bof dot de
Sorry, Laruence, have to reopen.

I test-built PHP 5.6.6RC1 with your patch applied.

Using your test case from the git commit today, I can see that that test case now works (okey two times), and with my previous 5.6.5 build it fails (bad two times).

However, my own reproducer from comment [2015-02-12 09:57 UTC] here, still fails.

Please let me know if you need any more information (configure arguments and such) for my build.
 [2015-02-27 15:41 UTC] laruence@php.net
as we talk via mail, this fixed in later commits
 [2015-02-27 15:41 UTC] laruence@php.net
-Status: Assigned +Status: Closed
 [2016-07-20 11:39 UTC] davey@php.net
Automatic comment on behalf of laruence
Revision: http://git.php.net/?p=php-src.git;a=commit;h=7299d435362b8ae623f3897656b895e10cee1e52
Log: Fixed bug #69038 (switch(SOMECONSTANT) misbehaves) for master
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Mon Feb 20 11:01:38 2017 UTC