🐛 PCRE2 のバグ:花の絵文字(🌷🌹🌺)が分割されない理由と修正の確認
はじめに
正規表現で文字単位の処理をするとき、\X を使えば「書記素クラスタ(grapheme cluster)」単位で文字列を扱えるはず──そう思っていました。
ところが、ある日テストしてみると、なぜか 🌷🌹🌺 が 1つのクラスタとしてしか認識されないという予想外の結果に。
本記事では、PCRE2 の \X に関するバグの再現と、どのバージョンで修正されたかを検証し、最新版での挙動を確認します。
調査の結果
2024年にリリースされた PCRE2 10.44 でバグは修正されていました。ChangeLog によると家族の絵文字のような ZWJ(Zero Width Joiner)を含む書記素クラスターと同一視されて、誤判定されていたことが原因でした。
書記素クラスターの処理のコードは src/pcre2_extuni.c と src/pcre2_tables.c に記載されています。コードの分量は少ないので、ファイルをダウンロードしてブラウザー版の ChatGPT などに解説を求めることができます。
このバグは PHP 8.4 の開発段階において発見され、報告されました。
PCRE2 のビルド
バグが修正されているか確認するために PCRE2 をビルドします。最新版は 10.45 です。
git clone --depth=1 --branch pcre2-10.45 https://github.com/PhilipHazel/pcre2.git
cd pcre2
git submodule update --init
./autogen.sh
./configure --prefix=$HOME/.local --enable-jit
make -j$(nproc)
make install
ビルドが成功すれば pcre2grep や pcre2test などのコマンドツールが生成されます。
Debian の場合、これらのコマンドのために pcre2-utils パッケージが用意されています。
pcre2grep によるテスト
ビルドした pcre2grep が花の絵文字を正常に扱えるかテストしてみます。
echo '🌷🌹🌺' | ./pcre2grep -o -u '\X'
🌷
🌹
🌺
古い pcre2grep の場合、次のような結果になります。
echo '🌷🌹🌺' | /usr/bin/pcre2grep -o -u '\X'
🌷🌹🌺
grep によるテスト
pcre2grep と同じ書き方が使えます。
echo -n 🌷🌹🌺 | grep -oP '\X'
Debian 12 Bookworm の場合、PCRE2 のバージョンは 10.42 で Debian 13 trixie では 10.45 です。
PHP によるテスト
PHP でテストする場合、次のようなコードを書きます。
<?php
preg_match_all("|\X|u", "🌷🌹🌺", $matches);
echo PCRE_VERSION, PHP_EOL;
var_dump($matches[0]);
PHP 8.4 に同梱されている PCRE2 のバージョンは 10.44 です。
ripgrep の PCRE2 サポート
最後に ripgrep を PCRE2 サポートつきでビルドしてテストしてみます。
ビルドするためには Rust がインストールされていることが前提です。
最新の ripgrep をシャロークローンします。
git clone --depth=1 --branch 14.1.1 https://github.com/BurntSushi/ripgrep.git
cd ripgrep
先程インストールした PCRE2 を見つけられるように環境変数を設定します。
export PKG_CONFIG_PATH="$HOME/.local/lib/pkgconfig:$PKG_CONFIG_PATH"
export LD_LIBRARY_PATH="$HOME/.local/lib:$LD_LIBRARY_PATH"
ビルドします。
cargo build --release --features 'pcre2'
PCRE2 がサポートされているか確認します。
./target/release/rg --version
ripgrep 14.1.1 (rev 4649aa9700)
features:+pcre2
simd(compile):+SSE2,-SSSE3,-AVX2
simd(runtime):+SSE2,+SSSE3,+AVX2
PCRE2 10.43 is available (JIT is available)
自分がビルドした PCRE2 がリンクされているか確認します。
ldd target/release/rg | grep pcre2
libpcre2-8.so.0 => /home/masakielastic/.local/lib/libpcre2-8.so.0 (0x00007f615afcf000)
動作を確認します。
echo '🌷🌹🌺' | ./target/release/rg -o --pcre2 '\X'
🌷
🌹
🌺
最後にシステムにインストールします。
cargo install --path . --features pcre2
Linux 限定で次のコマンドでもインストールできます。
install -Dm755 target/release/rg ~/.local/bin/rg
Discussion