Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP: Confusing disparity between invoking reflection method using constructed array or func_get_args() in version 5.4

Tags:

php

This is a very edge case in PHP 5.4 regarding passing objects by reference where get this error:

PHP Warning:  Parameter 1 to A::foo() expected to be a reference, value given

But only as a compound effect of:

  • Using reflection to set an inherited method as 'accessible',
  • AND that method taking an explicitly referential argument (&argument sig)
  • AND then invoking it with func_get_args() as opposed to constructing the array of args manually.

No idea why these things all cause this behaviour or if they should.

Important to note that this effect isn't present in PHP 5.5.

This is the code that will cause the above error, but if you comment the line with COMMENT THIS LINE the code runs fine (e.g. the object gets passed correctly to the 'foo' function):

class A {
    private function foo(&$arg1) {
        var_dump('arg1: ', $arg1);
    }   
}

class B extends A {
    public function bar() {
        $x = new stdClass();
        $x->baz = 'just a value';
        $this->callPrivate($x);
    }

    private function callPrivate($x)
    {
        $method = new \ReflectionMethod(
            'A',
            'foo'
        );

        //* for some reason, the private function needs to have been changed to be 'accessible' for this to work in 5.4
        $method->setAccessible(true);

        //working 5.4 (* see above) but not in 5.5
        $arguments = func_get_args();

        //not working in either
        $arguments = array($x); // <---- COMMENT THIS LINE TO SEE IT WORK IN PHP 5.4

        return $method->invokeArgs($this, $arguments);
    }
}

$y = new B();
$y->bar();

I can't see why there would be any difference between the two $arguments arrays since var_dumping them shows the same output. Therefore, I assume it is to do with something lower level like object 'pointers' being different (out of my depth here)?

The other question is if this is a bug in PHP 5.4, 5.5 or both?

like image 555
Sam Adams Avatar asked Nov 29 '13 11:11

Sam Adams


1 Answers

Prior to PHP 5.5.6 func_get_args() took arguments from the VM stack, copied them and returned them in an array. In PHP 5.5.6 an optimization was introduced which avoids these expensive copies in the common case. Instead of copying the zvals only the refcount is increased (by-ref args notwithstanding.)

Normally such a change would have zero effect on user code. But there are a few places in the engine where observable behavior differs based on the refcount of the zval. One such place is by-reference passing:

For the case of a dynamic function call, a zval can be passed by reference either if it is a reference or if it has refcount==1.

Before PHP 5.5.6 the zvals in the array returned by func_get_args() always had refcount==1, so they went through based on that second case. As of PHP 5.5.6 this is no longer true as by-value zvals will always have refcount>1 and cause an error if you try to pass them by-reference.

Note: The code didn't actually work before PHP 5.5.6 (the by-ref was ignored). It was just an unfortunate coincidence that you didn't get an error telling you so ;)

Update: We decided to revert the change on the 5.5 branch due to the BC break. You will get the old behavior back in PHP 5.5.8 and the new behavior will only be in PHP 5.6.

like image 126
NikiC Avatar answered Sep 28 '22 18:09

NikiC