mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
The async stepping blob's first field is the compiler-generated catch handler's IL offset plus one, and 0 when there is nothing to record. ILSpy wrote the raw offset, so a consumer decoding it as (value - 1) resolved an address in the middle of an instruction: Mono.Cecil throws ArgumentNullException while reading the body, which is why the reported assembly opened in ILSpy but killed ILLink on every MoveNext it had. CatchHandlerOffset now holds the offset it is named after, or -1 for "none", and BuildBlob applies the bias - the same model the compiler uses. It is recorded only where an escaping exception is unlikely to be observed: an async void method, and an async entry point. Recording it more widely would be worse than recording it nowhere, because an async Task method returns its exception through the Task and the debugger would then break on exceptions user code catches. Measured over csc 1.3.2 to 5.10: async void is handler+1 and a normal async Task is 0 in every version; only async Main changed, from 0 to handler+1 between 2.10 and 3.11. The entry point token names the synchronous '<Main>' shim that exists because the runtime will not take .entrypoint on an async method, so the method to record is the one the shim calls. Reading that call is exact; matching the shim's siblings by name is not, and gets a 'Main' overload beside the real entry point wrong. Both tests compare against the compiler's own PDB for the same assembly, so the blobs describe the same IL and the field compares directly. Assisted-by: Claude:claude-opus-5:Claude Codepull/4124/head
6 changed files with 235 additions and 9 deletions
@ -0,0 +1,30 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
internal class AsyncSteppingCatchHandler |
||||||
|
{ |
||||||
|
public static async Task RunAsync() |
||||||
|
{ |
||||||
|
await Task.Yield(); |
||||||
|
Console.WriteLine("run"); |
||||||
|
} |
||||||
|
|
||||||
|
public static async Task<int> SumAsync(int a, int b) |
||||||
|
{ |
||||||
|
try |
||||||
|
{ |
||||||
|
await Task.Yield(); |
||||||
|
return a + b; |
||||||
|
} |
||||||
|
catch (InvalidOperationException) |
||||||
|
{ |
||||||
|
return 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static async void FireAndForget() |
||||||
|
{ |
||||||
|
await Task.Yield(); |
||||||
|
Console.WriteLine("done"); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,57 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
internal class AsyncSteppingEntryPoint |
||||||
|
{ |
||||||
|
public static async Task Main() |
||||||
|
{ |
||||||
|
try |
||||||
|
{ |
||||||
|
await Task.Yield(); |
||||||
|
Console.WriteLine("main"); |
||||||
|
} |
||||||
|
catch (InvalidOperationException e) |
||||||
|
{ |
||||||
|
Console.WriteLine(e.Message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static async Task Main(int notTheEntryPoint) |
||||||
|
{ |
||||||
|
try |
||||||
|
{ |
||||||
|
await Task.Yield(); |
||||||
|
Console.WriteLine(notTheEntryPoint); |
||||||
|
} |
||||||
|
catch (InvalidOperationException e) |
||||||
|
{ |
||||||
|
Console.WriteLine(e.Message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static async Task NotTheEntryPointAsync() |
||||||
|
{ |
||||||
|
try |
||||||
|
{ |
||||||
|
await Task.Yield(); |
||||||
|
Console.WriteLine("other"); |
||||||
|
} |
||||||
|
catch (InvalidOperationException e) |
||||||
|
{ |
||||||
|
Console.WriteLine(e.Message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static async void FireAndForget() |
||||||
|
{ |
||||||
|
try |
||||||
|
{ |
||||||
|
await Task.Yield(); |
||||||
|
Console.WriteLine("done"); |
||||||
|
} |
||||||
|
catch (InvalidOperationException e) |
||||||
|
{ |
||||||
|
Console.WriteLine(e.Message); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue