--- lang: ja-jp breaks: true --- # C# `System.Reflection.Emit` `ILGenerator` でメソッドを作成したときに、最大スタックサイズが小さなメソッドが生成され、実行時にエラーとなることがある。 2021-11-02 ### `.NET Framework 4.8`で実行した場合 ![](https://i.imgur.com/yFqxF4W.png) ### `.NET Core 6.0`で実行した場合 ![](https://i.imgur.com/TautKuw.png) ### 対象のアセンブリを保存して`PE Verifier`でチェックした結果 「`スタックのオーバーフロ ー`」とエラーが表示される。 ```shell= icrosoft (R) .NET Framework PE Verifier バージョン 4.0.30319.0 Copyright (c) Microsoft Corporation. All rights reserved. [IL]: エラー:[D:\XXXXXXXX\bin\Debug\net48\Interface.NamedPipe.NamedPipeClient.ProcessInterface.dll : Interface.ProcessClient::Process][mdToken=0x6000003][オフセット 0x00000020] スタックのオーバーフロ ー 1 エラー確認中 Interface.NamedPipe.NamedPipeClient.ProcessInterface.dll ``` ### 問題が発生したメソッド ```csharp= public void Process() { PipeStreamUtil.Invoke_Method_Client( pipeName : _pipeName, method : Process, typRequest : null, req : null, typResponse : typeof(void), cancellationToken: _cancellationToken ); } ``` ### 正常に動作する場合のIL ※C#のコンパイラが生成したIL ![](https://i.imgur.com/3JFxbPj.png) ### エラーとなる場合のIL ※`ILGenerator`が生成したIL ![](https://i.imgur.com/SJPLqIh.png) 違いは、`.maxstack`の部分のみ。 最大スタックサイズが足りなくなり、`IL_0020: ldarg.0`の部分でエラーとなっているようだ。 ## 解決方法 `ILGenerator`の最大スタックサイズは、内部で自動的に計算されており、変更することは出来ない。 しょうがないので、処理の最後の`IL_002b: pop`と`IL_002c: ret`の間に無意味になプッシュ及びポップの処理を追加して対応。これにより、最大スタックサイズを増やすことができる。 ※今回の場合は、`.maxstack`が6以上であれば正常に動作する。 ![](https://i.imgur.com/36gonX4.png) このILをのC#のコードとしてデコンパイルしたソースは以下のような感じになる。 ![](https://i.imgur.com/c92aXRV.png) ```csharp= public void Process() { //Error decoding local variables: Signature type sequence must have at least one element. PipeStreamUtil.Invoke_Method_Client(_pipeName, new Action(Process), null, null, typeof(void), _cancellationToken); _ = null; _ = null; _ = null; _ = null; _ = null; _ = null; _ = null; } ``` 気持ち悪いが、ほかに解決方法が見つからない。。 :::warning 「`Error decoding local variables: Signature type sequence must have at least one element.`」は、ローカル変数を使用しない場合に `ILSpy` で表示される。なぜこのような警告が表示されているか不明。。。 ::: ### スタックサイズを増やすためのヘルパーメソッド ```csharp= /// <summary> /// IL生成後のMaxStackSizeが、コンパイルした場合と比較して小さく算出される場合があり、 /// StackOverflowによる実行時エラーが発生することがある為、適当に調整するためのメソッド。 /// </summary> /// <param name="il"></param> internal static void AdjustMaxStackSize(this ILGenerator il) { int maxStackSize = il.GetMaxStackSize(); int maxMidStack = il.GetMaxMidStack(); int maxMidStackCur = il.GetMaxMidStackCur(); int intMax = Math.Max(maxStackSize, maxMidStack); intMax = Math.Max(intMax , maxMidStackCur); // 適当に調整する。 intMax += 2; for (int i = 0; i < intMax; i++) { il.Emit(OpCodes.Ldnull); } for (int i = 0; i < intMax; i++) { il.Emit(OpCodes.Pop); } } ``` :::info `GetMaxStackSize()`、`GetMaxMidStack()`、`GetMaxMidStackCur()`は、リフレクションにより`ILGenerator`の内部で管理されている各種スタックサイズを取得する為のメソッド。 ::: ###### tags: `C#` `System.Reflection.Emit` `ILGenerator` `スタックサイズ` `PE Verifier`