識別子が識別子ではない場合、またはモンゴル語の​​母音区切り攻撃



翻訳者メモ
翻訳では、「有効」、「ネイティブ」、「バイナリ」などの英語を使用することを許可しました。 彼らに質問がないことを願っています。



識別子は、クラスの名前、変数の名前など、名前でアクセスできるすべてのものを識別するC#仕様の特別な用語です。



Roslynは、C#で記述されたC#コードコンパイラです。 既存のcsc.exeを置き換えるために作成されました。 通常、このテキストではコンパイラという語を省略します。



始めるために、あなたが聞いたことがないかもしれないいくつかのこと:



このすべてからいくつかの問題が発生します...



ウラジミールはすべてを責める



すべては先週のECMA技術グループの会議での議論から始まりました。 「規範的なリンク」、特に使用するUnicode標準のバージョンを調べました。 当時、ECMA-335仕様(第4版)はUnicode 4.0を使用しており、MicrosoftのC#5仕様はUnicode 3.0を使用しています。 コンパイラの開発者がそのような機能を考慮に入れているかどうかはわかりません。 私の意見では、ECMAとMicrosoftが仕様でUnicodeの特定のバージョンを指定しなかった方が良いでしょう。 コンパイラ開発者に、現在利用可能なUnicodeの最新バージョンを使用させます。 ただし、その場合、コンパイラーはUnicodeテーブルの個人用コピーを用意する必要がありますが、これは私の意見では少し奇妙です。



私たちの議論の中で、 ウラジミール・レシェトニコフは「 モンゴル語母音分離器 」(U + 180E)について何気なく言及しました。 この文字はUnicode 3.0.0でCf(その他、形式)カテゴリに追加されました。 次に、Unicode 4.0.0ではZsカテゴリ(セパレータ、スペース)に移動され、Unicode 6.3.0ではCfカテゴリに再び返されました。



もちろん、私はそのような行動を非難しようとしました。 私の最初の目標は、コンパイラが使用するUnicodeテーブルのバージョンに応じて、動作が異なるコードを表示することでした。 しかし、実際には、すべてがもう少し複雑であることが判明しました。 しかし、最初に、バグを含まない「仮想コンパイラ」を使用し、必要なUnicodeバージョンを使用すると仮定します(これは現在のC#仕様の要件によるバグですが、この微妙な点は無視します)。



仮説例1:正しいか間違っている



簡単にするために、しばらくの間、あらゆる種類のUTFを忘れて、通常のASCIIを使用します。



クラス MvsTest

{

静的 ボイドメイン()

{

string stringx = "a" ;

文字列\ u180ex = "b" ;

Console .WriteLine(stringx);

}

}




コンパイラーがUnicodeバージョン6.3以上(または4.0より前のバージョン)を使用する場合、U + 180EはCfカテゴリーの文字と見なされるため、識別子での使用が許可されます。 シンボルを識別子で使用できる場合、このシンボルの代わりにエスケープシーケンスを使用できます。コンパイラはそれを喜んで正しく処理します。 このメソッドの2行目の識別子は、stringxと「同一」と見なされるため、「b」が表示されます。



では、Unicodeバージョン4.0-6.2を使用するコンパイラーはどうでしょうか? この場合、U + 180EはZsカテゴリーの文字と見なされ、空白文字になります。 空白はC#コード内で使用できますが、識別子自体では使用できません。 また、この文字は許可された識別子ではなく、文字\文字列リテラル内にないため、コンパイラーの観点からは、このセクションでのエスケープシーケンスの使用は正しくないため、コードのこのセクションは単にコンパイルされません。



仮説例2:2つの異なる方法で修正



ただし、エスケープシーケンスを使用せずに同じコードを記述できます。 これを行うには、通常のASCIIファイルを作成します。



クラス MvsTest

{

静的 ボイドメイン()

{

string stringx = "a" ;

stringAAAx = "b" ;

Console .WriteLine(stringx);

}

}




次に、16進エディターで開き、AAA文字をバイトE1 A0 8Eに置き換えます。 したがって、最初の例のエスケープシーケンスを使用して表示されたのと同じ場所に、U + 180EシンボルのUTF-8表現を含むファイルを取得しました。



最初の例を正常に採用したコンパイラーもこのオプションをコンパイルし(ファイルがUTF-8でエンコードされていることをコンパイラーに伝えることができたと仮定)、結果はまったく同じになります。メソッドの構築は、既存の変数への単純な割り当てです。



ただし、コンパイラがU + 180Eを空白文字として認識しても(つまり、例1のプログラムのコンパイルを拒否します)、このオプションには問題はありません。コンパイラは、新しいローカル変数xを宣言し、何らかの種類の初期値。 未使用のローカル変数の宣言に関するコンパイラ警告が表示される場合がありますが、コードは正常にコンパイルされ、「a」が表示されます。



現実:Microsoftコンパイラー



Microsoft C#コンパイラについて話すときは、ネイティブコンパイラ(csc.exe)とRoslyn(rcsc、通常は単にRoslynと呼びます)を区別する必要があります。



csc.exeはネイティブコードで記述されているため、Unicodeを操作するために組み込みのWindowsツールを使用するか、単に実行可能ファイルにUnicode文字のテーブルを保存します。 (ネイティブWin32関数を検索するためにMSDN全体を検索して、文字が特定のUnicodeカテゴリに属しているかどうかを確認しましたが、何も見つかりませんでした。このような関数が非常に役立つのは残念です...)



現時点では、C#で記述され、Unicodeカテゴリーを判別するために(私の知る限り) Roslynは、mscorlib.dllに組み込まれたUnicodeテーブルに依存するchar.GetUnicodeCategory()を使用します。



私の実験では、ネイティブコンパイラがカテゴリの決定に使用するものに関係なく、U + 180Eが常にCfカテゴリのシンボルとして使用されることを示唆しています。 少なくとも2013年9月以降に更新プログラムがインストールされていない古いマシン(VMイメージを含む)を見つけようとしました(当時はUnicode 6.3標準が公開されていました)。いずれかのエラー。 csc.exeにはおそらくUnicode 3.0テーブルのコピーがバイナリに組み込まれていると思われ始めています。 彼は間違いなくU + 180Eをフォーマット文字として認識しますが、識別子のU + 0600およびU + 00ADが好きではありません(U + 0600はUnicode 4.0より前では表されませんでしたが、常にフォーマット文字でした; Unicode 3.0ではU + 00AD句読文字(ダッシュ)でしたが、Unicode 4.0以降はフォーマット文字です)



ただし、mscorlib.dllに組み込まれているテーブルは、.NET Frameworkの新しいバージョンの出現に伴って間違いなく変更されています。 そのようなプログラムを実行する場合:



システムを使用して ;



クラス テスト

{

静的 ボイドメイン()

{

Console .WriteLine( Environment .Version);

Console .WriteLine( char .GetUnicodeCategory( '\ u180e' ));

}

}




次に、CLRv2の下に「SpaceSeparator」が表示され、CLRv4の下に(少なくとも最近更新されたシステムでは)「フォーマット」が表示されます。



もちろん、Roslynは古いバージョンのCLRでは動作しません。 しかし、私たちはcsharppad.comにまだ期待を寄せており、何らかの環境(起源は不明、おそらくMonoかもしれませんが、これについては不明です)でRoslynを起動し、その結果、「SpaceSeparator」が表示されます。 最初の例のプログラムはコンパイルされないはずです。 ただし、2番目の例では、すべてがより複雑です-csharppad.comはソースコードファイルのダウンロードを許可せず、コピー/貼り付けは奇妙な結果をもたらします。



現実:mcs(Mono C#コンパイラー)



MonoコンパイラはGetUnicodeCategory()メソッドも使用します。これにより、実験がはるかに簡単になりますが、残念ながら、Monoパーサーには少なくとも2つのバグがあります。



このため、最初の例のプログラムは常にコンパイルされ、画面に「b」が表示されます。 ただし、2番目の例のプログラムは、コンパイラによると、どのカテゴリ(ZsまたはCf)が文字U + 180Eを参照しているかに関係なく、コンパイルエラーを生成します。



それで、それはどのバージョンですか?



次に、.NET自体のUnicodeテーブルについて考えてみましょう。さまざまなBCL実装がどのバージョンのUnicodeを使用しているかは不明です。 このプログラムを実行します:



システムを使用して ;



クラス テスト

{

静的 ボイドメイン()

{

Console .WriteLine( char .GetUnicodeCategory( '\ u00ad' ));

Console .WriteLine( char .GetUnicodeCategory( '\ u0600' ));

Console .WriteLine( char .GetUnicodeCategory( '\ u180e' ));

}

}




私のコンピューターでは、CLRv4で実行されるこのプログラムは「DashPunctuation、Format、Format」を生成し、Mono(3.3.0)およびCLRv2に対しては「DashPunctuation、Format、SpaceSeparator」を生成します。



これは少なくとも奇妙です。 私が言える限り、この動作はUnicode標準のどのバージョンにも準拠していません。



したがって、出力の1行目または3行目に一致するUnicode標準のバージョンはありません。 今私は本当に困惑しています...



nameofとcallerMemberNameはどうですか?



識別子は比較だけでなく、Reflectionを使用せずに文字列(C#文字列)として使用できます。 C#5から、CallerMemberName属性を使用できるようになり、次のようなことができるようになりました。



public static void X \ u0600y()

{

ShowCaller();

}



public static void ShowCaller([ CallerMemberName ] string caller = null

{

Console .WriteLine( " {0} によって呼び出され ます " 、呼び出し元);

}




そして、C#6では次のように記述できます。



文字列 x \ u0600y = "" ;

Console .WriteLine( "nameof = {0} "nameof (x \ u0600y));




これら2つのプログラムは何を表示しますか? コンパイラがすべての書式設定文字を単に捨てたように、単に名前として「Xy」と「xy」を出力します。 しかし、彼らは何を推測すべきですか? 2番目の場合、nameof(xy)を書くことができ、そのような文字列は宣言された識別子の文字列と同じままであることを考慮する必要があります。



「宣言されたメンバーの名前は何ですか?」と言うことすらできません。「異なるが等しい」識別子でオーバーロードできるためです。



public static void Xy(){}

public static void X \ u0600y(){}

public static void X \ u070fy(){}

...

Console .WriteLine( nameof (X \ u200by));




画面に何を表示する必要がありますか? C#の作成者がこの計画を持っていることを知って安心するでしょうが、これは実際には「明らかな正しい答えがない」シナリオの1つです。 CLI仕様が登場すると、事態はさらに奇妙になります。 ECMA-335 6th EditionのセクションI.8.5.1には次のように記載されています。

アセンブリは、Unicode Standard 3.0のTechnical Report 15のAppendix 7 に従って 、識別子で使用できる文字セットを定義する必要があります。これはwww.unicode.org/unicode/reports/tr15/tr15-18.htmlで入手できます。 識別子は、「Unicode Normalization Form C」で定義された標準形式である必要があります。 CLS仕様を満たすために、2つの識別子は、それらの小文字表現(Unicodeロケールに依存しない1対1の小文字マッピングで定義されている)が同じ場合にのみ同じでなければなりません。 このため、2つの識別子がCLSに応じて異なると見なされるためには、大文字と小文字の違いだけではありません。 ただし、継承された定義を再定義するには、CLIで元の定義のエンコードに使用された正確なエンコードが必要です。


ILにCf文字を追加してこのドキュメントの効果を調べたいと思いますが、残念ながら、私はilasmが使用するエンコーディングに影響を与える方法を見つけられず、私の「修正された」ILが何であるかを説得することができません。私は彼になりたいです。



おわりに



前述のように、 テキストは複雑です。



「テキストは複雑です」という識別子だけに限定されていることが判明しました。 誰が考えたでしょうか?



翻訳者から:John Skeetの以前の出版物を翻訳してくれたユーザーimpwxに感謝



All Articles