プロンプトとコンテキスト
あるリポジトリでは非ASCIIの識別子と多言語コメントが許可されています。ある変更にbidi制御文字や視覚的に混同しやすい識別子が含まれているにもかかわらず、プレーンテキストビューアーでは誤解を招く順序でレンダリングされています。アラビア語、ヘブライ語、その他の正当な多言語コメントや文字列を拒否することなく、リスクを検出するスキャナーを設計してください。
面接官が見ているポイント
- 字句解析、双方向レンダリング、セキュリティ診断が個別のステージとして分離されているか。
- UTS #55の字句ソースコードアトムとそのHL4表示セマンティクスを理解しているか。
- すべての非ASCII文字を敵対的として扱うのではなく、UTS #39の混同可能文字および制限ルールを使用しているか。
- Unicodeデータのバージョン、増分スキャン、説明可能な検出結果、および誤検知(false-positive)ポリシーを処理できているか。
質問して明確にすべき点
まず、対象言語、コンパイラバージョン、許可されている識別子の文字体系(スクリプト)、コメント言語を確認します。スキャナーがエディタ、CI、コードホスティングプラットフォームのどこで実行されるかを特定します。次に、検出結果が警告となるか、送信をブロックするか、レビューを要求するか、また古いUnicodeデータや構文解析不能なコードを引き続きサポートする必要があるかどうかを明確にします。
30秒での回答
対象言語のレキサーを使用してトークン、識別子、文字列、コメントを取得し、各アトムの元のコードポイント、方向制御文字、スクリプトを記録します。ソースの表示は、ファイルを1つの通常の段落として扱うのではなく、UTS #55の字句構造に従います。診断ではUTS #39の制限データと混同可能データを組み合わせ、位置、ルール、バージョン、レンダリングの差異をレポートします。検出結果はデフォルトで警告し、リポジトリポリシーによってブロックできます。有効なRTLテキストを攻撃と誤認しないよう、元のテキストは保持されます。
ステップごとの詳細解説
1. まず字句の境界を確立する
通常の段落のbidi処理を文字ごとに適用してはいけません。数値リテラル、識別子、単一行文字列、コメントコンテンツがアトムを形成します。ネストされた言語には外部文法からの境界が必要です。コンパイラまたは実績のあるパーサーのトークン範囲を再利用し、構文解析に失敗した場合は明示的に保守的な診断へとダウングレードします。
2. bidi制御文字とデフォルト不可視文字(default-ignorable)をマークする
各アトムについて、RLO、LRO、PDF、RLI、LRI、FSI、PDIなどの制御文字をそのスコープとともに記録します。ターミナルやWebページが再度制御文字を適用するのを防ぐため、レポートにはエスケープされたコードポイントと元の位置を表示する必要があります。正当なレイアウトにも方向制御文字が必要な場合があるため、盲目的に削除してはいけません。
3. UTS #55でソース表示を構成する
UTS #55では、字句構造に上位レベルプロトコルHL4を適用することを推奨しています。トークンの順序は言語の構文に従い、文字列、識別子、コメントはそれぞれの方向特性でレンダリングされる不可分のアトムとなります。これにより、読み取り可能なRTLテキストと監査可能なコード構造を同時に保持できます。
4. 識別子と混同可能文字のルールを適用する
言語で許可されている正規化、大文字・小文字、UAX #31プロファイルを識別子に適用し、次に混同スクリプトおよび混同可能文字の診断にUTS #39を使用します。skeleton(骨格)を識別子そのものにしてはいけません。これは特定のUnicodeデータバージョンに対する中間比較値であり、潜在的な衝突を見つけるために役立ちます。
5. 最小限のスキャンパイプラインを作成する
以下の擬似コードはステージの境界のみを示しています。本番コードには対象言語のレキサーとUnicodeデータファイルが別途必要です。
type Atom = { kind: string; start: number; end: number; text: string };
type Finding = { code: string; start: number; end: number; detail: string };
function diagnose(source: string, atoms: Atom[], unicodeVersion: string): Finding[] {
const findings: Finding[] = [];
for (const atom of atoms) {
findings.push(...scanBidiControls(atom));
if (atom.kind === "identifier") {
findings.push(...scanIdentifierConfusables(atom, unicodeVersion));
}
}
return findings;
}scanBidiControls は制御コードポイントとスコープを保持する必要があります。scanIdentifierConfusables は混同スクリプト、制限レベル、または既存の識別子との衝突に関する検出結果を返すべきです。診断ステージでソースコードを書き換えてはなりません。
6. バージョン、キャッシュ、増分スキャンを処理する
Unicodeデータのバージョン、言語レキサーのバージョン、ルール設定を各レポートに書き込みます。ファイルの内容とレキサーバージョンごとにトークンの結果をキャッシュします。増分ビルドでは、影響を受ける同一スコープ内のトークンと識別子を再スキャンします。Unicodeデータのアップグレード後は、古いキャッシュエントリを再利用せず、skeletonと衝突セットを再計算します。
7. 検出結果をポリシーから分離する
各検出結果には、ファイル、行と列、ルールコード、元のコードポイント、視認可能なレンダリング、修正のヒントを含める必要があります。ポリシー層が警告、ブロック、レビュー、許可を選択します。エディタ、CI、コードホスティングUIによって、同じ検出結果に対して異なるポリシーを設定できます。ログにはエスケープされていない制御文字を直接レンダリングしてはなりません。
質の高い模範解答
実装をレキサー、Unicode解析、レンダリングプレビュー、ポリシーレポーティングに分割します。レキサーはトークン、識別子、文字列、コメントの正確な範囲を返します。解析不能な領域には文字境界を割り当てるのではなく、不確定(uncertain)としてマークします。Unicode解析では、各アトムについてBidi_Control、スクリプトセット、正規化プロファイル、UTS #39混同可能結果を記録します。プレビューではUTS #55 HL4アプローチを使用してトークンの構文順序を維持し、各アトム内でbidiルールを適用し、レンダリングとともにエスケープされたコードポイントを表示します。レポートにはUnicodeデータバージョン、レキサーバージョン、ルールコード、位置が含まれます。ポリシーはフィールドリスクに応じて警告またはブロックを選択します。同一バージョンのskeletonは識別子の衝突を見つけるのに役立ちますが、ユーザー名や不変のハッシュではありません。キャッシュキーにはファイル内容、言語バージョン、Unicodeバージョンが含まれ、アップグレード時には一括再計算がトリガーされます。テストでは、RTLコメント、文字列内の制御文字、ネストされた言語、混同スクリプト識別子、解析失敗、増分編集、ターミナルとWebレンダラー間の差異を網羅します。
よくある間違いと失敗するアプローチ
- すべての文字を左から右へレンダリングし、有効なRTLコメントや文字列を読めなくしてしまう。
- すべての非ASCII文字をブロックし、正当な言語や国際化識別子を壊してしまう。
- レキサーを正規表現に置き換え、制御文字が文字列、コメント、識別子のいずれにあるかの文脈を失ってしまう。
- クリーンアップされたテキストのみを保持し、元のコードポイントと監査位置を失ってしまう。
- 移行計画なしに、異なるUnicodeバージョンによって生成されたskeletonを比較してしまう。
フォローアップの質問と回答
なぜすべてのBidi_Control文字を削除しないのですか?
正当なテキストの一部には方向制御文字が必要であり、削除するとユーザーに見える内容が変わってしまいます。言語ポリシーに従ってソースコード内でそれらをブロックまたはエスケープし、コメントや文字列ではコンテキストをレポートして原文を保持します。
字句解析が失敗した場合、スキャナーは何をすべきですか?
明示的に不確定な範囲を返し、制御文字とコードポイントの保守的な診断を実行し、その領域が安全であるとは主張しないようにします。パーサーがその言語を正確にサポートするまで、CIでレビューを必須とすることができます。
多言語コードが誤ってフラグ付けされていないことをどのように証明しますか?
有効なRTLコメント、文字列、許可された識別子を正例(positive cases)として使用し、RLO/LRO、混同スクリプト、混同可能文字の衝突を負例(negative cases)として使用します。複数のエディタ、ターミナル、Webレンダラー間で論理順序、表示順序、レポート位置を比較します。