C++で正規表現を使っていた時にバグにハマったので書き残し
バグ発見に至った経緯
某🐎ゲー向けのソフトを開発中に、OCRで読み取った文字列の正規化を行うために正規表現(std::regex)を使ったのだが、なぜか本来ならマッチするはずの文字列がマッチせず、書いたコードが動かなかった
# include <iostream>
# include <regex>
# include <string>
int main() {
const std::wstring turn = L"ジュニア紹7月得半"; // turn にはOCRで読み取った正確性に欠く文字列が入っている
std::wregex rx(LR"((ジュニア|クラシック|シニア)\D+(\d+)月(前|\W)半)");
std::wsmatch result;
if (std::regex_match(turn, result, rx)) {
// "前"は認識しやすいが、"後"は認識が難しいので
std::wstring half = result[3].str() == L"前" ? L"前半" : L"後半";
std::wstring guessTurn = result[1].str() + L"級" + result[2].str() + L"月" + half;
std::cout << "match!" << std::endl;
} else {
std::cout << "no match" << std::endl;
}
}
だいたいこういったコードを書いていたわけだ
OCRで読み取った文字列は完璧ではないので、正確に読み取れる部分だけを抜き出して再構築してあげる必要があった
このコードをvisual studio 2019で実行してみると"no match"と表示されてしまう
https://regex101.com/
などの正規表現のテストできるサイトでチェックしてみても、自分が書いた正規表現で問題なくマッチしてくれるので、これはおかしいぞとなった
で、いろいろ試行錯誤してみるとどうやら"\D"が怪しいことが分かった
正規表現の"\D"は"[^0-9]"と同じであり、数字以外にマッチするマッチングパターンだ
"紹"は数字ではないので、本来ならマッチするのが正しいが
# include <iostream>
# include <regex>
# include <string>
int main()
{
bool a = std::regex_search(L"あ", std::wregex(LR"(\D)"));// false
bool b = std::regex_search(L"A", std::wregex(LR"(\D)"));// true
bool c = std::regex_search(L"あ", std::wregex(LR"([^0-9])"));// true
bool d = std::regex_search(L"A", std::wregex(LR"([^0-9])"));// true
std::cout << std::boolalpha
<< "あ == \\D: " << a << "\n"
<< "A == \\D: " << b << "\n"
<< "あ == [^0-9] : " << c << "\n"
<< "A == [^0-9] : " << d << "\n"
<< std::endl;
return 0;
}
試しにこういうコードを書いてみると、 "a"がtrueにならない…
つまり、VC++の実装では"\D"は"[^0-9]"と同じではない
ちなみに他のコンパイラ(clang,gcc)では "a"はtrueとなるので、VC++だけがおかしい
https://wandbox.org/permlink/wmDPndG53xwsWZ73
バグ回避
VC++のstd::regexにおける"\D"の実装がバグってるのは分かったので、"[^0-9]"と書き直してバグを回避した
std::wregex rx(LR"((ジュニア|クラシック|シニア)[^0-9]+(\d+)月(前|\W)半)");
バグ報告
問題の再現が出来たので一応バグの報告しておいた
直るといいね!