diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 5adef4112..029058449 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -72,6 +72,7 @@ namespace ICSharpCode.Decompiler.CSharp
new ILInlining(),
new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms
new YieldReturnDecompiler(), // must run after inlining but before loop detection
+ new AsyncAwaitDecompiler(), // must run after inlining but before loop detection
new DetectExitPoints(canIntroduceExitForReturn: false),
new BlockILTransform {
PostOrderTransforms = {
@@ -652,6 +653,10 @@ namespace ICSharpCode.Decompiler.CSharp
}
RemoveAttribute(entityDecl, new TopLevelTypeName("System.Runtime.CompilerServices", "IteratorStateMachineAttribute"));
}
+ if (function.IsAsync) {
+ entityDecl.Modifiers |= Modifiers.Async;
+ RemoveAttribute(entityDecl, new TopLevelTypeName("System.Runtime.CompilerServices", "AsyncStateMachineAttribute"));
+ }
}
void RemoveAttribute(EntityDeclaration entityDecl, FullTypeName attrName)
diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
index 331abfaee..799fb3fc5 100644
--- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
@@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
return new GotoStatement(label);
}
-
+
protected internal override Statement VisitThrow(Throw inst)
{
return new ThrowStatement(exprBuilder.Translate(inst.Argument));
@@ -182,7 +182,8 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override Statement VisitReturn(Return inst)
{
- return new ReturnStatement(exprBuilder.Translate(inst.Value).ConvertTo(currentMethod.ReturnType, exprBuilder));
+ IType targetType = currentFunction.IsAsync ? currentFunction.AsyncReturnType : currentMethod.ReturnType;
+ return new ReturnStatement(exprBuilder.Translate(inst.Value).ConvertTo(targetType, exprBuilder));
}
protected internal override Statement VisitYieldReturn(YieldReturn inst)
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
index ae4775303..750231c49 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
@@ -168,6 +168,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
case InvocationExpression ie:
case ObjectCreateExpression oce:
case AssignmentExpression ae:
+ case ErrorExpression ee:
return true;
case UnaryOperatorExpression uoe:
switch (uoe.Operator) {
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index 091026c7a..d716877ad 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -72,7 +72,7 @@ namespace ICSharpCode.Decompiler
}
}
- bool asyncAwait = true;
+ bool asyncAwait = false;
///
/// Decompile async methods.
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 276e47dae..ae7d5674e 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -264,6 +264,7 @@
+
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
new file mode 100644
index 000000000..ceed23337
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
@@ -0,0 +1,387 @@
+using ICSharpCode.Decompiler.IL.Transforms;
+using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.Util;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ICSharpCode.Decompiler.IL.ControlFlow
+{
+ ///
+ /// Decompiler step for C# 5 async/await.
+ ///
+ class AsyncAwaitDecompiler : IILTransform
+ {
+ /*
+ public static bool IsCompilerGeneratedStateMachine(TypeDefinition type)
+ {
+ if (!(type.DeclaringType != null && type.IsCompilerGenerated()))
+ return false;
+ foreach (TypeReference i in type.Interfaces) {
+ if (i.Namespace == "System.Runtime.CompilerServices" && i.Name == "IAsyncStateMachine")
+ return true;
+ }
+ return false;
+ }
+ */
+
+ enum AsyncMethodType
+ {
+ Void,
+ Task,
+ TaskOfT
+ }
+
+ ILTransformContext context;
+
+ // These fields are set by MatchTaskCreationPattern()
+ IType taskType; // return type of the async method
+ IType underlyingReturnType; // return type of the method (only the "T" for Task{T})
+ AsyncMethodType methodType;
+ ITypeDefinition stateMachineStruct;
+ ITypeDefinition builderType;
+ IField builderField;
+ IField stateField;
+ int initialState;
+ Dictionary fieldToParameterMap = new Dictionary();
+
+ // These fields are set by AnalyzeMoveNext():
+ ILFunction moveNextFunction;
+ ILVariable cachedStateVar; // variable in MoveNext that caches the stateField.
+ TryCatch mainTryCatch;
+ Block setResultAndExitBlock; // block that is jumped to for return statements
+ int finalState; // final state after the setResultAndExitBlock
+ ILVariable resultVar; // the variable that gets returned by the setResultAndExitBlock
+
+ public void Run(ILFunction function, ILTransformContext context)
+ {
+ if (!context.Settings.AsyncAwait)
+ return; // abort if async/await decompilation is disabled
+ this.context = context;
+ fieldToParameterMap.Clear();
+ if (!MatchTaskCreationPattern(function))
+ return;
+ try {
+ AnalyzeMoveNext();
+ ValidateCatchBlock();
+ InlineBodyOfMoveNext(function);
+ } catch (SymbolicAnalysisFailedException) {
+ return;
+ }
+ }
+
+ #region MatchTaskCreationPattern
+ bool MatchTaskCreationPattern(ILFunction function)
+ {
+ if (!(function.Body is BlockContainer blockContainer))
+ return false;
+ if (blockContainer.Blocks.Count != 1)
+ return false;
+ var body = blockContainer.EntryPoint.Instructions;
+ if (body.Count < 5)
+ return false;
+ /* Example:
+ V_0 is an instance of the compiler-generated struct,
+ V_1 is an instance of the builder struct
+ Block IL_0000 (incoming: 1) {
+ stobj System.Runtime.CompilerServices.AsyncVoidMethodBuilder(ldflda [Field ICSharpCode.Decompiler.Tests.TestCases.Pretty.Async+d__3.<>t__builder](ldloca V_0), call Create())
+ stobj System.Int32(ldflda [Field ICSharpCode.Decompiler.Tests.TestCases.Pretty.Async+d__3.<>1__state](ldloca V_0), ldc.i4 -1)
+ stloc V_1(ldobj System.Runtime.CompilerServices.AsyncVoidMethodBuilder(ldflda [Field ICSharpCode.Decompiler.Tests.TestCases.Pretty.Async+d__3.<>t__builder](ldloc V_0)))
+ call Start(ldloca V_1, ldloca V_0)
+ leave IL_0000 (or ret for non-void async methods)
+ }
+ */
+
+ // Check the second-to-last instruction (the start call) first, as we can get the most information from that
+ if (!(body[body.Count - 2] is Call startCall))
+ return false;
+ if (startCall.Method.Name != "Start")
+ return false;
+ taskType = context.TypeSystem.Resolve(function.Method.ReturnType);
+ builderType = startCall.Method.DeclaringTypeDefinition;
+ const string ns = "System.Runtime.CompilerServices";
+ if (taskType.IsKnownType(KnownTypeCode.Void)) {
+ methodType = AsyncMethodType.Void;
+ underlyingReturnType = taskType;
+ if (builderType?.FullTypeName != new TopLevelTypeName(ns, "AsyncVoidMethodBuilder"))
+ return false;
+ } else if (taskType.IsKnownType(KnownTypeCode.Task)) {
+ methodType = AsyncMethodType.Task;
+ underlyingReturnType = context.TypeSystem.Compilation.FindType(KnownTypeCode.Void);
+ if (builderType?.FullTypeName != new TopLevelTypeName(ns, "AsyncTaskMethodBuilder", 0))
+ return false;
+ } else if (taskType.IsKnownType(KnownTypeCode.TaskOfT)) {
+ methodType = AsyncMethodType.TaskOfT;
+ underlyingReturnType = TaskType.UnpackTask(context.TypeSystem.Compilation, taskType);
+ if (builderType?.FullTypeName != new TopLevelTypeName(ns, "AsyncTaskMethodBuilder", 1))
+ return false;
+ } else {
+ return false; // TODO: generalized async return type
+ }
+ if (startCall.Arguments.Count != 2)
+ return false;
+ if (!startCall.Arguments[0].MatchLdLocRef(out ILVariable builderVar))
+ return false;
+ if (!startCall.Arguments[1].MatchLdLoca(out ILVariable stateMachineVar))
+ return false;
+ stateMachineStruct = stateMachineVar.Type.GetDefinition();
+ if (stateMachineStruct?.Kind != TypeKind.Struct)
+ return false;
+
+ // Check third-to-last instruction (copy of builder)
+ // stloc builder(ldfld StateMachine::<>t__builder(ldloc stateMachine))
+ if (!body[body.Count - 3].MatchStLoc(builderVar, out var loadBuilderExpr))
+ return false;
+ if (!loadBuilderExpr.MatchLdFld(out var loadStateMachineForBuilderExpr, out builderField))
+ return false;
+ builderField = (IField)builderField.MemberDefinition;
+ if (!(loadStateMachineForBuilderExpr.MatchLdLocRef(stateMachineVar) || loadStateMachineForBuilderExpr.MatchLdLoc(stateMachineVar)))
+ return false;
+
+ // Check the last instruction (ret)
+ if (methodType == AsyncMethodType.Void) {
+ if (!body.Last().MatchLeave(blockContainer))
+ return false;
+ } else {
+ // ret(call(AsyncTaskMethodBuilder::get_Task, ldflda(StateMachine::<>t__builder, ldloca(stateMachine))))
+ if (!body.Last().MatchReturn(out var returnValue))
+ return false;
+ if (!MatchCall(returnValue, "get_Task", out var getTaskArgs) || getTaskArgs.Count != 1)
+ return false;
+ ILInstruction target;
+ IField builderField2;
+ if (builderType.IsReferenceType == true) {
+ if (!getTaskArgs[0].MatchLdFld(out target, out builderField2))
+ return false;
+ } else {
+ if (!getTaskArgs[0].MatchLdFlda(out target, out builderField2))
+ return false;
+ }
+ if (builderField2.MemberDefinition != builderField)
+ return false;
+ if (!target.MatchLdLoca(stateMachineVar))
+ return false;
+ }
+
+ // Check the last field assignment - this should be the state field
+ // stfld <>1__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;
+
+ // Check the second-to-last field assignment - this should be the builder field
+ // stfld StateMachine.builder(ldloca stateMachine, call Create())
+ if (!MatchStFld(body[body.Count - 5], stateMachineVar, out var builderField3, out var builderInitialization))
+ return false;
+ if (builderField3 != builderField)
+ return false;
+ if (!(builderInitialization is Call createCall))
+ return false;
+ if (createCall.Method.Name != "Create" || createCall.Arguments.Count != 0)
+ return false;
+
+ for (int i = 0; i < body.Count - 5; i++) {
+ // stfld StateMachine.field(ldloca stateMachine, ldvar(param))
+ if (!MatchStFld(body[i], stateMachineVar, out var field, out var fieldInit))
+ return false;
+ if (!fieldInit.MatchLdLoc(out var v))
+ return false;
+ if (v.Kind != VariableKind.Parameter)
+ return false;
+ fieldToParameterMap[field] = v;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Matches a (potentially virtual) instance method call.
+ ///
+ static bool MatchCall(ILInstruction inst, string name, out InstructionCollection args)
+ {
+ if (inst is CallInstruction call && (call.OpCode == OpCode.Call || call.OpCode == OpCode.CallVirt)
+ && call.Method.Name == name && !call.Method.IsStatic)
+ {
+ args = call.Arguments;
+ return args.Count > 0;
+ }
+ args = null;
+ return false;
+ }
+
+ ///
+ /// Matches a store to the state machine.
+ ///
+ static bool MatchStFld(ILInstruction stfld, ILVariable stateMachineVar, out IField field, out ILInstruction value)
+ {
+ if (!stfld.MatchStFld(out var target, out field, out value))
+ return false;
+ field = field.MemberDefinition as IField;
+ return field != null && target.MatchLdLoca(stateMachineVar);
+ }
+ #endregion
+
+ #region AnalyzeMoveNext
+ ///
+ /// First peek into MoveNext(); analyzes everything outside the big try-catch.
+ ///
+ void AnalyzeMoveNext()
+ {
+ var moveNextMethod = context.TypeSystem.GetCecil(stateMachineStruct)?.Methods.FirstOrDefault(f => f.Name == "MoveNext");
+ if (moveNextMethod == null)
+ throw new SymbolicAnalysisFailedException();
+ moveNextFunction = YieldReturnDecompiler.CreateILAst(moveNextMethod, context);
+ if (!(moveNextFunction.Body is BlockContainer blockContainer))
+ throw new SymbolicAnalysisFailedException();
+ if (blockContainer.Blocks.Count != 2)
+ throw new SymbolicAnalysisFailedException();
+ if (blockContainer.EntryPoint.IncomingEdgeCount != 1)
+ throw new SymbolicAnalysisFailedException();
+ int pos = 0;
+ if (blockContainer.EntryPoint.Instructions[0].MatchStLoc(out cachedStateVar, out var cachedStateInit)) {
+ // stloc(cachedState, ldfld(valuetype StateMachineStruct::<>1__state, ldloc(this)))
+ if (!cachedStateInit.MatchLdFld(out var target, out var loadedField))
+ throw new SymbolicAnalysisFailedException();
+ if (!target.MatchLdThis())
+ throw new SymbolicAnalysisFailedException();
+ if (loadedField.MemberDefinition != stateField)
+ throw new SymbolicAnalysisFailedException();
+ ++pos;
+ }
+ mainTryCatch = blockContainer.EntryPoint.Instructions[pos] as TryCatch;
+ // TryCatch will be validated in ValidateCatchBlock()
+
+ setResultAndExitBlock = blockContainer.Blocks[1];
+ // stobj System.Int32(ldflda [Field ICSharpCode.Decompiler.Tests.TestCases.Pretty.Async+d__7.<>1__state](ldloc this), ldc.i4 -2)
+ // call SetResult(ldflda [Field ICSharpCode.Decompiler.Tests.TestCases.Pretty.Async+d__7.<>t__builder](ldloc this), ldloc result)
+ // leave IL_0000
+ if (setResultAndExitBlock.Instructions.Count != 3)
+ throw new SymbolicAnalysisFailedException();
+ if (!MatchStateAssignment(setResultAndExitBlock.Instructions[0], out finalState))
+ throw new SymbolicAnalysisFailedException();
+ if (!MatchCall(setResultAndExitBlock.Instructions[1], "SetResult", out var args))
+ throw new SymbolicAnalysisFailedException();
+ if (!IsBuilderFieldOnThis(args[0]))
+ throw new SymbolicAnalysisFailedException();
+ if (methodType == AsyncMethodType.TaskOfT) {
+ if (args.Count != 2)
+ throw new SymbolicAnalysisFailedException();
+ if (!args[1].MatchLdLoc(out resultVar))
+ throw new SymbolicAnalysisFailedException();
+ } else {
+ if (args.Count != 1)
+ throw new SymbolicAnalysisFailedException();
+ }
+ if (!setResultAndExitBlock.Instructions[2].MatchLeave(blockContainer))
+ throw new SymbolicAnalysisFailedException();
+ }
+
+ void ValidateCatchBlock()
+ {
+ // catch E_143 : System.Exception if (ldc.i4 1) BlockContainer {
+ // Block IL_008f (incoming: 1) {
+ // stloc exception(ldloc E_143)
+ // stfld <>1__state(ldloc this, ldc.i4 -2)
+ // call SetException(ldfld <>t__builder(ldloc this), ldloc exception)
+ // leave IL_0000
+ // }
+ // }
+ if (mainTryCatch?.Handlers.Count != 1)
+ throw new SymbolicAnalysisFailedException();
+ var handler = mainTryCatch.Handlers[0];
+ if (!handler.Variable.Type.IsKnownType(KnownTypeCode.Exception))
+ throw new SymbolicAnalysisFailedException();
+ if (!handler.Filter.MatchLdcI4(1))
+ throw new SymbolicAnalysisFailedException();
+ var catchBlock = YieldReturnDecompiler.SingleBlock(handler.Body);
+ if (catchBlock?.Instructions.Count != 4)
+ throw new SymbolicAnalysisFailedException();
+ // stloc exception(ldloc E_143)
+ if (!(catchBlock.Instructions[0] 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) || newState != finalState)
+ throw new SymbolicAnalysisFailedException();
+ // call SetException(ldfld <>t__builder(ldloc this), ldloc exception)
+ if (!MatchCall(catchBlock.Instructions[2], "SetException", out var args))
+ throw new SymbolicAnalysisFailedException();
+ if (args.Count != 2)
+ throw new SymbolicAnalysisFailedException();
+ if (!IsBuilderFieldOnThis(args[0]))
+ throw new SymbolicAnalysisFailedException();
+ if (!args[1].MatchLdLoc(stloc.Variable))
+ throw new SymbolicAnalysisFailedException();
+ // leave IL_0000
+ if (!catchBlock.Instructions[3].MatchLeave((BlockContainer)moveNextFunction.Body))
+ throw new SymbolicAnalysisFailedException();
+ }
+
+ bool IsBuilderFieldOnThis(ILInstruction inst)
+ {
+ IField field;
+ ILInstruction target;
+ if (builderType.IsReferenceType == true) {
+ // ldfld(StateMachine::<>t__builder, ldloc(this))
+ if (!inst.MatchLdFld(out target, out field))
+ return false;
+ } else {
+ // ldflda(StateMachine::<>t__builder, ldloc(this))
+ if (!inst.MatchLdFlda(out target, out field))
+ return false;
+ }
+ return target.MatchLdThis() && field.MemberDefinition == builderField;
+ }
+
+ bool MatchStateAssignment(ILInstruction inst, out int newState)
+ {
+ // stfld(StateMachine::<>1__state, ldloc(this), ldc.i4(stateId))
+ if (inst.MatchStFld(out var target, out var field, out var value)
+ && target.MatchLdThis()
+ && field.MemberDefinition == stateField
+ && value.MatchLdcI4(out newState))
+ {
+ return true;
+ }
+ newState = 0;
+ return false;
+ }
+ #endregion
+
+ #region InlineBodyOfMoveNext
+ void InlineBodyOfMoveNext(ILFunction function)
+ {
+ context.Step("Inline body of MoveNext()", function);
+ function.Body = mainTryCatch.TryBlock;
+ function.AsyncReturnType = underlyingReturnType;
+ moveNextFunction.Variables.Clear();
+ moveNextFunction.ReleaseRef();
+ foreach (var branch in function.Descendants.OfType()) {
+ if (branch.TargetBlock == setResultAndExitBlock) {
+ if (resultVar != null)
+ branch.ReplaceWith(new Return(new LdLoc(resultVar)) { ILRange = branch.ILRange });
+ else
+ branch.ReplaceWith(new Leave((BlockContainer)function.Body) { ILRange = branch.ILRange });
+ }
+ }
+ foreach (var leave in function.Descendants.OfType()) {
+ if (leave.TargetContainer == moveNextFunction.Body) {
+ leave.ReplaceWith(new InvalidBranch {
+ Message = " leave MoveNext - await not detected correctly ",
+ ILRange = leave.ILRange
+ });
+ }
+ }
+ function.Variables.AddRange(function.Descendants.OfType().Select(inst => inst.Variable).Distinct());
+ function.Variables.RemoveDead();
+ }
+ #endregion
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
index 1a72335f8..50b4afbbc 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
@@ -227,7 +227,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
///
/// Matches the body of a method as a single basic block.
///
- static Block SingleBlock(ILInstruction body)
+ internal static Block SingleBlock(ILInstruction body)
{
var block = body as Block;
if (body is BlockContainer blockContainer && blockContainer.Blocks.Count == 1) {
@@ -275,7 +275,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
///
void AnalyzeCtor()
{
- Block body = SingleBlock(CreateILAst(enumeratorCtor).Body);
+ Block body = SingleBlock(CreateILAst(enumeratorCtor, context).Body);
if (body == null)
throw new SymbolicAnalysisFailedException("Missing enumeratorCtor.Body");
foreach (var inst in body.Instructions) {
@@ -293,7 +293,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
///
/// Creates ILAst for the specified method, optimized up to before the 'YieldReturn' step.
///
- ILFunction CreateILAst(MethodDefinition method)
+ internal static ILFunction CreateILAst(MethodDefinition method, ILTransformContext context)
{
if (method == null || !method.HasBody)
throw new SymbolicAnalysisFailedException();
@@ -329,7 +329,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
MethodDefinition getCurrentMethod = enumeratorType.Methods.FirstOrDefault(
m => m.Name.StartsWith("System.Collections.Generic.IEnumerator", StringComparison.Ordinal)
&& m.Name.EndsWith(".get_Current", StringComparison.Ordinal));
- Block body = SingleBlock(CreateILAst(getCurrentMethod).Body);
+ Block body = SingleBlock(CreateILAst(getCurrentMethod, context).Body);
if (body == null)
throw new SymbolicAnalysisFailedException();
if (body.Instructions.Count == 1) {
@@ -365,7 +365,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
&& m.Name.EndsWith(".GetEnumerator", StringComparison.Ordinal));
if (getEnumeratorMethod == null)
return; // no mappings (maybe it's just an IEnumerator implementation?)
- var function = CreateILAst(getEnumeratorMethod);
+ var function = CreateILAst(getEnumeratorMethod, context);
foreach (var block in function.Descendants.OfType()) {
foreach (var inst in block.Instructions) {
// storeTarget.storeField = this.loadField;
@@ -388,7 +388,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
void ConstructExceptionTable()
{
disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose");
- var function = CreateILAst(disposeMethod);
+ var function = CreateILAst(disposeMethod, context);
var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorDispose, stateField);
rangeAnalysis.AssignStateRanges(function.Body, LongSet.Universe);
@@ -411,7 +411,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{
context.StepStartGroup("AnalyzeMoveNext");
MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext");
- ILFunction moveNextFunction = CreateILAst(moveNextMethod);
+ ILFunction moveNextFunction = CreateILAst(moveNextMethod, context);
// Copy-propagate temporaries holding a copy of 'this'.
// This is necessary because the old (pre-Roslyn) C# compiler likes to store 'this' in temporary variables.
@@ -689,7 +689,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
void DecompileFinallyBlocks()
{
foreach (var method in finallyMethodToStateRange.Keys) {
- var function = CreateILAst((MethodDefinition)context.TypeSystem.GetCecil(method));
+ var function = CreateILAst((MethodDefinition)context.TypeSystem.GetCecil(method), context);
var body = (BlockContainer)function.Body;
var newState = GetNewState(body.EntryPoint);
if (newState != null) {
diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
index c9d4d9a9f..22e3db204 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
@@ -43,6 +43,18 @@ namespace ICSharpCode.Decompiler.IL
///
public bool IsIterator;
+ ///
+ /// Gets whether this function is async.
+ /// This flag gets set by the AsyncAwaitDecompiler.
+ ///
+ public bool IsAsync { get => AsyncReturnType != null; }
+
+ ///
+ /// Return element type -- if the async method returns Task{T}, this field stores T.
+ /// If the async method returns Task or void, this field stores void.
+ ///
+ public IType AsyncReturnType;
+
public ILFunction(MethodDefinition method, ILInstruction body) : base(OpCode.ILFunction)
{
this.Body = body;
@@ -73,7 +85,10 @@ namespace ICSharpCode.Decompiler.IL
}
output.WriteLine(" {");
output.Indent();
-
+
+ if (IsAsync) {
+ output.WriteLine(".async");
+ }
if (IsIterator) {
output.WriteLine(".iterator");
}
diff --git a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
index 7e0e884d0..1ab9fd822 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
@@ -52,7 +52,36 @@ namespace ICSharpCode.Decompiler.IL
var inst = this as LdLoca;
return inst != null && inst.Variable == variable;
}
-
+
+ ///
+ /// Matches either ldloc (if the variable is a reference type), or ldloca (otherwise).
+ ///
+ public bool MatchLdLocRef(ILVariable variable)
+ {
+ if (variable.Type.IsReferenceType == true)
+ return MatchLdLoc(variable);
+ else
+ return MatchLdLoca(variable);
+ }
+
+ ///
+ /// Matches either ldloc (if the variable is a reference type), or ldloca (otherwise).
+ ///
+ public bool MatchLdLocRef(out ILVariable variable)
+ {
+ switch (this) {
+ case LdLoc ldloc:
+ variable = ldloc.Variable;
+ return variable.Type.IsReferenceType == true;
+ case LdLoca ldloca:
+ variable = ldloca.Variable;
+ return variable.Type.IsReferenceType != true;
+ default:
+ variable = null;
+ return false;
+ }
+ }
+
public bool MatchLdThis()
{
var inst = this as LdLoc;
diff --git a/ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs
index 572d5d702..62ac417d2 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs
@@ -47,7 +47,7 @@ namespace ICSharpCode.Decompiler.IL
partial class InvalidBranch : SimpleInstruction
{
public string Message;
- public StackType ExpectedResultType = StackType.Unknown;
+ public StackType ExpectedResultType = StackType.Void;
public InvalidBranch(string message) : this()
{
@@ -62,9 +62,9 @@ namespace ICSharpCode.Decompiler.IL
{
output.Write(OpCode);
if (!string.IsNullOrEmpty(Message)) {
- output.Write('(');
+ output.Write("(\"");
output.Write(Message);
- output.Write(')');
+ output.Write("\")");
}
}
}
@@ -88,9 +88,9 @@ namespace ICSharpCode.Decompiler.IL
{
output.Write(OpCode);
if (!string.IsNullOrEmpty(Message)) {
- output.Write('(');
+ output.Write("(\"");
output.Write(Message);
- output.Write(')');
+ output.Write("\")");
}
}
}