PHP5.5.16と5.4.32でarrayのバグが修正された
TOP > てきとうにこらむ > ゲーム作りとプログラミング日記 > 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)
}