てきとうなさいと べぇたばん

PHP5.5.16と5.4.32でarrayのバグが修正された

PHP 5.5.16、5.4.32がリリースされた

PHP 5.5.16と5.4.32がリリースされたが、changelogを覗いてみると、Coreに#67693 incorrect push to the empty arrayというバグがあったようだ。面白そうだったので覗いてみた。

どういうバグなのか

arrayのインデックスに-1を入れてarray_popをした後にarray_pushをすると、覚えのない値がインデックスになってる。

Test scriptのままやってみる。ubuntu 14.04 64bit PHP 5.5.9での環境。

<?php
$b=array(-1=>0);
$c=array_pop($b); assert($c == 0);

assert(array_push($b, 0), 1);
assert(array_push($b, 0), 2);
var_dump($b);
assert(array_push($b, 0), 3);
?>

array(2) {
  [-1]=>
  int(0)
  [9223372036854775807]=>
  int(0)
}
PHP Warning:  array_push(): Cannot add element to the array as the next element is already occupied in /home/tekitoh/php/test.php on line 11
PHP Warning:  assert(): 3 failed in /home/tekitoh/php/test.php on line 11

oh...

どうしてこうなった

_phpi_popという関数とのことで、gdbでデバッグしてみた。

(gdb) print stack->value.ht
$4 = (HashTable *) 0x7ffff7fe4728
(gdb) print stack->value.ht->nNextFreeElement
$5 = 0
(gdb) n
1988            } else if (!key_len && index >= Z_ARRVAL_P(stack)->nNextFreeElement - 1) {
(gdb) n
1989                    Z_ARRVAL_P(stack)->nNextFreeElement = Z_ARRVAL_P(stack)->nNextFreeElement - 1;
(gdb) print stack->value.ht->nNextFreeElement
$6 = 0
(gdb) n
1992            zend_hash_internal_pointer_reset(Z_ARRVAL_P(stack));
(gdb) print stack->value.ht->nNextFreeElement
$7 = 18446744073709551615
(gdb) list
1987                    }
1988            } else if (!key_len && index >= Z_ARRVAL_P(stack)->nNextFreeElement - 1) {
1989                    Z_ARRVAL_P(stack)->nNextFreeElement = Z_ARRVAL_P(stack)->nNextFreeElement - 1;
1990            }
1991
1992            zend_hash_internal_pointer_reset(Z_ARRVAL_P(stack));
1993    }
1994    /* }}} */
1995
1996    /* {{{ proto mixed array_pop(array stack)
(gdb)

array_popをする際に、要素数が0でかつインデックスがnNextFreeElement-1を上回る場合にnNextFreeElementを1減らすという処理をしている。しかし、nNextFreeElementが0だとnNextFreeElement-1はulong(unsigened long)なため、桁あふれによりnNextFreeElementが18446744073709551615(64bit)という値になってしまう。

ここから更にarray_pushするのだが、挿入するインデックスはnNextFreeElementが元(ちょっとわかってないがnTableMaskとのAND演算?)になる。ところがLONG_MAXより上の数値を指定することが出来ないので追加できなくなってしまう。ということでいいのかな。

対処

Z_ARRVAL_P(stack)->nNextFreeElementは、本来であれば負の数を扱えるlongとすべきかもしれないが、その変更だとどの部分に影響があるかわからないほどの大規模な修正になってしまう。というわけで、nNextFreeElementが0より多い場合のみに限定しているということらしい。

結果、修正されている。

~/src/php-5.5.16$ sapi/cli/php ~/php/test.php
array(2) {
  [0]=>
  int(0)
  [1]=>
  int(0)
}