読者です 読者をやめる 読者になる 読者になる

イテレータオブジェクトのバグを再現した

PHP

PHP5.3.6ではこの問題が発生しなくなっていました。

きのうの http://d.hatena.ne.jp/tanakahisateru/20090827/1251379409 を再現できるコードをのっけときます。

<?php
ini_set("display_errors", "1");
error_reporting(E_ALL);

header("Content-Type:text/plain");

class MyArrayObject implements IteratorAggregate {
    private $arr;
    public function __construct() {
        $this->arr = func_get_args();
    }
    public function getat($i) {
        return $this->arr[$i];
    }
    public function len() {
        return count($this->arr);
    }
    public function getIterator() {
        return new MyArrayObjectIterator($this);
    }
}
class MyArrayObjectIterator implements Iterator {
    private $self;
    private $position;
    public function __construct($self) { $this->self = $self; $this->position = 0; }
    function rewind()  { $this->position = 0; }
    function current() { return $this->self->getat($this->position); }
    function key()     { return $this->position; }
    function next()    { ++$this->position; }
    function valid()   { return $this->position < $this->self->len(); }
}
function myarray($a,$b,$c){
    return new MyArrayObject($a,$b,$c);
}

echo "test1\n";
$vs = myarray(1,2,3);
foreach($vs as $v) {
    echo $v; // OK: 123 が表示される
}
echo "\n\n";

echo "test2\n";
foreach(myarray(1,2,3) as $v) {
    echo $v; // OK: 123 が表示される
}
echo "\n\n";

echo "test3\n";
$cond = TRUE;
$vs = $cond ? myarray(1,2,3) : array(4,5,6);
foreach($vs as $v) {
    echo $v; // OK: 123 が表示される
}
echo "\n\n";

echo "test4-1 *bug:123 expected\n";
$cond = TRUE;
foreach(($cond ? myarray(1,2,3) : array(4,5,6)) as $v) {
    echo $v; // BUG: ここ通らない
}
echo "\n\n";

echo "test4-2 *bug:123 expected\n";
$cond = TRUE;
$obj = myarray(1,2,3);
$obj->p0 = 'foo';
$obj->p1 = 'bar';
foreach(($cond ? $obj : array(4,5,6)) as $v) {
    echo $v; // BUG: fooとbarが出る
}
echo "\n\n";
// 構文の第一要素について、式の最上位が演算になっている場合、その評価結果が Traversible であっても、
// 無視して一般のオブジェクトと同じように処理される

echo "test5\n";
function a_or_b($cond, $a, $b) {
    return $cond ? $a : $b;
}
$cond = TRUE;
foreach(a_or_b($cond, myarray(1,2,3), array(4,5,6)) as $v) {
    echo $v; // OK: 123 が表示される
}
echo "\n\n";

echo "test6\n";
$vss = array(myarray(1,2,3), array(4, 5, 6));
$cond = TRUE;
foreach($vss[intval(!($cond == TRUE))] as $v) {
    echo $v; // OK: 123 が表示される
}
echo "\n\n";
// 構文の第一要素が複雑な式であったとしても、最上位が「演算」でなければよい
?>

配列やオブジェクトを出力する演算って、いまのところPHPでは c?a:b の三項演算だけ?でももし、将来オブジェクトに演算したときの振る舞いをユーザ定義できるようになったら…。