Browse Source

Merge pull request #2853 from ElektroKill/vb-async

pull/2754/head
Siegfried Pammer 2 years ago committed by GitHub
parent
commit
8e48f638d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      ICSharpCode.Decompiler.Tests/TestCases/VBPretty/.gitignore
  2. 262
      ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs
  3. 226
      ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.vb
  4. 6
      ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs
  5. 290
      ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs

4
ICSharpCode.Decompiler.Tests/TestCases/VBPretty/.gitignore vendored

@ -1 +1,3 @@ @@ -1 +1,3 @@
*.dll
/*.dll
/*.exe
/*.pdb

262
ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs

@ -1,45 +1,255 @@ @@ -1,45 +1,255 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace EquivalentCSharpConsoleApp
using Microsoft.VisualBasic.CompilerServices;
namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty
{
static class Program
public class Async
{
public static void Main(string[] args)
private int memberField;
private static bool True()
{
return true;
}
public async void SimpleVoidMethod()
{
Console.WriteLine("Before");
await Task.Delay(TimeSpan.FromSeconds(1.0));
Console.WriteLine("After");
}
public async void VoidMethodWithoutAwait()
{
Console.WriteLine("No Await");
}
public async void EmptyVoidMethod()
{
}
public async void AwaitYield()
{
await Task.Yield();
}
public async void AwaitDefaultYieldAwaitable()
{
#if LEGACY_VBC || (OPTIMIZE && !ROSLYN4)
YieldAwaitable yieldAwaitable = default(YieldAwaitable);
YieldAwaitable yieldAwaitable2 = yieldAwaitable;
await yieldAwaitable2;
#else
await default(YieldAwaitable);
#endif
}
public async void AwaitDefaultHopToThreadPool()
{
#if LEGACY_VBC || (OPTIMIZE && !ROSLYN4)
HopToThreadPoolAwaitable hopToThreadPoolAwaitable = default(HopToThreadPoolAwaitable);
HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = hopToThreadPoolAwaitable;
await hopToThreadPoolAwaitable2;
#else
await default(HopToThreadPoolAwaitable);
#endif
}
public async Task SimpleVoidTaskMethod()
{
Console.WriteLine("Before");
await Task.Delay(TimeSpan.FromSeconds(1.0));
Console.WriteLine("After");
}
public async Task TaskMethodWithoutAwait()
{
Console.WriteLine("No Await");
}
public async Task CapturingThis()
{
await Task.Delay(memberField);
}
public async Task CapturingThisWithoutAwait()
{
Console.WriteLine(memberField);
}
public async Task<bool> SimpleBoolTaskMethod()
{
var task = new Task(ProcessDataAsync);
task.Start();
task.Wait();
Console.ReadLine();
Console.WriteLine("Before");
await Task.Delay(TimeSpan.FromSeconds(1.0));
Console.WriteLine("After");
return true;
}
public async static void ProcessDataAsync()
public async void TwoAwaitsWithDifferentAwaiterTypes()
{
Task<int> task = HandleFileAsync("C:\\enable1.txt");
Console.WriteLine("Please wait, processing");
int result = await task;
Console.WriteLine("Count: " + result.ToString());
Console.WriteLine("Before");
if (await SimpleBoolTaskMethod())
{
await Task.Delay(TimeSpan.FromSeconds(1.0));
}
Console.WriteLine("After");
}
public async void AwaitInLoopCondition()
{
while (await SimpleBoolTaskMethod())
{
Console.WriteLine("Body");
}
}
public async static Task<int> HandleFileAsync(string file)
public async Task Issue2366a()
{
Console.WriteLine("HandleFile enter");
int count = 0;
using (StreamReader reader = new StreamReader(file))
while (true)
{
string value = await reader.ReadToEndAsync();
count += value.Length;
for (var i = 0; i <= 10000; i += 1)
try
{
var x = value.GetHashCode();
if (x == 0)
count -= 1;
await Task.CompletedTask;
}
catch (Exception projectError)
{
ProjectData.SetProjectError(projectError);
ProjectData.ClearProjectError();
}
}
}
public static async Task<int> GetIntegerSumAsync(IEnumerable<int> items)
{
await Task.Delay(100);
int num = 0;
foreach (int item in items)
{
num = checked(num + item);
}
return num;
}
Console.WriteLine("HandleFile exit");
return count;
public async Task AsyncCatch(bool b, Task<int> task1, Task<int> task2)
{
try
{
Console.WriteLine("Start try");
await task1;
Console.WriteLine("End try");
}
catch (Exception projectError)
{
ProjectData.SetProjectError(projectError);
Console.WriteLine("No await");
ProjectData.ClearProjectError();
}
}
public async Task AsyncCatchThrow(bool b, Task<int> task1, Task<int> task2)
{
try
{
Console.WriteLine("Start try");
await task1;
Console.WriteLine("End try");
}
catch (Exception projectError)
{
ProjectData.SetProjectError(projectError);
Console.WriteLine("No await");
throw;
}
}
public async Task AsyncFinally(bool b, Task<int> task1, Task<int> task2)
{
try
{
Console.WriteLine("Start try");
await task1;
Console.WriteLine("End try");
}
finally
{
Console.WriteLine("No await");
}
}
public static async Task AlwaysThrow()
{
throw new Exception();
}
public static async Task InfiniteLoop()
{
while (true)
{
}
}
public static async Task InfiniteLoopWithAwait()
{
while (true)
{
await Task.Delay(10);
}
}
public async Task AsyncWithLocalVar()
{
object objectValue = RuntimeHelpers.GetObjectValue(new object());
await UseObj(RuntimeHelpers.GetObjectValue(objectValue));
await UseObj(RuntimeHelpers.GetObjectValue(objectValue));
}
public static async Task UseObj(object a)
{
}
}
public struct AsyncInStruct
{
private int i;
public async Task<int> Test(AsyncInStruct xx)
{
checked
{
xx.i++;
i++;
await Task.Yield();
return i + xx.i;
}
}
}
public struct HopToThreadPoolAwaitable : INotifyCompletion
{
public bool IsCompleted { get; set; }
public HopToThreadPoolAwaitable GetAwaiter()
{
return this;
}
public void OnCompleted(Action continuation)
{
Task.Run(continuation);
}
void INotifyCompletion.OnCompleted(Action continuation)
{
//ILSpy generated this explicit interface implementation from .override directive in OnCompleted
this.OnCompleted(continuation);
}
public void GetResult()
{
}
}
}
}

226
ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.vb

@ -1,47 +1,181 @@ @@ -1,47 +1,181 @@
Imports System
Imports System.IO
Module Program
' Sample taken verbatim from https://www.dotnetperls.com/async-vbnet
Sub Main(args As String())
Dim task = New Task(AddressOf ProcessDataAsync)
' Start and wait for task to end.
task.Start()
task.Wait()
Console.ReadLine()
End Sub
Async Sub ProcessDataAsync()
' Create a task Of Integer.
' ... Use HandleFileAsync method with a large file.
Dim task As Task(Of Integer) = HandleFileAsync("C:\enable1.txt")
' This statement runs while HandleFileAsync executes.
Console.WriteLine("Please wait, processing")
' Use await to wait for task to complete.
Dim result As Integer = Await task
Console.WriteLine("Count: " + result.ToString())
End Sub
Async Function HandleFileAsync(ByVal file As String) As Task(Of Integer)
Console.WriteLine("HandleFile enter")
' Open the file.
Dim count As Integer = 0
Using reader As StreamReader = New StreamReader(file)
Dim value As String = Await reader.ReadToEndAsync()
count += value.Length
' Do a slow computation on the file.
For i = 0 To 10000 Step 1
Dim x = value.GetHashCode()
If x = 0 Then
count -= 1
End If
Next
End Using
Console.WriteLine("HandleFile exit")
Return count
End Function
End Module
Imports System.Collections.Generic
Imports System.Threading.Tasks
Imports System.Runtime.CompilerServices
Namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty
Public Class Async
Private memberField As Integer
Private Shared Function [True]() As Boolean
Return True
End Function
Public Async Sub SimpleVoidMethod()
Console.WriteLine("Before")
Await Task.Delay(TimeSpan.FromSeconds(1.0))
Console.WriteLine("After")
End Sub
Public Async Sub VoidMethodWithoutAwait()
Console.WriteLine("No Await")
End Sub
Public Async Sub EmptyVoidMethod()
End Sub
Public Async Sub AwaitYield()
Await Task.Yield()
End Sub
Public Async Sub AwaitDefaultYieldAwaitable()
Await CType(Nothing, YieldAwaitable)
End Sub
Public Async Sub AwaitDefaultHopToThreadPool()
' unlike YieldAwaitable which implements ICriticalNotifyCompletion,
' the HopToThreadPoolAwaitable struct only implements
' INotifyCompletion, so this results in different codegen
Await CType(Nothing, HopToThreadPoolAwaitable)
End Sub
Public Async Function SimpleVoidTaskMethod() As Task
Console.WriteLine("Before")
Await Task.Delay(TimeSpan.FromSeconds(1.0))
Console.WriteLine("After")
End Function
Public Async Function TaskMethodWithoutAwait() As Task
Console.WriteLine("No Await")
End Function
Public Async Function CapturingThis() As Task
Await Task.Delay(memberField)
End Function
Public Async Function CapturingThisWithoutAwait() As Task
Console.WriteLine(memberField)
End Function
Public Async Function SimpleBoolTaskMethod() As Task(Of Boolean)
Console.WriteLine("Before")
Await Task.Delay(TimeSpan.FromSeconds(1.0))
Console.WriteLine("After")
Return True
End Function
Public Async Sub TwoAwaitsWithDifferentAwaiterTypes()
Console.WriteLine("Before")
If Await SimpleBoolTaskMethod() Then
Await Task.Delay(TimeSpan.FromSeconds(1.0))
End If
Console.WriteLine("After")
End Sub
Public Async Sub AwaitInLoopCondition()
While Await SimpleBoolTaskMethod()
Console.WriteLine("Body")
End While
End Sub
Public Async Function Issue2366a() As Task
While True
Try
Await Task.CompletedTask
Catch
End Try
End While
End Function
Public Shared Async Function GetIntegerSumAsync(ByVal items As IEnumerable(Of Integer)) As Task(Of Integer)
Await Task.Delay(100)
Dim num = 0
For Each item In items
num += item
Next
Return num
End Function
Public Async Function AsyncCatch(ByVal b As Boolean, ByVal task1 As Task(Of Integer), ByVal task2 As Task(Of Integer)) As Task
Try
Console.WriteLine("Start try")
Await task1
Console.WriteLine("End try")
Catch ex As Exception
Console.WriteLine("No await")
End Try
End Function
Public Async Function AsyncCatchThrow(ByVal b As Boolean, ByVal task1 As Task(Of Integer), ByVal task2 As Task(Of Integer)) As Task
Try
Console.WriteLine("Start try")
Await task1
Console.WriteLine("End try")
Catch ex As Exception
Console.WriteLine("No await")
Throw
End Try
End Function
Public Async Function AsyncFinally(ByVal b As Boolean, ByVal task1 As Task(Of Integer), ByVal task2 As Task(Of Integer)) As Task
Try
Console.WriteLine("Start try")
Await task1
Console.WriteLine("End try")
Finally
Console.WriteLine("No await")
End Try
End Function
Public Shared Async Function AlwaysThrow() As Task
Throw New Exception
End Function
Public Shared Async Function InfiniteLoop() As Task
While True
End While
End Function
Public Shared Async Function InfiniteLoopWithAwait() As Task
While True
Await Task.Delay(10)
End While
End Function
Public Async Function AsyncWithLocalVar() As Task
Dim a As Object = New Object()
Await UseObj(a)
Await UseObj(a)
End Function
Public Shared Async Function UseObj(ByVal a As Object) As Task
End Function
End Class
Public Structure AsyncInStruct
Private i As Integer
Public Async Function Test(ByVal xx As AsyncInStruct) As Task(Of Integer)
xx.i += 1
i += 1
Await Task.Yield()
Return i + xx.i
End Function
End Structure
Public Structure HopToThreadPoolAwaitable
Implements INotifyCompletion
Public Property IsCompleted As Boolean
Public Function GetAwaiter() As HopToThreadPoolAwaitable
Return Me
End Function
Public Sub OnCompleted(ByVal continuation As Action) Implements INotifyCompletion.OnCompleted
Task.Run(continuation)
End Sub
Public Sub GetResult()
End Sub
End Structure
End Namespace

6
ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs

@ -89,10 +89,10 @@ namespace ICSharpCode.Decompiler.Tests @@ -89,10 +89,10 @@ namespace ICSharpCode.Decompiler.Tests
CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest,
};
[Test, Ignore("Implement VB async/await")]
[Test]
public async Task Async([ValueSource(nameof(defaultOptions))] CompilerOptions options)
{
await Run(options: options);
await Run(options: options | CompilerOptions.Library);
}
[Test] // TODO: legacy VB compound assign
@ -142,7 +142,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -142,7 +142,7 @@ namespace ICSharpCode.Decompiler.Tests
}
var executable = await Tester.CompileVB(vbFile, options | CompilerOptions.ReferenceVisualBasic, exeFile).ConfigureAwait(false);
var decompiled = await Tester.DecompileCSharp(executable.PathToAssembly, settings).ConfigureAwait(false);
var decompiled = await Tester.DecompileCSharp(executable.PathToAssembly, settings ?? new DecompilerSettings { FileScopedNamespaces = false }).ConfigureAwait(false);
CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(options).ToArray());
Tester.RepeatOnIOError(() => File.Delete(decompiled));

290
ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
// Copyright (c) 2017 Daniel Grunwald
// Copyright (c) 2017 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
@ -105,6 +105,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -105,6 +105,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// across the yield point.
Dictionary<Block, (ILVariable awaiterVar, IField awaiterField)> awaitBlocks = new Dictionary<Block, (ILVariable awaiterVar, IField awaiterField)>();
bool isVisualBasicStateMachine;
int catchHandlerOffset;
List<AsyncDebugInfo.Await> awaitDebugInfos = new List<AsyncDebugInfo.Await>();
@ -158,8 +160,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -158,8 +160,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// and inlining because TranslateFieldsToLocalAccess() might have opened up new inlining opportunities.
function.RunTransforms(CSharpDecompiler.EarlyILTransforms(), context);
AwaitInCatchTransform.Run(function, context);
AwaitInFinallyTransform.Run(function, context);
if (!isVisualBasicStateMachine)
{
AwaitInCatchTransform.Run(function, context);
AwaitInFinallyTransform.Run(function, context);
}
awaitDebugInfos.SortBy(row => row.YieldOffset);
function.AsyncDebugInfo = new AsyncDebugInfo(catchHandlerOffset, awaitDebugInfos.ToImmutableArray());
@ -180,6 +185,23 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -180,6 +185,23 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
EarlyExpressionTransforms.StObjToStLoc(stobj, context);
}
if (isVisualBasicStateMachine)
{
// Visual Basic state machines often contain stack slots for ldflda instruction for the builder field.
// This messes up the matching code in AnalyzeAwaitBlock() and causes it to fail.
// Propagating these stack stores makes the matching code work without the need for a lot of
// additional matching code to handle these stack slots.
foreach (var stloc in function.Descendants.OfType<StLoc>().Where(s => s.Variable.Kind == VariableKind.StackSlot && s.Variable.IsSingleDefinition
&& s.Value.MatchLdFlda(out var target, out var field) && field.Equals(builderField) && target.MatchLdThis()).ToList())
{
CopyPropagation.Propagate(stloc, context);
}
// Remove lone 'ldc.i4', present in older Roslyn VB compiler output
foreach (var block in function.Descendants.OfType<Block>())
block.Instructions.RemoveAll(inst => inst.OpCode == OpCode.LdcI4);
}
// Copy-propagate temporaries holding a copy of 'this'.
foreach (var stloc in function.Descendants.OfType<StLoc>().Where(s => s.Variable.IsSingleDefinition && s.Value.MatchLdThis()).ToList())
{
@ -335,6 +357,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -335,6 +357,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return false;
}
if (IsPotentialVisualBasicStateMachineInitialiation(body[0], stateMachineVar))
{
// Visual Basic compiler uses a different order of field assignements
if (MatchVisualBasicStateMachineFieldAssignements(body, stateMachineVar))
{
isVisualBasicStateMachine = true;
return true;
}
// If the Visual Basic matching code failed, reset the map in case it
// was partially populated and try matching the C# state machine initialization.
fieldToParameterMap.Clear();
}
// Check the last field assignment - this should be the state field
// stfld <>1__state(ldloca stateField, ldc.i4 -1)
if (!MatchStFld(body[pos], stateMachineVar, out stateField, out var initialStateExpr))
@ -388,6 +423,62 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -388,6 +423,62 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return builderFieldIsInitialized;
}
bool IsPotentialVisualBasicStateMachineInitialiation(ILInstruction inst, ILVariable stateMachineVar)
{
// stobj VB$StateMachine(ldloca stateMachine, default.value VB$StateMachine)
// Used by VBC (Debug and Release) and Roslyn VB (Release) builds
if (inst.MatchStObj(out var target, out var val, out var type1) && target.MatchLdLoca(stateMachineVar) &&
val.MatchDefaultValue(out var type2) && type1.Equals(type2))
return true;
// stloc stateMachine(newobj VB$StateMachine..ctor())
// Used by Roslyn VB (Debug) builds
if (inst.MatchStLoc(stateMachineVar, out var init) && init is NewObj newObj && newObj.Arguments.Count == 0 &&
stateMachineType.Equals(newObj.Method.DeclaringTypeDefinition))
return true;
return false;
}
bool MatchVisualBasicStateMachineFieldAssignements(IList<ILInstruction> body, ILVariable stateMachineVar)
{
// stfld $State(ldloca stateField, ldc.i4 -1)
if (!MatchStFld(body[body.Count - 4], stateMachineVar, out stateField, out var initialStateExpr))
return false;
if (!initialStateExpr.MatchLdcI4(out initialState))
return false;
if (initialState != -1)
return false;
// stfld $Builder(ldloca stateMachine, call Create())
if (!MatchStFld(body[body.Count - 3], stateMachineVar, out var builderField2, out var fieldInit))
return false;
if (!builderField.Equals(builderField2))
return false;
if (fieldInit is not Call { Method: { Name: "Create" }, Arguments: { Count: 0 } })
return false;
for (int i = 1; i < body.Count - 4; i++)
{
if (!MatchStFld(body[i], stateMachineVar, out var field, out fieldInit))
return false;
// Legacy Visual Basic compiler can emit a call to RuntimeHelpers.GetObjectValue here
if (fieldInit is Call call && call.Arguments.Count == 1 && call.Method.IsStatic && call.Method.Name == "GetObjectValue")
fieldInit = call.Arguments[0];
if (fieldInit.MatchLdLoc(out var v) && v.Kind == VariableKind.Parameter)
{
// OK, copies parameter into state machine
fieldToParameterMap[field] = v;
}
else if (fieldInit is LdObj ldobj && ldobj.Target.MatchLdThis())
{
// stfld <>4__this(ldloc stateMachine, ldobj AsyncInStruct(ldloc this))
fieldToParameterMap[field] = ((LdLoc)ldobj.Target).Variable;
}
else
return false;
}
return true;
}
/// <summary>
/// Matches a (potentially virtual) instance method call.
/// </summary>
@ -622,6 +713,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -622,6 +713,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
bool[] blocksAnalyzed = new bool[blockContainer.Blocks.Count];
cachedStateVar = null;
int pos = 0;
// Visual Basic state machines initialize doFinallyBodies at the start of MoveNext()
// stloc doFinallyBodies(ldc.i4 1)
if (isVisualBasicStateMachine && blockContainer.EntryPoint.Instructions[pos].MatchStLoc(out var v, out var ldci4) &&
ldci4.MatchLdcI4(1))
{
doFinallyBodies = v;
pos++;
}
while (blockContainer.EntryPoint.Instructions[pos] is StLoc stloc)
{
// stloc V_1(ldfld <>4__this(ldloc this))
@ -651,6 +750,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -651,6 +750,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
throw new SymbolicAnalysisFailedException();
// CatchHandler will be validated in ValidateCatchBlock()
// stloc doFinallyBodies(ldc.i4 1)
if (((BlockContainer)mainTryCatch.TryBlock).EntryPoint.Instructions[0] is StLoc initDoFinallyBodies
&& initDoFinallyBodies.Variable.Kind == VariableKind.Local
&& initDoFinallyBodies.Variable.Type.IsKnownType(KnownTypeCode.Boolean)
@ -715,10 +815,36 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -715,10 +815,36 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
var block = blockContainer.Blocks[setResultReturnBlockIndex];
// stfld <>1__state(ldloc this, ldc.i4 -2)
int pos = 0;
// [vb-only] stloc S_10(ldloc this)
if (block.Instructions[pos] is StLoc stlocThisCache && stlocThisCache.Value.MatchLdThis() &&
stlocThisCache.Variable.Kind == VariableKind.StackSlot)
{
pos++;
}
// [vb-only] stloc S_11(ldc.i4 -2)
ILVariable finalStateSlot = null;
int? finalStateSlotValue = null;
if (block.Instructions[pos] is StLoc stlocFinalState && stlocFinalState.Value is LdcI4 ldcI4 &&
stlocFinalState.Variable.Kind == VariableKind.StackSlot)
{
finalStateSlot = stlocFinalState.Variable;
finalStateSlotValue = ldcI4.Value;
pos++;
}
// [vb-only] stloc cachedStateVar(ldloc S_11)
if (block.Instructions[pos] is StLoc stlocCachedState && stlocCachedState.Variable.Kind == VariableKind.Local &&
stlocCachedState.Variable.Index == cachedStateVar?.Index && stlocCachedState.Value.MatchLdLoc(finalStateSlot))
{
pos++;
}
// stfld <>1__state(ldloc this, ldc.i4 -2)
if (!MatchStateAssignment(block.Instructions[pos], out finalState))
throw new SymbolicAnalysisFailedException();
if (finalStateSlotValue is not null && finalState != finalStateSlotValue.Value)
throw new SymbolicAnalysisFailedException();
finalStateKnown = true;
pos++;
@ -874,13 +1000,20 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -874,13 +1000,20 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
bool[] blocksAnalyzed = new bool[handlerContainer.Blocks.Count];
var catchBlock = handlerContainer.EntryPoint;
catchHandlerOffset = catchBlock.StartILOffset;
int pos = 0;
// [vb-only] call SetProjectError(ldloc E_143)
if (isVisualBasicStateMachine && catchBlock.Instructions[pos] is Call call && call.Method.Name == "SetProjectError" &&
call.Arguments.Count == 1 && call.Arguments[0].MatchLdLoc(handler.Variable))
{
pos++;
}
// stloc exception(ldloc E_143)
if (!(catchBlock.Instructions[0] is StLoc stloc))
if (!(catchBlock.Instructions[pos++] is StLoc stloc))
throw new SymbolicAnalysisFailedException();
if (!stloc.Value.MatchLdLoc(handler.Variable))
throw new SymbolicAnalysisFailedException();
// stfld <>1__state(ldloc this, ldc.i4 -2)
if (!MatchStateAssignment(catchBlock.Instructions[1], out int newState))
if (!MatchStateAssignment(catchBlock.Instructions[pos++], out int newState))
throw new SymbolicAnalysisFailedException();
if (finalStateKnown)
{
@ -892,7 +1025,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -892,7 +1025,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
finalState = newState;
finalStateKnown = true;
}
int pos = 2;
if (pos + 2 == catchBlock.Instructions.Count && catchBlock.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst))
{
if (MatchDisposeCombinedTokens(handlerContainer, condition, trueInst, falseInst, blocksAnalyzed, out var setResultAndExitBlock))
@ -924,6 +1056,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -924,6 +1056,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// [optional] call Complete(ldfld <>t__builder(ldloc this))
MatchCompleteCall(catchBlock, ref pos);
// [vb-only] call ClearProjectError()
if (isVisualBasicStateMachine && catchBlock.Instructions[pos] is Call call2 &&
call2.Method.Name == "ClearProjectError" && call2.Arguments.Count == 0)
{
pos++;
}
// leave IL_0000
if (!catchBlock.Instructions[pos].MatchLeave((BlockContainer)moveNextFunction.Body))
throw new SymbolicAnalysisFailedException();
@ -1594,77 +1733,116 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -1594,77 +1733,116 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (!RestoreStack(block, ref pos, stackField))
return false;
// stloc awaiterVar(ldfld awaiterField(ldloc this))
if (!block.Instructions[pos].MatchStLoc(awaiterVar, out var value))
return false;
if (value is CastClass cast && cast.Type.Equals(awaiterVar.Type))
// Visual Basic state machines use a different order of field assignements.
if (isVisualBasicStateMachine)
{
// If the awaiter is a reference type, it might get stored in a field of type `object`
// and cast back to the awaiter type in the resume block
value = cast.Argument;
}
if (!value.MatchLdFld(out var target, out var field))
return false;
if (!target.MatchLdThis())
return false;
if (!field.Equals(awaiterField))
return false;
pos++;
// stloc S_28(ldc.i4 -1)
// stloc cachedStateVar(ldloc S_28)
// stfld <>1__state(ldloc this, ldloc S_28)
if (!MatchStateFieldAssignement(block, ref pos))
return false;
pos++;
// stfld awaiterField(ldloc this, default.value)
if (block.Instructions[pos].MatchStFld(out target, out field, out value)
&& target.MatchLdThis()
&& field.Equals(awaiterField)
&& (value.OpCode == OpCode.DefaultValue || value.OpCode == OpCode.LdNull))
{
// stloc awaiterVar(ldfld awaiterField(ldloc this))
if (!MatchStoreToAwaiterVariable(block.Instructions[pos]))
return false;
pos++;
// [optional] stfld awaiterField(ldloc this, default.value)
MatchResetAwaiterField(block, ref pos);
}
else
{
// {stloc V_6(default.value System.Runtime.CompilerServices.TaskAwaiter)}
// {stobj System.Runtime.CompilerServices.TaskAwaiter`1[[System.Int32]](ldflda <>u__$awaiter4(ldloc this), ldloc V_6) at IL_0163}
if (block.Instructions[pos].MatchStLoc(out var variable, out value) && value.OpCode == OpCode.DefaultValue
&& block.Instructions[pos + 1].MatchStFld(out target, out field, out value)
&& field.Equals(awaiterField)
&& value.MatchLdLoc(variable))
{
pos += 2;
}
// stloc awaiterVar(ldfld awaiterField(ldloc this))
if (!MatchStoreToAwaiterVariable(block.Instructions[pos]))
return false;
pos++;
// [optional] stfld awaiterField(ldloc this, default.value)
MatchResetAwaiterField(block, ref pos);
// stloc S_28(ldc.i4 -1)
// stloc cachedStateVar(ldloc S_28)
// stfld <>1__state(ldloc this, ldloc S_28)
if (!MatchStateFieldAssignement(block, ref pos))
return false;
pos++;
}
return block.Instructions[pos].MatchBranch(completedBlock);
// stloc S_28(ldc.i4 -1)
// stloc cachedStateVar(ldloc S_28)
// stfld <>1__state(ldloc this, ldloc S_28)
ILVariable m1Var = null;
if (block.Instructions[pos] is StLoc stlocM1 && stlocM1.Value.MatchLdcI4(initialState) && stlocM1.Variable.Kind == VariableKind.StackSlot)
bool MatchStoreToAwaiterVariable(ILInstruction instr)
{
m1Var = stlocM1.Variable;
pos++;
// stloc awaiterVar(ldfld awaiterField(ldloc this))
if (!instr.MatchStLoc(awaiterVar, out var value))
return false;
if (value is CastClass cast && cast.Type.Equals(awaiterVar.Type))
{
// If the awaiter is a reference type, it might get stored in a field of type `object`
// and cast back to the awaiter type in the resume block
value = cast.Argument;
}
if (!value.MatchLdFld(out var target, out var field))
return false;
if (!target.MatchLdThis())
return false;
if (!field.Equals(awaiterField))
return false;
return true;
}
if (block.Instructions[pos] is StLoc stlocCachedState)
void MatchResetAwaiterField(Block block, ref int pos)
{
if (stlocCachedState.Variable.Kind == VariableKind.Local && stlocCachedState.Variable.Index == cachedStateVar?.Index)
// stfld awaiterField(ldloc this, default.value)
if (block.Instructions[pos].MatchStFld(out var target, out var field, out var value)
&& target.MatchLdThis()
&& field.Equals(awaiterField)
&& (value.OpCode == OpCode.DefaultValue || value.OpCode == OpCode.LdNull))
{
pos++;
}
else
{
if (stlocCachedState.Value.MatchLdLoc(m1Var) || stlocCachedState.Value.MatchLdcI4(initialState))
pos++;
// {stloc V_6(default.value System.Runtime.CompilerServices.TaskAwaiter)}
// {stobj System.Runtime.CompilerServices.TaskAwaiter`1[[System.Int32]](ldflda <>u__$awaiter4(ldloc this), ldloc V_6) at IL_0163}
if (block.Instructions[pos].MatchStLoc(out var variable, out value) && value.OpCode == OpCode.DefaultValue
&& block.Instructions[pos + 1].MatchStFld(out target, out field, out value)
&& field.Equals(awaiterField)
&& value.MatchLdLoc(variable))
{
pos += 2;
}
}
}
if (block.Instructions[pos].MatchStFld(out target, out field, out value))
bool MatchStateFieldAssignement(Block block, ref int pos)
{
// stloc S_28(ldc.i4 -1)
// stloc cachedStateVar(ldloc S_28)
// stfld <>1__state(ldloc this, ldloc S_28)
ILVariable m1Var = null;
if (block.Instructions[pos] is StLoc stlocM1 && stlocM1.Value.MatchLdcI4(initialState) && stlocM1.Variable.Kind == VariableKind.StackSlot)
{
m1Var = stlocM1.Variable;
pos++;
}
if (block.Instructions[pos] is StLoc stlocCachedState)
{
if (stlocCachedState.Variable.Kind == VariableKind.Local && stlocCachedState.Variable.Index == cachedStateVar?.Index)
{
if (stlocCachedState.Value.MatchLdLoc(m1Var) || stlocCachedState.Value.MatchLdcI4(initialState))
pos++;
}
}
if (!block.Instructions[pos].MatchStFld(out var target, out var field, out var value))
return false;
if (!target.MatchLdThis())
return false;
if (!field.MemberDefinition.Equals(stateField.MemberDefinition))
return false;
if (!(value.MatchLdcI4(initialState) || value.MatchLdLoc(m1Var)))
return false;
pos++;
}
else
{
return false;
return true;
}
return block.Instructions[pos].MatchBranch(completedBlock);
}
private bool RestoreStack(Block block, ref int pos, IField stackField)

Loading…
Cancel
Save