发现问题
线上预警出现了 core dump 导致应用重启,分析后,报错如下:
问题追踪
第一感觉是哪个正则匹配引起的,导致循环次数过多。在网上搜一下相关的问题,已经有人指出了,并且很容易复现,具体参考 https://stackoverflow.com/questions/36304204/%D0%A1-regex-segfault-on-long-sequences。
那具体到自己的业务情况是怎样的呢?我们要找出有问题的正则,并且要修复。
出现 core 的情况下,错误日志还没有及时刷新到磁盘上,程序已经崩溃了。只能尽量查找出错的上下文。首先确定具体的 core 时间点,越精确越好,到秒基本就可以了。然后根据这个时间点 grep 具体机器上的日志。日志会打印入口请求及结束记录。如果找到断层的日志,也就是没有结束记录,那基本上可以断定在那个请求上发现的问题。确定具体请求后,结合相关业务,很容易找到问题出现的原因。
最后确定的正则是 《.*》。程序会做一层过滤,把书名号里面的文本进行过滤掉。确定原因后,我们可以复现下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# include <regex> # include <iostream> int main() { constexpr int N = 65536; std::regex r("《.*》"); const char* bomb2 = "《仙界科技》《灵魂导游》《NBA万界商城》"; std::string test(bomb2); for (auto i =0; i < N; i++) { test += " "; std::string out = test; out = std::regex_replace(out, r, " "); std::cout << "i=" << i << std::endl; } return 0; } |
发现当 i=32697 时,程序出现了 core。
把 test += " "; 变成 test += "中";,发现当 i=10896,出现 core。
把 test += " ";变成 test += "《中》";, 发现当 i=3630 时,就会出现 core。
问题分析
首先来看下 《.*》 这个正则,严格的说,是错误的,语义并不明确。
这个正则的执行步骤是,首先匹配 《,如果匹配到,则执行 .* 的匹配,不管文本有多长,什么内容,是否包含 》, 它都会一直往下匹配,直到文本最后。然后开始回溯,向前回溯一个字符,然后尝试匹配 》,如果找到,则匹配成功,完成。否则,会继续回溯,直到找到 》,如果没有找到,则匹配失败。
假设匹配的文本 《仙界科技》《灵魂导游》我是中国人,那么匹配的内容是 《仙界科技》《灵魂导游》,而不是单独的两个 《仙界科技》 和 《灵魂导游》。
解决问题
上面的正则,更准确的表述应该是 《[^》]+》。但在C++中,对中文字符支持的很弱,导致某些匹配失败。
对于正则引擎来说,默认的规则是执行最长的匹配,通过特殊字符的使用,可以使用最短路径匹配。在我们的例子中就是 《.*?》。执行步骤是首先匹配 《,然后对下一个字符尝试匹配 》,如果匹配成功,则成功,否则匹配 .*, 这肯定能匹配到,然后再次尝试匹配 》,依次循环下去。
改正后的正则,再次执行相关用例,就不会出现 core 问题了(匹配过程中没有使用回溯)。
总结
std::regex_replace 确实有 bug 会引起 core 问题。没有统一的方式如何去规避。只能具体情况具体分析。保证两点基本上会规避这样的问题:
- 匹配的内容不宜过长,很少正则需要匹配太长的内容(没有意义),向我们上面的例子,某些情况下,就是有 bug 的正则。 test += "《中》"; 这里的测试,就很容易复现。匹配内容过长。
- 正则匹配的过程中,回溯的次数越少越好,更多的回溯,造成 core 的可能性越大。需要对正则做最坏情况的估计,保证最坏情况下,回溯越少越好。
遇到同样的问题,搞了好久,找到这里才解决