Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why am I getting a segmentation fault in PHP?

I've never seen a SegFault in PHP before today, but apparently it's possible. At first I thought it was mysql driver, but it turned out it was my code ;).

I spent about 2 days debugging my code and finally tracked it down to it's cause (so to all you future PHP programmers who run into this, you are welcome!)

Long story short, you CAN'T do an unset() on the same array your are walking while you're doing array_walk().

The purpose is to eliminate all elements from $this->votes that don't exist in the $out array (where the key of $this->votes matches to the id property of one of the elements in $out).

The Problem I was having was about half the time the code would run fine, and the other half it would crash with a Segmentation Fault in the apache log (making it pretty hard to debug because it was a while until I noticed this error).

And yea, it's a pretty poorly thought out piece of code to begin with....

    array_walk($this->votes, function(&$o, $key) use($that, $out) {
        $found = array_filter($out, function($p) use($key) {
            return $p['id'] == $key;
        });

        if(count($found) == 0) {
            unset($this->votes[$key]); //very very bad!!!!
        }
    });
like image 373
Toli Avatar asked Oct 21 '22 17:10

Toli


1 Answers

As I understand it, what ends up happening is unset() messes up the $this->votes array length. array_walk uses an iterator which expects the $this->votes array to remain the same length throughout the entire walk. If I wrote my own array_walk function (for ($i = 0; $i < count($this->votes); $i++) it would just throw an undefined index notice. But since we are using array_walk it will actually try to look in a location in memory which may or may not have some data. This can cause the unpredictability of the function (sometimes the code can run just fine, other times it will cause a seg fault).

So the RIGHT way to do it would be

    $tmpVotes = array();
    array_walk($this->votes, function(&$o, $key) use($that, $out, $tmpVotes) {
        $found = array_filter($out, function($p) use($key, $that, $tmpVotes) {
            return $p['id'] == $key;
        });

        if(count($found) > 0) {
            $tpmVotes[$key] = $o;
        }
    });

    $this->votes = $tmpVotes;

From PHP Manual:

Only the values of the array may potentially be changed; its structure cannot be altered, i.e., the programmer cannot add, unset or reorder elements. If the callback does not respect this requirement, the behavior of this function is undefined, and unpredictable.

If anyone has a better way of explaining what happens here, please post!

like image 87
Toli Avatar answered Oct 28 '22 22:10

Toli