Ruby と Vue のプリプロセッサで無効な UTF‑8 バイトを安全に扱うように修正
Tailwind CSS の Ruby と Vue プリプロセッサが、無効な UTF‑8 バイトを含むファイルをスキャンした際にパニックする問題を解消し、エラー発生時でも安全に処理を継続できるようになった。
背景
これまで std::str::from_utf8 の結果を unwrap() していたため、UTF‑8 に変換できないバイト列が検出されるとスレッドがパニックし、全体のクラス抽出が停止していた。Ruby の ruby.rs:37 と Vue の vue.rs:18 が同様の実装であったことから、Rails プロジェクトでバイナリファイルや非 UTF‑8 エンコードが混入した場合にクラッシュが頻発していた。
技術的な変更
Ruby プリプロセッサ
crates/oxide/src/extractor/pre_processors/ruby.rs では、UTF‑8 変換を if let Ok(content_as_str) = std::str::from_utf8(content) に置き換え、変換失敗時は HEREDOC 抽出ロジックをスキップしつつ、バイトレベルの Ruby 処理は従来どおり実行するようにした。
変更前:
let content_as_str = std::str::from_utf8(content).unwrap();
変更後:
if let Ok(content_as_str) = std::str::from_utf8(content) {
// 既存の HEREDOC 抽出ロジック
let starts = TEMPLATE_START_REGEX.captures_iter(content_as_str).collect::<Vec<_>>();
let ends = TEMPLATE_END_REGEX.captures_iter(content_as_str).collect::<Vec<_>>();
// …
}
Vue プリプロセッサ
crates/oxide/src/extractor/pre_processors/vue.rs でも同様に unwrap() を除去し、UTF‑8 変換が成功した場合にだけテンプレートタグの置換処理を走らせ、失敗した場合は入力バイト列をそのまま返すようにした。
変更前:
let content_as_str = std::str::from_utf8(content).unwrap();
変更後:
if let Ok(content_as_str) = std::str::from_utf8(content) {
for (_, [lang, body]) in TEMPLATE_REGEX.captures_iter(content_as_str).map(|c| c.extract()) {
let replaced = pre_process_input(body.as_bytes().to_vec(), lang);
result = result.replace(body, replaced);
}
}
テスト追加
-
test_invalid_utf8_does_not_panicが Ruby と Vue の両方に追加され、無効な UTF‑8 バイト列でもパニックせず入力がそのまま返ることを検証した。 -
test_valid_utf8_with_multibyte_charsでマルチバイト文字が正しく処理されることも確認した。
設計判断
無効な UTF‑8 バイトが検出された場合に テンプレート抽出のみをスキップ し、低レベルのバイト処理は継続させる方針が採られた。これにより、既存の機能は保持しつつ、クラッシュのリスクだけを除去できる。エラーハンドリングを追加したのは最小侵入で済む if let パターンであり、コードベース全体への影響を抑える設計選択となっている。
まとめ
この変更により、Tailwind CSS が Ruby や Vue のプリプロセッサで無効な UTF‑8 バイトを含むファイルを安全にスキャンできるようになり、Rails プロジェクト等で見られたパニック問題が解消された。機能的な回帰はなく、テストで正常・異常ケースの両方が保証されている。