LoginSignup
12
5

More than 5 years have passed since last update.

PHP7.3.0β3で修正されたバグたち

Last updated at Posted at 2018-09-06

7.3の新機能 / 7.3.0β3のバグ

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 ?? []);

01.png

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";

02.png

バージョンによって文字数がバラバラですね。

日本語環境であれば、デコードした後に再エンコードせずともデコード文字列を直接見ればいいです。

03.png

なんだこの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を指定しているのだが、

04.png

結果はどう見ても逆になっている。

ところで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);

05.png

Subjectside-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));

07.png

マニュアルには「はじめの二つは文字列でそれぞれ パブリック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できなくなるバグ。

/var/www/test.pharma.tld/test.php
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番目だけが異なっています。

実はSimpleXMLだけではなくDOMでも発生します

$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));

08.png

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のように正しい型と与えられた型まで出してくれます。

09.png

なのですが、何故か配列関数に限ってはArgument #1 is not an arrayのようにメッセージが省エネです。

10.png

ということで他のエラーメッセージに合わせるように修正されました

感想

はっきり言って、普段書くようなコードには全く影響の無いようなバグがほとんどです。

しかし、そんな重箱の隅のようなものでも地道になおしてくれる人たちのおかげで、かつてはバグの宝庫と言われていたPHPも、非常に安定して運用できるまでに成長してきました。
誠にありがたいことです。

まあ、かつてのカオスだったPHPも嫌いじゃないですが、さすがに今となっては10リクエスト毎にApache再起動というわけにもいかないでしょう。

12
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
5