Browse Source

Merge pull request #2853 from ElektroKill/vb-async

pull/2754/head
Siegfried Pammer 3 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. 260
      ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs
  3. 216
      ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.vb
  4. 6
      ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs
  5. 212
      ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs

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

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

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

@ -1,45 +1,255 @@
using System; using System;
using System.IO; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace EquivalentCSharpConsoleApp using Microsoft.VisualBasic.CompilerServices;
namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty
{
public class Async
{
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()
{ {
static class Program Console.WriteLine("No Await");
}
public async Task CapturingThis()
{
await Task.Delay(memberField);
}
public async Task CapturingThisWithoutAwait()
{
Console.WriteLine(memberField);
}
public async Task<bool> SimpleBoolTaskMethod()
{
Console.WriteLine("Before");
await Task.Delay(TimeSpan.FromSeconds(1.0));
Console.WriteLine("After");
return true;
}
public async void TwoAwaitsWithDifferentAwaiterTypes()
{ {
public static void Main(string[] args) Console.WriteLine("Before");
if (await SimpleBoolTaskMethod())
{ {
var task = new Task(ProcessDataAsync); await Task.Delay(TimeSpan.FromSeconds(1.0));
task.Start(); }
task.Wait(); Console.WriteLine("After");
Console.ReadLine();
} }
public async static void ProcessDataAsync() public async void AwaitInLoopCondition()
{
while (await SimpleBoolTaskMethod())
{ {
Task<int> task = HandleFileAsync("C:\\enable1.txt"); Console.WriteLine("Body");
Console.WriteLine("Please wait, processing"); }
int result = await task;
Console.WriteLine("Count: " + result.ToString());
} }
public async static Task<int> HandleFileAsync(string file) public async Task Issue2366a()
{ {
Console.WriteLine("HandleFile enter"); while (true)
int count = 0;
using (StreamReader reader = new StreamReader(file))
{ {
string value = await reader.ReadToEndAsync(); try
count += value.Length;
for (var i = 0; i <= 10000; i += 1)
{ {
var x = value.GetHashCode(); await Task.CompletedTask;
if (x == 0)
count -= 1;
} }
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"); public async Task AsyncCatch(bool b, Task<int> task1, Task<int> task2)
return count; {
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()
{
} }
} }
} }

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

@ -1,47 +1,181 @@
Imports System Imports System
Imports System.IO Imports System.Collections.Generic
Imports System.Threading.Tasks
Module Program Imports System.Runtime.CompilerServices
' Sample taken verbatim from https://www.dotnetperls.com/async-vbnet
Sub Main(args As String()) Namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty
Dim task = New Task(AddressOf ProcessDataAsync) Public Class Async
' Start and wait for task to end. Private memberField As Integer
task.Start()
task.Wait() Private Shared Function [True]() As Boolean
Console.ReadLine() Return True
End Sub End Function
Async Sub ProcessDataAsync() Public Async Sub SimpleVoidMethod()
' Create a task Of Integer. Console.WriteLine("Before")
' ... Use HandleFileAsync method with a large file. Await Task.Delay(TimeSpan.FromSeconds(1.0))
Dim task As Task(Of Integer) = HandleFileAsync("C:\enable1.txt") Console.WriteLine("After")
' This statement runs while HandleFileAsync executes. End Sub
Console.WriteLine("Please wait, processing")
' Use await to wait for task to complete. Public Async Sub VoidMethodWithoutAwait()
Dim result As Integer = Await task Console.WriteLine("No Await")
Console.WriteLine("Count: " + result.ToString()) End Sub
End Sub
Public Async Sub EmptyVoidMethod()
Async Function HandleFileAsync(ByVal file As String) As Task(Of Integer) End Sub
Console.WriteLine("HandleFile enter")
Public Async Sub AwaitYield()
' Open the file. Await Task.Yield()
Dim count As Integer = 0 End Sub
Using reader As StreamReader = New StreamReader(file)
Dim value As String = Await reader.ReadToEndAsync() Public Async Sub AwaitDefaultYieldAwaitable()
count += value.Length Await CType(Nothing, YieldAwaitable)
End Sub
' Do a slow computation on the file.
For i = 0 To 10000 Step 1 Public Async Sub AwaitDefaultHopToThreadPool()
Dim x = value.GetHashCode() ' unlike YieldAwaitable which implements ICriticalNotifyCompletion,
If x = 0 Then ' the HopToThreadPoolAwaitable struct only implements
count -= 1 ' 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 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 Next
End Using 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
Console.WriteLine("HandleFile exit") Public Shared Async Function InfiniteLoopWithAwait() As Task
Return count While True
Await Task.Delay(10)
End While
End Function End Function
End Module
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
CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest,
}; };
[Test, Ignore("Implement VB async/await")] [Test]
public async Task Async([ValueSource(nameof(defaultOptions))] CompilerOptions options) 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 [Test] // TODO: legacy VB compound assign
@ -142,7 +142,7 @@ namespace ICSharpCode.Decompiler.Tests
} }
var executable = await Tester.CompileVB(vbFile, options | CompilerOptions.ReferenceVisualBasic, exeFile).ConfigureAwait(false); 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()); CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(options).ToArray());
Tester.RepeatOnIOError(() => File.Delete(decompiled)); Tester.RepeatOnIOError(() => File.Delete(decompiled));

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

@ -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 // 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 // software and associated documentation files (the "Software"), to deal in the Software
@ -105,6 +105,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// across the yield point. // across the yield point.
Dictionary<Block, (ILVariable awaiterVar, IField awaiterField)> awaitBlocks = new Dictionary<Block, (ILVariable awaiterVar, IField awaiterField)>(); Dictionary<Block, (ILVariable awaiterVar, IField awaiterField)> awaitBlocks = new Dictionary<Block, (ILVariable awaiterVar, IField awaiterField)>();
bool isVisualBasicStateMachine;
int catchHandlerOffset; int catchHandlerOffset;
List<AsyncDebugInfo.Await> awaitDebugInfos = new List<AsyncDebugInfo.Await>(); List<AsyncDebugInfo.Await> awaitDebugInfos = new List<AsyncDebugInfo.Await>();
@ -158,8 +160,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// and inlining because TranslateFieldsToLocalAccess() might have opened up new inlining opportunities. // and inlining because TranslateFieldsToLocalAccess() might have opened up new inlining opportunities.
function.RunTransforms(CSharpDecompiler.EarlyILTransforms(), context); function.RunTransforms(CSharpDecompiler.EarlyILTransforms(), context);
if (!isVisualBasicStateMachine)
{
AwaitInCatchTransform.Run(function, context); AwaitInCatchTransform.Run(function, context);
AwaitInFinallyTransform.Run(function, context); AwaitInFinallyTransform.Run(function, context);
}
awaitDebugInfos.SortBy(row => row.YieldOffset); awaitDebugInfos.SortBy(row => row.YieldOffset);
function.AsyncDebugInfo = new AsyncDebugInfo(catchHandlerOffset, awaitDebugInfos.ToImmutableArray()); function.AsyncDebugInfo = new AsyncDebugInfo(catchHandlerOffset, awaitDebugInfos.ToImmutableArray());
@ -180,6 +185,23 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
EarlyExpressionTransforms.StObjToStLoc(stobj, context); 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'. // 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()) 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
return false; 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 // Check the last field assignment - this should be the state field
// stfld <>1__state(ldloca stateField, ldc.i4 -1) // stfld <>1__state(ldloca stateField, ldc.i4 -1)
if (!MatchStFld(body[pos], stateMachineVar, out stateField, out var initialStateExpr)) if (!MatchStFld(body[pos], stateMachineVar, out stateField, out var initialStateExpr))
@ -388,6 +423,62 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return builderFieldIsInitialized; 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> /// <summary>
/// Matches a (potentially virtual) instance method call. /// Matches a (potentially virtual) instance method call.
/// </summary> /// </summary>
@ -622,6 +713,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
bool[] blocksAnalyzed = new bool[blockContainer.Blocks.Count]; bool[] blocksAnalyzed = new bool[blockContainer.Blocks.Count];
cachedStateVar = null; cachedStateVar = null;
int pos = 0; 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) while (blockContainer.EntryPoint.Instructions[pos] is StLoc stloc)
{ {
// stloc V_1(ldfld <>4__this(ldloc this)) // stloc V_1(ldfld <>4__this(ldloc this))
@ -651,6 +750,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
// CatchHandler will be validated in ValidateCatchBlock() // CatchHandler will be validated in ValidateCatchBlock()
// stloc doFinallyBodies(ldc.i4 1)
if (((BlockContainer)mainTryCatch.TryBlock).EntryPoint.Instructions[0] is StLoc initDoFinallyBodies if (((BlockContainer)mainTryCatch.TryBlock).EntryPoint.Instructions[0] is StLoc initDoFinallyBodies
&& initDoFinallyBodies.Variable.Kind == VariableKind.Local && initDoFinallyBodies.Variable.Kind == VariableKind.Local
&& initDoFinallyBodies.Variable.Type.IsKnownType(KnownTypeCode.Boolean) && initDoFinallyBodies.Variable.Type.IsKnownType(KnownTypeCode.Boolean)
@ -715,10 +815,36 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
var block = blockContainer.Blocks[setResultReturnBlockIndex]; var block = blockContainer.Blocks[setResultReturnBlockIndex];
// stfld <>1__state(ldloc this, ldc.i4 -2)
int pos = 0; 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)) if (!MatchStateAssignment(block.Instructions[pos], out finalState))
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
if (finalStateSlotValue is not null && finalState != finalStateSlotValue.Value)
throw new SymbolicAnalysisFailedException();
finalStateKnown = true; finalStateKnown = true;
pos++; pos++;
@ -874,13 +1000,20 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
bool[] blocksAnalyzed = new bool[handlerContainer.Blocks.Count]; bool[] blocksAnalyzed = new bool[handlerContainer.Blocks.Count];
var catchBlock = handlerContainer.EntryPoint; var catchBlock = handlerContainer.EntryPoint;
catchHandlerOffset = catchBlock.StartILOffset; 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) // stloc exception(ldloc E_143)
if (!(catchBlock.Instructions[0] is StLoc stloc)) if (!(catchBlock.Instructions[pos++] is StLoc stloc))
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
if (!stloc.Value.MatchLdLoc(handler.Variable)) if (!stloc.Value.MatchLdLoc(handler.Variable))
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
// stfld <>1__state(ldloc this, ldc.i4 -2) // 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(); throw new SymbolicAnalysisFailedException();
if (finalStateKnown) if (finalStateKnown)
{ {
@ -892,7 +1025,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
finalState = newState; finalState = newState;
finalStateKnown = true; finalStateKnown = true;
} }
int pos = 2;
if (pos + 2 == catchBlock.Instructions.Count && catchBlock.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst)) 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)) if (MatchDisposeCombinedTokens(handlerContainer, condition, trueInst, falseInst, blocksAnalyzed, out var setResultAndExitBlock))
@ -924,6 +1056,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// [optional] call Complete(ldfld <>t__builder(ldloc this)) // [optional] call Complete(ldfld <>t__builder(ldloc this))
MatchCompleteCall(catchBlock, ref pos); 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 // leave IL_0000
if (!catchBlock.Instructions[pos].MatchLeave((BlockContainer)moveNextFunction.Body)) if (!catchBlock.Instructions[pos].MatchLeave((BlockContainer)moveNextFunction.Body))
throw new SymbolicAnalysisFailedException(); throw new SymbolicAnalysisFailedException();
@ -1594,8 +1733,47 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (!RestoreStack(block, ref pos, stackField)) if (!RestoreStack(block, ref pos, stackField))
return false; return false;
// Visual Basic state machines use a different order of field assignements.
if (isVisualBasicStateMachine)
{
// 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++;
// 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 awaiterVar(ldfld awaiterField(ldloc this)) // stloc awaiterVar(ldfld awaiterField(ldloc this))
if (!block.Instructions[pos].MatchStLoc(awaiterVar, out var value)) 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);
bool MatchStoreToAwaiterVariable(ILInstruction instr)
{
// stloc awaiterVar(ldfld awaiterField(ldloc this))
if (!instr.MatchStLoc(awaiterVar, out var value))
return false; return false;
if (value is CastClass cast && cast.Type.Equals(awaiterVar.Type)) if (value is CastClass cast && cast.Type.Equals(awaiterVar.Type))
{ {
@ -1609,10 +1787,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return false; return false;
if (!field.Equals(awaiterField)) if (!field.Equals(awaiterField))
return false; return false;
pos++; return true;
}
void MatchResetAwaiterField(Block block, ref int pos)
{
// stfld awaiterField(ldloc this, default.value) // stfld awaiterField(ldloc this, default.value)
if (block.Instructions[pos].MatchStFld(out target, out field, out value) if (block.Instructions[pos].MatchStFld(out var target, out var field, out var value)
&& target.MatchLdThis() && target.MatchLdThis()
&& field.Equals(awaiterField) && field.Equals(awaiterField)
&& (value.OpCode == OpCode.DefaultValue || value.OpCode == OpCode.LdNull)) && (value.OpCode == OpCode.DefaultValue || value.OpCode == OpCode.LdNull))
@ -1631,7 +1812,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
pos += 2; pos += 2;
} }
} }
}
bool MatchStateFieldAssignement(Block block, ref int pos)
{
// stloc S_28(ldc.i4 -1) // stloc S_28(ldc.i4 -1)
// stloc cachedStateVar(ldloc S_28) // stloc cachedStateVar(ldloc S_28)
// stfld <>1__state(ldloc this, ldloc S_28) // stfld <>1__state(ldloc this, ldloc S_28)
@ -1649,22 +1833,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
pos++; pos++;
} }
} }
if (block.Instructions[pos].MatchStFld(out target, out field, out value)) if (!block.Instructions[pos].MatchStFld(out var target, out var field, out var value))
{ return false;
if (!target.MatchLdThis()) if (!target.MatchLdThis())
return false; return false;
if (!field.MemberDefinition.Equals(stateField.MemberDefinition)) if (!field.MemberDefinition.Equals(stateField.MemberDefinition))
return false; return false;
if (!(value.MatchLdcI4(initialState) || value.MatchLdLoc(m1Var))) if (!(value.MatchLdcI4(initialState) || value.MatchLdLoc(m1Var)))
return false; return false;
pos++; return true;
}
else
{
return false;
} }
return block.Instructions[pos].MatchBranch(completedBlock);
} }
private bool RestoreStack(Block block, ref int pos, IField stackField) private bool RestoreStack(Block block, ref int pos, IField stackField)

Loading…
Cancel
Save