ANTLR4解析器在_errHandler->sync()调用时崩溃问题咨询
问题背景
解析器语法规则如下:
tokens_statement_1 : TOK_01 | TOK_02 | TOK_03 ; tokens_statement_2 : TOK_04 | TOK_05 | TOK_06 ; rule_1 @init { // Some C++ code } : statement1=tokens_statement_1 { // Some C++ code } ( statement2=tokens_statement_2 { // Some C++ code } )* ;
当可选的tokens_statement_2部分缺失token时,解析器崩溃,报错信息:
terminate called after throwing an instance of 'std::out_of_range' what(): bitset::set: __position (which is 1138) >= _Nb (which is 1048)
通过gdb调试确认,崩溃发生在错误处理程序的sync方法中,位置在statement1=tokens_statement_1代码块之后,生成的C++代码片段显示崩溃点:
... setState(158); _errHandler->sync(this); // CRASHES HERE alt = getInterpreter<atn::ParserATNSimulator>()->adaptivePredict(_input, 94, _ctx); ...
核心疑问:是否需要重写ErrorStrategy来避免崩溃?DefaultErrorStrategy难道不应该自动处理这种token缺失的情况吗?
附加调用栈:
#5 0x00007fd734224713 in __cxa_throw () .../64bit/libboost_program_options-mt-d-x64.so.1.79.0 #6 0x00007fd73427dd35 in std::__throw_out_of_range_fmt(char const*, ...) () from libboost_program_options-mt-d-x64.so.1.79.0 #7 0x00007fd733bc2303 in std::bitset<2048ul>::_M_check (this=0x7ffda80e2660, __position=1138, __s=0x7fd733c92fae "bitset::set") at .../include/c++/7.3.0/bitset:769 #8 0x00007fd733bc227f in std::bitset<2048ul>::set (this=0x7ffda80e2660, __position=1138, __val=true) at .../include/c++/7.3.0/bitset:1083 #9 0x00007fd733c1b4d9 in (anonymous namespace)::LL1AnalyzerImpl::LOOK (this=0x7ffda80e2650, s=0x561d4521c710, stopState=0x0, ctx=...) at .../antlr_4_10_1_build/runtime/src/atn/LL1Analyzer.cpp:122 #10 0x00007fd733c1b5a3 in (anonymous namespace)::LL1AnalyzerImpl::LOOK (this=0x7ffda80e2650, s=0x561d4521c6a0, stopState=0x0, ctx=...) at .../antlr_4_10_1_build/runtime/src/atn/LL1Analyzer.cpp:128 #11 0x00007fd733c1b605 in (anonymous namespace)::LL1AnalyzerImpl::LOOK (this=0x7ffda80e2650, s=0x561d4521c9b0, stopState=0x0, ctx=...) at .../antlr_4_10_1_build/runtime/src/atn/LL1Analyzer.cpp:133 #12 0x00007fd733c1b605 in (anonymous namespace)::LL1AnalyzerImpl::LOOK (this=0x7ffda80e2650, s=0x561d450e3de0, stopState=0x0, ctx=...) at .../antlr_4_10_1_build/runtime/src/atn/LL1Analyzer.cpp:133 #13 0x00007fd733c1b4fe in (anonymous namespace)::LL1AnalyzerImpl::LOOK (this=0x7ffda80e2650, s=0x561d4521cf20, stopState=0x0, ctx=...) at .../antlr_4_10_1_build/runtime/src/atn/LL1Analyzer.cpp:123 #14 0x00007fd733c1b605 in (anonymous namespace)::LL1AnalyzerImpl::LOOK (this=0x7ffda80e2650, s=0x561d4521cf90, stopState=0x0, ctx=...) at .../antlr_4_10_1_build/runtime/src/atn/LL1Analyzer.cpp:133 #15 0x00007fd733c1b605 in (anonymous namespace)::LL1AnalyzerImpl::LOOK (this=0x7ffda80e2650, s=0x561d4521d080, stopState=0x0, ctx=...) at .../antlr_4_10_1_build/runtime/src/atn/LL1Analyzer.cpp:133 #16 0x00007fd733c1b605 in (anonymous namespace)::LL1AnalyzerImpl::LOOK (this=0x7ffda80e2650, s=0x561d4521ce40, stopState=0x0, ctx=...) at .../antlr_4_10_1_build/runtime/src/atn/LL1Analyzer.cpp:133 #17 0x00007fd733c1b605 in (anonymous namespace)::LL1AnalyzerImpl::LOOK (this=0x7ffda80e2650, s=0x561d4521cdc0, stopState=0x0, ctx=...) at .../antlr_4_10_1_build/runtime/src/atn/LL1Analyzer.cpp:133 #18 0x00007fd733c1bb31 in antlr4::atn::LL1Analyzer::LOOK (this=0x7ffda80e2838, s=0x561d4521cdc0, stopState=0x0, ctx=0x0) at .../antlr_4_10_1_build/runtime/src/atn/LL1Analyzer.cpp:187 #19 0x00007fd733c1ba64 in antlr4::atn::LL1Analyzer::LOOK (this=0x7ffda80e2838, s=0x561d4521cdc0, ctx=0x0) at .../antlr_4_10_1_build/runtime/src/atn/LL1Analyzer.cpp:180 #20 0x00007fd733bf69af in antlr4::atn::ATN::nextTokens (this=0x561d450d1970, s=0x561d4521cdc0, ctx=0x0) at .../antlr_4_10_1_build/runtime/src/atn/ATN.cpp:35 #21 0x00007fd733bf6a2c in antlr4::atn::ATN::nextTokens (this=0x561d450d1970, s=0x561d4521cdc0) at .../antlr_4_10_1_build/runtime/src/atn/ATN.cpp:43 #22 0x00007fd733bbe816 in antlr4::DefaultErrorStrategy::sync (this=0x561d44fcec50, recognizer=0x7ffda80e2ec0)
问题分析
从调用栈和报错信息来看,崩溃的根源是Antlr4 C++运行时的LL1AnalyzerImpl::LOOK方法中,使用的std::bitset大小(1048)小于要设置的token位置(1138),导致越界访问。这并非DefaultErrorStrategy的逻辑错误,而是运行时内部处理token集合时的容器容量不足问题——当语法定义的token类型数量超过bitset的预设大小时,就会触发这个崩溃。
DefaultErrorStrategy确实应该处理token缺失的情况,但它在执行sync逻辑时需要调用LL1分析器计算后续可能的token,而这个计算过程因为bitset容量不足直接崩溃了,根本没走到错误恢复的逻辑。
解决方案
优先升级Antlr4版本:当前使用的4.10.1存在bitset容量不足的已知问题,后续版本(比如4.13.1及以上)已修复该问题,将LL1分析器中的bitset替换为动态容器或增大了默认容量,升级后可直接解决崩溃问题。
临时修改运行时源码(若无法升级):
- 找到
antlr_4_10_1_build/runtime/src/atn/LL1Analyzer.cpp中的LL1AnalyzerImpl类,将其中的std::bitset<2048>替换为更大容量(比如std::bitset<4096>),或改用std::unordered_set<size_t>存储token集合,避免固定容量限制。 - 修改后重新编译Antlr4运行时库,替换项目中的依赖库即可。
- 找到
临时规避方案(不修改运行时):
- 重写
DefaultErrorStrategy的sync方法,跳过导致崩溃的LL1分析步骤,直接执行简单错误恢复,示例代码如下:class CustomErrorStrategy : public antlr4::DefaultErrorStrategy { public: void sync(antlr4::Parser *recognizer) override { antlr4::TokenStream *tokens = recognizer->getInputStream(); if (tokens->LA(1) == antlr4::Token::EOF) { return; } // 跳过当前token,直到遇到tokens_statement_2的token或EOF size_t expected[] = {TOK_04, TOK_05, TOK_06, antlr4::Token::EOF}; skipUntil(recognizer, expected, sizeof(expected)/sizeof(size_t)); } }; - 在解析器初始化时设置自定义错误策略:
parser.setErrorStrategy(std::make_shared<CustomErrorStrategy>()); - 此方案仅为过渡方案,无法覆盖所有错误场景,建议尽快升级版本。
- 重写
内容的提问来源于stack exchange,提问作者Jay Zee

