facebook / hhvm

A virtual machine for executing programs written in Hack.
https://hhvm.com
Other
18.19k stars 3k forks source link

Inconsistency with array_uintersect_uassoc between PHP and HHVM #1696

Closed jubianchi closed 10 years ago

jubianchi commented 10 years ago
OS HHVM PHP
Ubuntu 13.10 2.4.0 (c82fcd8e6199c500454b1a8b930a876ef2c1522b) 5.5.3

I got different result with array_uintersect_uassoc between PHP and HHVM while working on @atoum.

In @atoum, we use array_uintersect_uassoc to compare arrays with the same callback used to compare values and keys. This callback only will return only two values : 0 if values (or keys) are equal (==) and -1 if they are different.

With PHP, this works fine but with HHVM, the keys comparaison seems to fail. I had to write a dedicated callback for keys which returns 3 values : 0 if values are equal, -1 if $a is lower than $b and 1 otherwise.

I reproduced this use case in a tiny PHP test (I don't know if I should commit this one and where I should put it but I would be glad to open a PR):

<?php

class Dummy
{
    public function __toString()
    {
        return '<' . gettype($this) . ' ' . __CLASS__ . ' #' . spl_object_hash($this) . '>';
    }
}

$data = array(
    array($a = uniqid(), $b = uniqid(), $c = uniqid()),
    array($d = rand(0, PHP_INT_MAX), $e = rand(0, PHP_INT_MAX), $f = rand(0, PHP_INT_MAX)),
    array($g = new Dummy, $h = new Dummy, $i = new Dummy),
    array($a, $d, $g)
);

$callbacks = array(
    array(
        $cb = function($a, $b) {
            return ($a == $b ? 0 : -1);
        },
        $cb
    ),
    array(
        $cb,
        function($a, $b) {
            return ($a == $b ? 0 : ($a < $b ? -1 : 1));
        }
    )
);

foreach ($data as $index => $case) {
    foreach ($callbacks as $callback) {
        list($callback, $key_callback) = $callback;
        $actual = array_uintersect_uassoc($case, $case, $callback, $key_callback);

        if ($case !== $actual) {
            echo 'FAIL' . PHP_EOL;
            echo 'Expected: ' . implode(', ', $case) . PHP_EOL;
            echo 'Actual: ' . implode(', ', $actual) . PHP_EOL;
        } else {
            echo 'OK' . PHP_EOL;
        }
    }

    echo '---' . PHP_EOL;
}

Running this script, we get the following results :

# PHP 5.5.3 (EXPECTED)
$ php test.php
OK
OK

---
OK
OK

---
OK
OK

---
OK
OK

---

# HHVM 2.4.0 (ACTUAL)
$ hhvm test.php
FAIL
Expected: 52eecc8381d52, 52eecc8381e4d, 52eecc8382c4f
Actual: 52eecc8381e4d, 52eecc8382c4f
OK

---
FAIL
Expected: 6535625504957923328, 7741984395661148160, 3516897415862943744
Actual: 7741984395661148160, 3516897415862943744
OK

---
FAIL
Expected: <object Dummy #000000000000000057f584c1e2752d78>, <object Dummy #000000000000000057f584c1e2752ca8>, <object Dummy #000000000000000057f584c1e2752c88>
Actual: <object Dummy #000000000000000057f584c1e2752ca8>, <object Dummy #000000000000000057f584c1e2752c88>
OK

---
FAIL
Expected: 52eecc8381d52, 6535625504957923328, <object Dummy #000000000000000057f584c1e2752d78>
Actual: 6535625504957923328, <object Dummy #000000000000000057f584c1e2752d78>
OK

---

It seems there is the same inconsistency with array_intersect_uassoc.

scannell commented 10 years ago

(CC @paroski)

Thanks for reporting this. This is an intentional difference. We make the assumption, for performance reasons, that sort comparator functions are transitive.

In other words, if $a < $b and $b < $c then $a < $c. Your first callback violates this constraint, since if $a, $b and $c are all distinct then $a < $b and $b < $c and $c < $a.

Your second callback is transitive so we end up performing the comparisons you expect.