不正なコードポイントによるコンパイラクラッシュを修正

tailwindlabs/tailwindcss

CSSエスケープシーケンスの展開時に不正なコードポイントが渡されると String.fromCodePoint が例外を投げてコンパイラがクラッシュする問題を修正しました。仕様に従い、不正なコードポイントを \uFFFD(置換文字)に置き換えることで、安全に処理を継続できるようになります。

背景

Windowsの開発環境でTailwind CSS v4とVite、Tauriを組み合わせて使用した際に、コンパイラがクラッシュするバグが #19786 として報告されました。

クラッシュの引き金となったのは、Tauriのdev serverがランタイムスクリプトに埋め込む以下のようなパス+UUID文字列です。

--Coding-Projects-CharacterMapper-Master-Workspace\d8819554-4725-4235-9d22-2d0ed572e924

この文字列は -- で始まるためCSSカスタム変数の候補として認識されます。その後、コンパイラが変数名をアンエスケープする際に、Windowsのパス区切り文字 \ と直後の6桁の16進数 d88195 がCSSの16進エスケープシーケンスとして解釈されます。parseInt("d88195", 16)141889490xD88195)となり、Unicodeの最大コードポイント 0x10FFFF を超えるため String.fromCodePoint(14188949) が例外を投げていました。

同様の問題は #19801 でも報告されており、グローバルな .gitignore で除外されたファイルがスキャン対象になった場合にも発生していました。回避策として @source not "…"; でファイルを除外する方法はありましたが、根本的な修正が必要でした。

技術的な変更

修正の核心は packages/tailwindcss/src/utils/escape.tsunescape 関数で、String.fromCodePoint を呼び出す前にコードポイントの妥当性を検証するガード節を追加した点です。

変更前:

export function unescape(escaped: string) {
  return escaped.replace(/\\([\dA-Fa-f]{1,6}[\t\n\f\r ]?|[\S\s])/g, (match) => {
    return match.length > 2
      ? String.fromCodePoint(Number.parseInt(match.slice(1).trim(), 16))
      : match[1]
  })
}

変更後:

export function unescape(escaped: string) {
  return escaped.replace(/\\([\dA-Fa-f]{1,6}[\t\n\f\r ]?|[\S\s])/g, (match) => {
    if (match.length <= 2) {
      return match[1]
    }

    let codePoint = Number.parseInt(match.slice(1).trim(), 16)

    if (
      // Invalid codepoint: https://infra.spec.whatwg.org/#code-point
      codePoint === 0x0000 ||
      codePoint > 0x10ffff ||
      // Is surrogate: https://infra.spec.whatwg.org/#leading-surrogate
      //  - A leading surrogate is a code point that is in the range U+D800 to U+DBFF, inclusive.
      //  - A trailing surrogate is a code point that is in the range U+DC00 to U+DFFF, inclusive.
      (codePoint >= 0xd800 && codePoint <= 0xdfff)
    ) {
      return '\uFFFD'
    }

    return String.fromCodePoint(codePoint)
  })
}

バリデーションは以下の3条件でコードポイントの不正を判定します。

  • codePoint === 0x0000:NULLコードポイント
  • codePoint > 0x10FFFF:Unicodeの有効範囲外(0x00000x10FFFF が有効)
  • codePoint >= 0xD800 && codePoint <= 0xDFFFサロゲートコードポイント(leadingサロゲートとtrailingサロゲートの両範囲をカバー)

これらの条件に該当する場合は '\uFFFD'(Unicode置換文字)を返し、String.fromCodePoint の呼び出しを回避します。これはCSSパーサ仕様 CSS Syntax Level 3 で規定されている動作と一致します。

テストは2箇所に追加されています。packages/tailwindcss/src/index.test.ts では問題の文字列を候補として渡した際にクラッシュしないことを検証し、packages/tailwindcss/src/utils/escape.test.ts ではアンエスケープ後の文字列が \uFFFD を含む期待値と一致することを確認しています。

設計判断

CSSパーサ仕様に準拠した フェイルセーフな変換を選択した点が、この修正の設計上の要点です。

不正なコードポイントを検出した際の選択肢としては「例外を再スローする」「空文字列を返す」「置換文字を返す」などが考えられます。今回は仕様に明示された \uFFFD への置換が採用されました。これにより、候補文字列のアンエスケープが失敗してもコンパイル処理全体を中断せず、不正なCSSカスタム変数候補は最終的に無効な変数として無視される流れになります。

また、既存の正規表現とロジックの大枠は変えず、ガード節の挿入のみで修正を完結させています。変更行数を最小化しながら、仕様準拠のバリデーションを一か所に集約した構成です。

まとめ

String.fromCodePoint 呼び出し前にCSSパーサ仕様準拠のバリデーションを追加することで、Windowsパスを含む文字列やグローバルgitignoreで除外されたファイルなど、予期しない入力によるクラッシュを根本的に解消しました。仕様の置換文字 \uFFFD を使用する設計により、コンパイラの堅牢性が向上しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
92f026a0

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→各論(背景、技術詳細、設計判断)→まとめ(結論)という理想的な3部構成が明確に適用されており、非常に分かりやすい記事構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライトやGitHubのIssueへのリンク記法がガイドラインに沿って正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

コードポイントやエスケープシーケンスなど、専門的なトピックを前提知識として扱っており、対象読者であるエンジニアに適合した内容です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成で、各パラグラフがトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が徹底されており、極めて高い可読性を実現しています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事で引用されている変更前後のコードは、提供されたDiffの内容を正確に反映しています。ファイルパスも正しく記載されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「コードポイント」「サロゲートコードポイント」「置換文字」などの技術用語が、PR情報と照合しても、一般的な用法としても正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

クラッシュ原因の解説(16進数パースとコードポイント範囲超過)や、修正ロジックの説明が技術的に正確かつ論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内の主張はすべて、PRのDescriptionや関連Issue、Diff内のコードによって裏付けられており、ハルシネーションは一切見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号、Issue番号、コードポイントの16進数値など、すべての数値や固有名詞が正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「不正なコードポイントによるコンパイラクラッシュを修正」は、PRのタイトル「Fix crash due to invalid characters in candidate」の内容を的確に要約しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事内で言及されている仕様へのリンクはPR情報に含まれており、PRに記載のない外部知識を創作している箇所はありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現に歪曲は見られず、PRで示された状況を正確に伝えています。