2018/08/30にPHP7.3.0β3がリリースされたのでNEWSを眺めていたところ、なんか意味のわからないバグが色々あったので取り上げてみます。
Crash in ZEND_COALESCE_SPEC_TMP_HANDLER
BetterReflectionを動かすとクラッシュするんだが、という内容。
ミニマム再現条件はこう。
$obj = new stdClass;
$val = 'foo';
$obj->prop = &$val;
var_dump($obj->prop ?? []);
PHP7.3.0のα2からβ2の間だけ&string
になっています。
さらに、意味があるようには見えない?? []
を削除すると発生しません。
なんなんだこれ。
まあ参照渡しなんて使うなってことだ。
parent private constant in extends class memory leak
class FOO{
private const FOO = 'BAR';
}
class BAR extends FOO {}
何の変哲も無いただの定数ですが、実はこの定数がメモリリークしていたとかいうバグ。
原因はハッシュテーブル破棄の位置をほんのり間違っていたというもの。
あまり詳しく読んでないのでわからないのだが、同じものが317行目にもあるのだがこちらは大丈夫なのだろうか?
iconv_mime_decode can return extra characters in a header
長い文字列にiconv_mime_decodeすると正しくデコードされないことがあるというバグ。
バグチケットに添付されているサンプルはこんなかんじです。
$original = "=?UTF-8?Q?=E3=80=8E=E3=80=90=E5=A4=96=E8=B3=87=E7=B3=BB=E6=88=A6=E7=95=A5=E3=82=B3=E3=83=B3=E3=82=B5=E3=83=AB=E3=81=8C=E9=9B=86=E7=B5=90=E3=80=91=E3=83=88=E3=83=83=E3=83=97=E3=82=B3=E3=83=B3=E3=82=B5=E3=83=AB=E3=82=BF=E3=83=B3=E3=83=88=E3=81=A8=E8=A9=B1=E3=81=9B=E3=82=8B=E3=82=B3=E3=83=B3=E3=82=B5=E3=83=AB=E6=A5=AD=E7=95=8C=E7=A0=94=E7=A9=B6=E3=82=BB=E3=83=9F=E3=83=8A=E3=83=BC=E3=80=8F=E3=81=B8=E3=81=AE=E3=82=A8=E3=83=B3=E3=83=88=E3=83=AA=E3=83=BC=E3=81=82=E3=82=8A=E3=81=8C=E3=81=A8=E3=81=86=E3=81=94=E3=81=96=E3=81=84=E3=81=BE=E3=81=97=E3=81=9F=E3=80=82?=";
$decoded = iconv_mime_decode($original, ICONV_MIME_DECODE_STRICT, 'utf-8');
$prefs = array('input-charset' => 'UTF-8', 'output-charset' => 'UTF-8', 'scheme' => 'Q', 'line-length' => 1000);
$encoded = iconv_mime_encode('Subject', $decoded, $prefs);
list($_, $encoded) = explode(' ', $encoded, 2);
echo 'Before: ' . strlen($original) . ', After: ' . strlen($encoded) . "\n";
バージョンによって文字数がバラバラですね。
日本語環境であれば、デコードした後に再エンコードせずともデコード文字列を直接見ればいいです。
なんだこのSPAMっぽいタイトル。
それはそうとデコードした文字列の後ろに変なものが入っていますね。
_php_iconv_appendlのバグで文字列長が正しく計算されておらず、最後に変なゴミがくっついてしまっていたようです。
iconv_mime_decode_headers() skips some headers
iconv_set_encoding('iconv.internal_encoding', 'UTF-8');
iconv_set_encoding('iconv.input_encoding', 'UTF-8');
iconv_set_encoding('iconv.output_encoding', 'UTF-8');
$headers = <<< HEADERS
X-Header-One: H4sIAAAAAAAAA+NgFlsCAAA=
X-Header-Two: XtLePq6GTMn8G68F0
HEADERS;
echo "Wrong decoding:\n";
var_dump(iconv_mime_decode_headers($headers, ICONV_MIME_DECODE_CONTINUE_ON_ERROR));
echo "Correct decoding:\n";
var_dump(iconv_mime_decode_headers($headers, ICONV_MIME_DECODE_STRICT));
前者はエラーが出ても処理を継続するICONV_MIME_DECODE_CONTINUE_ON_ERRORオプション、後者は正しいヘッダしか受け付けないICONV_MIME_DECODE_STRICTを指定しているのだが、
結果はどう見ても逆になっている。
ところでiconv_set_encodingのマニュアルにはinternal_encoding
の設定がDeprecatedであることが書かれていない。
かわりにiconv.internal_encoding
を入れるとエラーが出ずに設定できるのだが、この書き方はPHP5.6.0で非推奨になったと書かれている。
いったいどうすればいいんだ。
iconv_mime_decode_headers function is skipping headers
iconv_mime_decode_headers関数において、Subjectに=?
という文字列があるとそれ以降のフィールドが全て消滅することがあるというバグ。
<?php
$headers = 'From: "xyz" <xyz@xyz.com>
To: <xyz@xyz.com>
Subject: Reply Is? white side-LED =? in Help
Date: Sat, 22 Dec 2012
Message-ID: <006f01cde00e$d9f79da0$8de6d8e0>
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="----=_NextPart_000_0070_01CDE03C.F3AFD9A0"
X-Mailer: Microsoft Office Outlook 12.0
Thread-Index: Ac3gDtcH2huHjzYcQVmFJPPoWjJogA==
Content-Language: en-us
';
$headers = iconv_mime_decode_headers($headers, ICONV_MIME_DECODE_CONTINUE_ON_ERROR);
print_r($headers);
Subject
のside-LED
より後が全部消えています。
修正のプルリクがものすごい場当たり的なものにしか見えないので大丈夫なのだろうかと思ったのですが、ソースを見てみたらswitchが芸術品だったので読むのを諦めました。
iconvは全部で5個のバグフィックスが行われていますが、この勢いを見るにまだまだバグが出てきそうな気がしますね。
"public id" parameter of libxml_set_external_entity_loader callback undefined
libxml_set_external_entity_loader関数は、引数に三つの引数をとるcallableを受け取ります。
が、何故か第一引数が未定義になるというバグ。
$xml=<<<EOF
<?xml version="1.0"?>
<test/>
EOF;
$xsd=<<<EOF
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:include schemaLocation="nonexistent.xsd"/>
<xs:element name="test"/>
</xs:schema>
EOF;
libxml_set_external_entity_loader(function($p,$s,$c) {
var_dump($p,$s,$c);
die();
});
$dom=new DOMDocument($xml);
var_dump($dom->schemaValidateSource($xsd));
マニュアルには「はじめの二つは文字列でそれぞれ パブリックID とシステムID 」とありますが、パブリックIDが存在しないときに何故かnullではなくUndefined variableになっています。
そもそもlibxml関数自体使ったことないのだが、いったいどういう需要があるのだろうか、と思っていたら各種XMLモジュールがlibxmlに依存してるみたい。
あと、そんなことよりini_set('error_reporting', PHP_INT_MAX-1)
とかいう設定方法初めて見た。
Opcache treats path containing "test.pharma.tld" as a phar file
OpCacheを有効にすると、名前にphar
が入ったディレクトリでstream_wrapper_unregister('phar')した後でrequire
できなくなるバグ。
stream_wrapper_unregister('phar');
require 'info.php'; // Warning: require(): Unable to find the wrapper "phar"
ディレクトリ名をtest.blabla.tld
に変更したら起きなくなったということです。
再現環境はDockerで用意されていますが、手元のXAMPP環境(Windows10・PHP7.2)では何故か再現しませんでした。
Assignment of empty string creates extraneous text node
SimpleXMLにおいて、addChildのやりかたによってできあがるXMLが何故か異なるというバグ。
$sxe = new SimpleXMLElement('<foo></foo>');
$sxe->addChild('bar');
echo $sxe->asXML(); // <foo><bar/></foo>
$sxe = new SimpleXMLElement('<foo></foo>');
$sxe->addChild('bar', '');
echo $sxe->asXML(); // <foo><bar/></foo>
$sxe = new SimpleXMLElement('<foo></foo>');
$sxe->addChild('bar');
$sxe->bar = '';
echo $sxe->asXML(); // <foo><bar></bar></foo>
barの要素は最初だけがnull、2番目3番目は空文字列になるはずですが、出力XMLは何故か3番目だけが異なっています。
$dom = new DOMDocument();
$dom->appendChild($dom->createElement("foo"));
echo $dom->saveXML(); // <foo/>
$dom = new DOMDocument();
$dom->appendChild($dom->createElement("foo", ""));
echo $dom->saveXML(); // <foo/>
$dom = new DOMDocument();
$dom->appendChild($foo = $dom->createElement("foo"));
$foo->textContent = "";
echo $dom->saveXML(); // <foo/>
$dom = new DOMDocument();
$dom->appendChild($foo = $dom->createElement("foo"));
$foo->nodeValue = "";
echo $dom->saveXML(); // <foo></foo>
実際のところこれが何か問題を引き起こすとは到底思えないし、そもそも仕様上はほぼ同じなのでどちらでもいいのですが、やり方によって結果が異なるのは気持ち悪いですね。
ということで空要素タグに統一されました。
でも修正はSimpleXMLにしか行われてないように見えるんだけど大丈夫なのだろうか。
RegexIterator pregFlags are NULL instead of 0
RegexIterator::getPregFlagsが0を返すべき時にnullを返すバグ。
$arr = new ArrayIterator([]);
$regex = new RegexIterator($arr, '/^test/');
var_dump($regex->getMode(), $regex->getFlags(), $regex->getPregFlags()); // 0, 0, NULL
そもそも正規表現フラグってなんだよってかんじですが、コンストラクタの第5引数で渡すフラグです。
原因はreturn;って書いてたからという単純なものだったので修正も1行で終わっています。
array_reduce leaks memory if callback throws exception
array_reduceがメモリリークしているバグ。
for ($i = 0; $i < 1000; $i++) {
try {
array_reduce(
[1],
function ($carry, $item) {
throw new Exception;
},
range(1, 200000)
);
} catch (Exception $e) {
}
}
原因は参照解放してなかったというよくあるやつです。
PHPはメモリ関連の話題をほぼ全く気にせずに使うことができますが、それはPHP本体が裏で確保や管理や解放を色々やってくれているからです。
全くありがたいことですね。
setcookie does not accept "double" type for expire time
setcookieの第三引数にlongを渡すとE_WARNINGが出るバグ。
setcookie('name', 'value', (real)(time() + 1296000));
7.3.0β1とβ2でだけ発生するというレアなバグです。
α4までは起こらなかったのにβ1で発生した理由はというとSame Site CookieというRFCです。
しかしSame Site Cookie自体は原因ではなく、引数の与え方が変わったのが原因です。
元々setcookieはsetcookie($name, $value, $expires, $path, $domain, $secure, $httponly)
と引数を並べて与えていました。
しかし、ここからさらにSame Site Cookieフラグ引数を追加するととても残念なかんじになります。
そこでRFCでは、引数を配列でも与えることができるようにしようと提案されました。
RFCではsetcookie($name, $value, $expires, $options)
と第4引数に配列を渡すようになっていましたが、実際はβ1で第三引数に配列を渡すことができるように実装されました。
setcookie('hoge', 'fuga', [
'expires' => 3600,
'path' => '/',
'secure' => true,
'httponly' => true,
"samesite" => 'Strict',
]);
こっちのほうがわかりやすいのでよかったと思いますが、このときに引数の型の扱い方が変わったためエラーが出るようになりました。
そもそもexpiresの値はintなのでエラーが出る方が正しいと思うのですが、これまでの動作に合わせる方を選んだようです。
Error message should be more detailed
配列関数のエラーメッセージがおかしいという指摘。
PHPのエラーメッセージはめったらやたら親切で、引数の型が合わない場合はXX expects parameter 1 to be XX, XX given
のように正しい型と与えられた型まで出してくれます。
なのですが、何故か配列関数に限ってはArgument #1 is not an array
のようにメッセージが省エネです。
ということで他のエラーメッセージに合わせるように修正されました。
感想
はっきり言って、普段書くようなコードには全く影響の無いようなバグがほとんどです。
しかし、そんな重箱の隅のようなものでも地道になおしてくれる人たちのおかげで、かつてはバグの宝庫と言われていたPHPも、非常に安定して運用できるまでに成長してきました。
誠にありがたいことです。
まあ、かつてのカオスだったPHPも嫌いじゃないですが、さすがに今となっては10リクエスト毎にApache再起動というわけにもいかないでしょう。