Press "Enter" to skip to content

PHP错误抑制符(@)导致引用传参失败的Bug

今天cici网友发来一个问题, 说是在函数调用参数前面使用错误抑制符号(@)的时候, 貌似引用传参就失效了. 他想让我帮他解答为什么.
看下面的例子:

<?php
$array = array(1,2,3);
function add (&$arr) {
    $arr[] = 4;
}
add(@$array);
print_r($array);
/**
此时, $array没有改变, 输出:
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)
*/
add($array);
print_r($array);
/**
不使用错误抑制的情况下, 输出正常:
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
)
*/
?>

这个问题, 我之前没有遇到过, 所以首先去找找相关资料, 看看有没有现成的答案, Goolge了一番, 发现虽然有人已经向PHP报了类似的Bug:http://bugs.php.net/bug.php?id=47623, 但PHP官方还没有解决, 也没有给出答复.
没办法, 只能自己分析了, 之前我曾经在文章中介绍过错误抑制符的原理( 深入理解PHP原理之错误抑制与内嵌HTML), 从原理上来说, 错误抑制只是修改了error_reporting的level, 按理来说不会影响到上下文之间的函数调用的机制. 只能通过实地试验了.
经过gdb跟踪, 发现在使用了错误移植符以后, 函数调用前的传参opcode不同:

//没有使用错误抑制符的时候
OPCODE = SEND_REF
//使用了错误抑制符号以后
OPCODE = SEND_VAR_NO_REF

问题初步定位了, 但是造成这种差异的原因又是什么呢?
既然OPCODE不同, 那么肯定是在语法分析的阶段, 走了不同的分支了, 想到这一层, 问题也就好定位了,
原来, PHP语法分析阶段, 把形如 "@"+expr的条目, 规约成了expr_without_variable, 而这种节点的意义就是没有变量的值, 也就是字面值, 我们都知道字面值是不能传递引用的(因为它不是变量), 所以, 就会导致这种差异.
具体过程如下:
1. 语法分析阶段:

expr_without_variable:
//...有省略
   |   '@' { zend_do_begin_silence(&$1 TSRMLS_CC); }
       expr { zend_do_end_silence(&$1 TSRMLS_CC); $$ = $3; }
//此处走了ZEND_SEND_VAL分支
non_empty_function_call_parameter_list:
        expr_without_variable   { ....} //错误的走了这个分支
    |   variable                {..... } //正常情况下

所以导致在编译期间, 生成了不同的OPCODE, 也导致了问题的表象.
最后, 我已经把原因在PHP的这个bug页做了说明, 有兴趣的可以去看看我的烂英语水平. 最后谢谢cici网友提供的这个有趣的问题.

20 Comments

  1. easybuilder.pro
    easybuilder.pro June 14, 2019

    cool!

  2. Matlab Code Help
    Matlab Code Help July 30, 2018

    I Loved The Way You Discuss The Topic Great Work Thanks For The Share Your Informative Post.

  3. battery 6015i 6016i
    battery 6015i 6016i December 8, 2014

    It’s genuinely very difficult in this busy life to listen news
    on TV, thus I just use web for that purpose,
    and take the most recent news.

  4. lwt
    lwt March 26, 2013

    @add($array);

  5. […] func(); error_reporting($report); 另外错误抑制符号, 可能会造成一些问题, 参看(PHP错误抑制符(@)导致引用传参失败的Bug);最后,错误抑制符在发生错误调试的时候也可能会带来麻烦. 12. […]

  6. 过客
    过客 September 28, 2010

    大哥,我们之前没有C的开发经验,但也想了解一下PHP的源码。能否把这个例子整个gdb的追踪步骤都写出来呀?谢谢

  7. mahone
    mahone July 30, 2010

    @雪候鸟
    这样的啊,那isset这样判断下不好么?
    如果不判断穿进去,那是去函数里面判断了是吧 ?

  8. 雪候鸟
    雪候鸟 July 30, 2010

    @mahone 有的时候, 我们引用一个数组的元素的时候, 这个数组元素可能不存在, 我们为了避免它notice, 就会采用 func(@$array[‘key’]);

  9. mahone
    mahone July 30, 2010

    错误抑制符是这样用的?add(@$array);
    不是用在函数前面的么?@add($array);
    望求教

  10. 小熊
    小熊 June 1, 2010

    长知识了,劳伦斯大叔威武

Leave a Reply to lwt Cancel reply

Your email address will not be published. Required fields are marked *