🐛

🐛 PCRE2 のバグ:花の絵文字(🌷🌹🌺)が分割されない理由と修正の確認

に公開

はじめに

正規表現で文字単位の処理をするとき、\X を使えば「書記素クラスタ(grapheme cluster)」単位で文字列を扱えるはず──そう思っていました。
ところが、ある日テストしてみると、なぜか 🌷🌹🌺 が 1つのクラスタとしてしか認識されないという予想外の結果に。

本記事では、PCRE2 の \X に関するバグの再現と、どのバージョンで修正されたかを検証し、最新版での挙動を確認します。

調査の結果

2024年にリリースされた PCRE2 10.44 でバグは修正されていました。ChangeLog によると家族の絵文字のような ZWJ(Zero Width Joiner)を含む書記素クラスターと同一視されて、誤判定されていたことが原因でした。
書記素クラスターの処理のコードは src/pcre2_extuni.csrc/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

ビルドが成功すれば pcre2greppcre2test などのコマンドツールが生成されます。
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
GitHubで編集を提案

Discussion