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 (profile)
Status: Closed Package: opcache
PHP Version: 5.6.5 OS: Linux
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: php at bof dot de
New email:
PHP Version: OS:

 

 [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

Pull Requests

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-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 15:01:30 2024 UTC