LoginSignup
8
8

More than 5 years have passed since last update.

PHPのcatch句による代入が参照を無視するバグ

Last updated at Posted at 2016-07-20

概要

ほとんど気にならないレベルだけど一応バグです…

#72629 Caught exception assignment to variables ignores references

<?php

$var = null;
$e = &$var;

try {
    throw new Exception;
} catch (Exception $e) { }

var_dump($var === $e); // bool(false)

報告から10分で修正されました!laruence氏仕事速すぎワロタ

Fixed bug #72629 (Caught exception assignment to variables ignores references)

困りそうなとき

cURLによるリクエストが内在するコルーチンをジェネレータで処理するmpyw/coというライブラリがあるんですが,それを利用して「ある処理が終わった時に止めたいタイマー」のような物を作りたくて,こういうコードを書いたんです.

use mpyw\Co\Co;
use mpyw\Co\CURLException;

class TerminatedException extends \RuntimeException {}

Co::wait(function () {
    try {
        yield [timer($e), main()];
    } catch (TerminatedException $e) {
        var_dump('Terminated.');
    }
});

function curl_init_with($url, array $options = [CURLOPT_RETURNTRANSFER => true])
{
    $ch = curl_init($url);
    curl_setopt_array($ch, $options);
    return $ch;
}

function timer(&$e)
{
    $ms = 0;
    while (true) {
        yield CO::DELAY => 0.2;
        if ($e) return;
        $ms += 200;
        echo "[Timer]: $ms miliseconds passed\n";
    }
}

function main()
{
    var_dump(array_map('strlen', yield [
        'Content-Length of github.com' => curl_init_with('https://github.com/mpyw'),
        'Content-Length of twitter.com' => curl_init_with('https://twitter.com/mpyw'),
    ]));
    throw new TerminatedException;
}

これでちゃんとタイマーが止まってくれるかと思ったんですが,スローされた後も止まらないんですね.ここでバグ気づいて

     try {
         yield [timer($e), main()];
-    } catch (TerminatedException $e) {
+    } catch (TerminatedException $_) {
+        $e = $_;
         var_dump('Terminated.');
     }

としたところ直りました…

「そもそもスローされた段階ですべてのyield中の処理を止めるべきじゃないの?」と思われる方もいるかもしれませんが,JavaScriptにおけるtj/coでも同じような挙動だったので,そこまでする必要はないとしてこれに関する修正はやめました.

ただし,ルートレベル,すなわちCo::wait()のコールスタックまで這い上がってきた例外に関しては,オプションでthrowが無効にされていない場合には,即座にスローするように変更しました.これを行っておかないと,使われない返り値のためにすべての処理が遅延させられることになるので.なお,ルートレベルでないものに関しては元から特に問題はありません.

Generator does not stop in spite of RuntimeException thrown into parent scope #11

余談

そもそも例外使う必要なかったですねw

-   function main()
+   function main(&$e)
    {
        var_dump(array_map('strlen', yield [
            'Content-Length of github.com' => curl_init_with('https://github.com/mpyw'),
            'Content-Length of twitter.com' => curl_init_with('https://twitter.com/mpyw'),
        ]));
-       throw new TerminatedException;
+       $e = true;
    }

こういうふうにして,呼び出し側で

yield [timer($e), main($e)];

として変数$eを共有するだけで良さそうです.

8
8
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
8
8