带有调试输出的X3解析器segfaults(boost_spirit_x3_debug)

X3 parser segfaults with debug output (BOOST_SPIRIT_X3_DEBUG)

本文关键字:boost spirit x3 debug segfaults 输出 调试 X3      更新时间:2023-10-16

更新

这个问题涉及两个问题(如被接受的答案所示(,这两者都存在于Boost Spirit X3的版本中,该版本以Boost 1.64发射运行,但现在两者都固定(或至少在Compile中检测到时间(在撰写本文时(2017-04-30(。

我已经更新了MCVE项目,以反映我为使用开发分支而不是最新的Boost版本所做的更改,希望它可以帮助您面临类似问题的其他人。


原始问题

我正在尝试学习如何将X3解析器分解为单独的可重复使用的语法,如示例代码(尤其是REXPR_FULL和CALC(的鼓励,以及CPPCON 2015和BOOSTCON的演示。

我有一个符号表(本质上将不同类型的类型映射到我支持的类型的枚举类别(,我想在几个解析器中重复使用。我能找到的符号表的唯一示例是Roman Numerals示例,该示例位于一个源文件中。

当我尝试以更结构化的示例的样式将符号表移动到自己的CPP/H文件中时,如果我尝试解析任何不在符号表中的字符串,我的解析器就会segfault。如果符号表是在与使用它的解析器的同一汇编单元中定义的,则会引发期望异常(这是我期望它会做的(。

使用BOOstrongPIRIT_X3_DEBUG定义,我将获得以下输出:

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

我做了一个小型项目,该项目显示了我要实现的目标,并且可以在此处提供:

我的问题:

  • 为什么在这种情况下,将符号解析器移动到单独的汇编单元会导致分段故障?
  • 在多个解析器中重复使用符号表的推荐方法是什么?(在MCVE中,我显然只在另一个解析器中使用fruit解析器,但是在我的完整项目中,我想在其他几个解析器中使用它。(

以下是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

水果.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(请参阅此处的分析,在此分析后,Boost Spirit X3的奇怪语义行为(。

问题将是静态初始化命令惨败在初始化之前的规则调试名称的副本。

实际上,确实禁用调试信息确实会删除崩溃,并正确丢弃了期望失败。

开发分支机构也发生了同样的事情,所以要么还有另一件类似的事情,要么我错过了一个位置。现在,知道您可以禁用调试输出。如果找到该点,我会发布更新。

更新:

我没有错过一个地方。call_rule_definition中有一个单独的问题,其中它以实际属性类型而不是转换的一个:

参数化context_debug<>辅助类别类别
#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对临时转换的属性进行悬空,从而导致不确定的行为。

实际上,在工作案例中,这也是不确定的行为。我只能假设在内联定义案例中碰巧碰到了,这看起来像是按预期工作的。

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

#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


¹开发分支似乎没有合并到1.64版本