From b36ae9df7e611d240e6c4a7ee78c14179489f521 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 1 Sep 2017 23:18:53 +0200 Subject: [PATCH] Start on new async/await decompiler. --- .../CSharp/CSharpDecompiler.cs | 5 + .../CSharp/StatementBuilder.cs | 5 +- .../CSharp/Transforms/DeclareVariables.cs | 1 + ICSharpCode.Decompiler/DecompilerSettings.cs | 2 +- .../ICSharpCode.Decompiler.csproj | 1 + .../IL/ControlFlow/AsyncAwaitDecompiler.cs | 387 ++++++++++++++++++ .../IL/ControlFlow/YieldReturnDecompiler.cs | 16 +- .../IL/Instructions/ILFunction.cs | 17 +- .../IL/Instructions/PatternMatching.cs | 31 +- .../IL/Instructions/SimpleInstruction.cs | 10 +- 10 files changed, 457 insertions(+), 18 deletions(-) create mode 100644 ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs 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("\")"); } } }