带有调试输出的 X3 解析器段错误 (BOOST_SPIRIT_X3_DEBUG)

X3 parser segfaults with debug output (BOOST_SPIRIT_X3_DEBUG)

更新

This question touches on two issues (as shown by the ), both of which are present in the version of Boost Spirit X3 that ships with Boost 1.64, but both of which are now fixed (or at least detected at compile time) in develop branch at the time of writing (2017-04-30).

I have updated the mcve project to reflect the changes that I made to use the develop branch instead of the latest boost release, in the hopes that it might help out others who face a similar issues.


原题

我正在尝试学习如何将 Spirit X3 解析器分解为单独的可重用语法,正如 example code (rexpr_full and calc in particular) and the presentations from CppCon 2015 and BoostCon 所鼓励的那样。

我有一个符号 table(本质上是将不同类型映射到我支持的类型的枚举 class),我想在多个解析器中重用它。我能找到的符号 tables 的唯一示例是罗马数字示例,它位于单个源文件中。

当我尝试以更结构化示例的样式将符号 table 移动到它自己的 cpp/h 文件中时,如果我尝试解析任何不在符号 table。如果符号 table 在与使用它的解析器相同的编译单元中定义,则会抛出预期异常(这是我希望它做的)。

定义 BOOST_SPIRIT_X3_DEBUG 我得到以下输出:

<FruitType>
  <try>GrannySmith: Mammals</try>
  <Identifier>
    <try>GrannySmith: Mammals</try>
    <success>: Mammals</success>
    <attributes>[[
Process finished with exit code 11

我做了一个小项目,展示了我正在努力实现的目标,可以在这里找到:https://github.com/sigbjornlo/spirit_fruit_mcve

我的问题:

以下是 MCVE 项目的代码:

main.cpp

#include <iostream>

#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

#include "common.h"
#include "fruit.h"

namespace ast {
    struct FruitType {
        std::string identifier;
        FRUIT fruit;
    };
}

BOOST_FUSION_ADAPT_STRUCT(ast::FruitType, identifier, fruit);

namespace parser {
    // Identifier
    using identifier_type = x3::rule<class identifier, std::string>;
    const auto identifier = identifier_type {"Identifier"};
    const auto identifier_def = x3::raw[x3::lexeme[(x3::alpha | '_') >> *(x3::alnum | '_')]];
    BOOST_SPIRIT_DEFINE(identifier);

    // FruitType
    struct fruit_type_class;
    const auto fruit_type = x3::rule<fruit_type_class, ast::FruitType> {"FruitType"};

    // Using the sequence operator creates a grammar which fails gracefully given invalid input.
    // const auto fruit_type_def = identifier >> ':' >> make_fruit_grammar();

    // Using the expectation operator causes EXC_BAD_ACCESS exception with invalid input.
    // Instead, I would have expected an expectation failure exception.
    // Indeed, an expectation failure exception is thrown when the fruit grammar is defined here in this compilation unit instead of in fruit.cpp.
    const auto fruit_type_def = identifier > ':' > make_fruit_grammar();

    BOOST_SPIRIT_DEFINE(fruit_type);
}

int main() {
    std::string input = "GrannySmith: Mammals";
    parser::iterator_type it = input.begin(), end = input.end();

    const auto& grammar = parser::fruit_type;
    auto result = ast::FruitType {};

    bool successful_parse = boost::spirit::x3::phrase_parse(it, end, grammar, boost::spirit::x3::ascii::space, result);
    if (successful_parse && it == end) {
        std::cout << "Parsing succeeded!\n";
        std::cout << result.identifier << " is a kind of " << to_string(result.fruit) << "!\n";
    } else {
        std::cout << "Parsing failed!\n";
    }

    return 0;
}

std::string to_string(FRUIT fruit) {
    switch (fruit) {
        case FRUIT::APPLES:
            return "apple";
        case FRUIT::ORANGES:
            return "orange";
    }
}

common.h

#ifndef SPIRIT_FRUIT_COMMON_H
#define SPIRIT_FRUIT_COMMON_H

namespace x3 = boost::spirit::x3;

enum class FRUIT {
    APPLES,
    ORANGES
};

std::string to_string(FRUIT fruit);

namespace parser {
    using iterator_type = std::string::const_iterator;
    using context_type = x3::phrase_parse_context<x3::ascii::space_type>::type;
}

#endif //SPIRIT_FRUIT_COMMON_H

fruit.h

#ifndef SPIRIT_FRUIT_FRUIT_H
#define SPIRIT_FRUIT_FRUIT_H

#include <boost/spirit/home/x3.hpp>

#include "common.h"

namespace parser {
    struct fruit_class;
    using fruit_grammar = x3::rule<fruit_class, FRUIT>;

    BOOST_SPIRIT_DECLARE(fruit_grammar)

    fruit_grammar make_fruit_grammar();
}


#endif //SPIRIT_FRUIT_FRUIT_H

fruit.cpp

#include "fruit.h"

namespace parser {
    struct fruit_symbol_table : x3::symbols<FRUIT> {
        fruit_symbol_table() {
            add
                    ("Apples", FRUIT::APPLES)
                    ("Oranges", FRUIT::ORANGES);
        }
    };

    struct fruit_class;
    const auto fruit = x3::rule<fruit_class, FRUIT> {"Fruit"};
    const auto fruit_def = fruit_symbol_table {};
    BOOST_SPIRIT_DEFINE(fruit);

    BOOST_SPIRIT_INSTANTIATE(fruit_grammar, iterator_type, context_type);

    fruit_grammar make_fruit_grammar() {
        return fruit;
    }
}

复制器做得很好。这让我想起了我的 PR https://github.com/boostorg/spirit/pull/229 (see analysis here ).

问题是静态初始化顺序 Fiasco 在规则初始化之前复制了规则的调试名称。

事实上,确实禁用调试信息确实消除了崩溃,并正确抛出预期失败。

develop 分支 ¹ 也发生了同样的情况,所以要么有另一个类似的事情,要么我错过了一个地方。现在,知道您可以禁用调试输出。如果找到地点,我会 post 更新。

更新:

我没有错过任何地方。 call_rule_definition 中有一个单独的问题,它使用实际属性类型而不是转换后的属性类型参数化 context_debug<> 助手 class:

#if defined(BOOST_SPIRIT_X3_DEBUG)
                typedef typename make_attribute::type dbg_attribute_type;
                context_debug<Iterator, dbg_attribute_type>
                dbg(rule_name, first, last, dbg_attribute_type(attr_), ok_parse);
#endif

该评论似乎表明此行为符合要求:它尝试在转换前打印属性。但是,除非合成属性类型与实际属性类型匹配,否则它完全不起作用。在这种情况下,它使 context_debug 对临时转换的属性采用悬空引用,从而导致 Undefined Behaviour.

It's in fact also undefined behaviour in the working cases. I can only assume things happen to pan out nicely in the inline-definition case, making it seem like things work as intended.

据我所知,这将是一个干净的修复程序,可以防止任何不必要的转换以及随之而来的临时文件:

#if defined(BOOST_SPIRIT_X3_DEBUG)
                context_debug<Iterator, transform_attr>
                dbg(rule_name, first, last, attr_, ok_parse);
#endif

我为此创建了拉取请求:https://github.com/boostorg/spirit/pull/232


¹ develop branch doesn't seem merged into the 1.64 发布