読者です 読者をやめる 読者になる 読者になる

Spirit.Qiのパーサーをautoで受けると吹っ飛ぶ問題が解決した

C++ GCC

GCCにバグ報告:最適化オプションを付けるとBoost.Spiritのプログラムが吹っ飛ぶ

先日GCCにバグ報告した件ですが、GCCのバグではありませんでした。@k_satodaさんに教えてもらいました。

Boost.Spirit.Qiが内部で使っているBoost.Protoの式テンプレートが、引数を(一時オブジェクトであっても)コピーせずに参照で持ってるのが原因だそうです。

http://twitter.com/k_satoda/status/420606068025069568

パーサー式をautoで受けると、パーサー式が持ってる参照がシャローコピーされ、寿命が尽きた一時オブジェクトへの参照が発生して、未定義動作になります。なので、qi::ruleを使わずautoで受ける場合は、boost::proto::deep_copy()関数でパーサー式をラップして、パーサー式をディープコピーする必要があります。

#include <iostream>
#include <string>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;
namespace proto = boost::proto;

int main()
{
    const std::string input = "hello:";

    auto rule = proto::deep_copy(*(qi::char_ - ':') >> ':');

    std::string::const_iterator it = input.begin();
    std::string result;
    if (qi::parse(it, input.end(), rule, result)) {
        std::cout << "parse successful" << std::endl;
    }
    else {
        std::cout << "parse failed" << std::endl;
    }
}

とりあえず、先日のバグ報告は、私のほうで取り下げました。

このことはBoost.Spiritの公式ブログにも載っています。

Zero to 60 MPH in 2 seconds! - Spirit

これはC++11のautoが考慮されていないBoost.Spirit V2の問題で、これに対処するために、Boost 1.55.0後のtrunkには、Spiritのディレクトリに、BOOST_SPIRIT_AUTOというマクロが定義されています(<boost/spirit/include/support_auto.hpp><boost/spirit/home/support/auto.hpp>をインクルードする)。

BOOST_SPIRIT_AUTO(qi, comment, "/*" >> *(char_ - "*/") >> "*/");

このマクロは内部で、パーサー式をディープコピーしてcommentという変数に代入します。

しかしこんなマクロは書きたくないので、現在開発中のBoost.Spirit X3では、この問題を解決してC++11のautoをふつうに使えるようにする予定だそうです。

参照