diff --git a/src/Libraries/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/src/Libraries/ICSharpCode.Decompiler/Ast/AstBuilder.cs index fc288d80fd..a95ddbaf2d 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Ast/AstBuilder.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -81,6 +81,8 @@ namespace ICSharpCode.Decompiler.Ast return true; if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(type)) return true; + if (settings.AsyncAwait && AsyncDecompiler.IsCompilerGeneratedStateMachine(type)) + return true; } else if (type.IsCompilerGenerated()) { if (type.Name.StartsWith("", StringComparison.Ordinal)) return true; @@ -770,6 +772,10 @@ namespace ICSharpCode.Decompiler.Ast SetNewModifier(astMethod); } astMethod.Body = CreateMethodBody(methodDef, astMethod.Parameters); + if (context.CurrentMethodIsAsync) { + astMethod.Modifiers |= Modifiers.Async; + context.CurrentMethodIsAsync = false; + } } ConvertAttributes(astMethod, methodDef); if (methodDef.HasCustomAttributes && astMethod.Parameters.Count > 0) { @@ -1342,6 +1348,7 @@ namespace ICSharpCode.Decompiler.Ast static void ConvertCustomAttributes(AstNode attributedNode, ICustomAttributeProvider customAttributeProvider, string attributeTarget = null) { + EntityDeclaration entityDecl = attributedNode as EntityDeclaration; if (customAttributeProvider.HasCustomAttributes) { var attributes = new List(); foreach (var customAttribute in customAttributeProvider.CustomAttributes.OrderBy(a => a.AttributeType.FullName)) { @@ -1353,6 +1360,15 @@ namespace ICSharpCode.Decompiler.Ast // don't show the ParamArrayAttribute (it's converted to the 'params' modifier) continue; } + // if the method is async, remove [DebuggerStepThrough] and [Async + if (entityDecl != null && entityDecl.HasModifier(Modifiers.Async)) { + if (customAttribute.AttributeType.Name == "DebuggerStepThroughAttribute" && customAttribute.AttributeType.Namespace == "System.Diagnostics") { + continue; + } + if (customAttribute.AttributeType.Name == "AsyncStateMachineAttribute" && customAttribute.AttributeType.Namespace == "System.Runtime.CompilerServices") { + continue; + } + } var attribute = new ICSharpCode.NRefactory.CSharp.Attribute(); attribute.AddAnnotation(customAttribute); diff --git a/src/Libraries/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/src/Libraries/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index 5de5b0ba5a..8ad9047c1c 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -58,6 +58,7 @@ namespace ICSharpCode.Decompiler.Ast MethodDefinition oldCurrentMethod = context.CurrentMethod; Debug.Assert(oldCurrentMethod == null || oldCurrentMethod == methodDef); context.CurrentMethod = methodDef; + context.CurrentMethodIsAsync = false; try { AstMethodBodyBuilder builder = new AstMethodBodyBuilder(); builder.methodDef = methodDef; @@ -847,8 +848,11 @@ namespace ICSharpCode.Decompiler.Ast case ILCode.ExpressionTreeParameterDeclarations: args[args.Count - 1].AddAnnotation(new ParameterDeclarationAnnotation(byteCode)); return args[args.Count - 1]; + case ILCode.Await: + return new UnaryOperatorExpression(UnaryOperatorType.Await, arg1); case ILCode.NullableOf: - case ILCode.ValueOf: return arg1; + case ILCode.ValueOf: + return arg1; default: throw new Exception("Unknown OpCode: " + byteCode.Code); } diff --git a/src/Libraries/ICSharpCode.Decompiler/Ast/DecompilerContext.cs b/src/Libraries/ICSharpCode.Decompiler/Ast/DecompilerContext.cs index 4a3f277827..624b1b33fd 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Ast/DecompilerContext.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Ast/DecompilerContext.cs @@ -33,6 +33,7 @@ namespace ICSharpCode.Decompiler public TypeDefinition CurrentType; public MethodDefinition CurrentMethod; public DecompilerSettings Settings = new DecompilerSettings(); + public bool CurrentMethodIsAsync; // public ITypeResolveContext TypeResolveContext; // public IProjectContent ProjectContent; diff --git a/src/Libraries/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs b/src/Libraries/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs index 85fef513ee..b17e24656f 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs @@ -114,6 +114,9 @@ namespace ICSharpCode.Decompiler.Ast object GetCurrentLocalDefinition() { AstNode node = nodeStack.Peek(); + if (node is Identifier && node.Parent != null) + node = node.Parent; + var parameterDef = node.Annotation(); if (parameterDef != null) return parameterDef; @@ -126,14 +129,11 @@ namespace ICSharpCode.Decompiler.Ast //if (variable.OriginalVariable != null) // return variable.OriginalVariable; return variable; - } else { - } } var label = node as LabelStatement; - if (label != null) - { + if (label != null) { var method = nodeStack.Select(nd => nd.Annotation()).FirstOrDefault(mr => mr != null); if (method != null) return method.ToString() + label.Label; diff --git a/src/Libraries/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/src/Libraries/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs index 9264000f08..9c5aa97f6e 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -151,6 +151,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms DecompilerContext subContext = context.Clone(); subContext.CurrentMethod = method; + subContext.CurrentMethodIsAsync = false; subContext.ReservedVariableNames.AddRange(currentlyUsedVariableNames); BlockStatement body = AstMethodBodyBuilder.CreateMethodBody(method, subContext, ame.Parameters); TransformationPipeline.RunTransformationsUntil(body, v => v is DelegateConstruction, subContext); diff --git a/src/Libraries/ICSharpCode.Decompiler/DecompilerSettings.cs b/src/Libraries/ICSharpCode.Decompiler/DecompilerSettings.cs index 435124491c..f78194a6f9 100644 --- a/src/Libraries/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/src/Libraries/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -72,6 +72,21 @@ namespace ICSharpCode.Decompiler } } + bool asyncAwait = true; + + /// + /// Decompile async methods. + /// + public bool AsyncAwait { + get { return asyncAwait; } + set { + if (asyncAwait != value) { + asyncAwait = value; + OnPropertyChanged("AsyncAwait"); + } + } + } + bool automaticProperties = true; /// diff --git a/src/Libraries/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/src/Libraries/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 846edc60a2..60bead91a6 100644 --- a/src/Libraries/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/src/Libraries/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -101,6 +101,7 @@ + @@ -114,6 +115,8 @@ + + diff --git a/src/Libraries/ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs b/src/Libraries/ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs new file mode 100644 index 0000000000..4535c0f82a --- /dev/null +++ b/src/Libraries/ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs @@ -0,0 +1,653 @@ +// Copyright (c) 2012 AlphaSierraPapa for the SharpDevelop Team +// +// 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 +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace ICSharpCode.Decompiler.ILAst +{ + /// + /// Decompiler step for C# 5 async/await. + /// + class AsyncDecompiler + { + 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 + } + + DecompilerContext context; + + // These fields are set by MatchTaskCreationPattern() + AsyncMethodType methodType; + int initialState; + TypeDefinition stateMachineStruct; + MethodDefinition moveNextMethod; + FieldDefinition builderField; + FieldDefinition stateField; + Dictionary fieldToParameterMap = new Dictionary(); + + // These fields are set by AnalyzeMoveNext() + int finalState = -2; + ILTryCatchBlock mainTryCatch; + ILLabel setResultAndExitLabel; + ILLabel exitLabel; + ILExpression resultExpr; + + #region RunStep1() method + public static void RunStep1(DecompilerContext context, ILBlock method) + { + if (!context.Settings.AsyncAwait) + return; // abort if async decompilation is disabled + var yrd = new AsyncDecompiler(); + yrd.context = context; + if (!yrd.MatchTaskCreationPattern(method)) + return; + #if DEBUG + if (Debugger.IsAttached) { + yrd.Run(); + } else { + #endif + try { + yrd.Run(); + } catch (SymbolicAnalysisFailedException) { + return; + } + #if DEBUG + } + #endif + context.CurrentMethodIsAsync = true; + + method.Body.Clear(); + method.EntryGoto = null; + method.Body.AddRange(yrd.newTopLevelBody); + ILAstOptimizer.RemoveRedundantCode(method); + } + + void Run() + { + AnalyzeMoveNext(); + ValidateCatchBlock(mainTryCatch.CatchBlocks[0]); + AnalyzeStateMachine(mainTryCatch.TryBlock); + // AnalyzeStateMachine invokes ConvertBody + MarkGeneratedVariables(); + YieldReturnDecompiler.TranslateFieldsToLocalAccess(newTopLevelBody, fieldToParameterMap); + } + #endregion + + #region MatchTaskCreationPattern + bool MatchTaskCreationPattern(ILBlock method) + { + if (method.Body.Count < 5) + return false; + // Check the second-to-last instruction (the start call) first, as we can get the most information from that + MethodReference startMethod; + ILExpression loadStartTarget, loadStartArgument; + // call(AsyncTaskMethodBuilder::Start, ldloca(builder), ldloca(stateMachine)) + if (!method.Body[method.Body.Count - 2].Match(ILCode.Call, out startMethod, out loadStartTarget, out loadStartArgument)) + return false; + if (startMethod.Name != "Start" || startMethod.DeclaringType == null || startMethod.DeclaringType.Namespace != "System.Runtime.CompilerServices") + return false; + switch (startMethod.DeclaringType.Name) { + case "AsyncTaskMethodBuilder`1": + methodType = AsyncMethodType.TaskOfT; + break; + case "AsyncTaskMethodBuilder": + methodType = AsyncMethodType.Task; + break; + case "AsyncVoidMethodBuilder": + methodType = AsyncMethodType.Void; + break; + default: + return false; + } + ILVariable stateMachineVar, builderVar; + if (!loadStartTarget.Match(ILCode.Ldloca, out builderVar)) + return false; + if (!loadStartArgument.Match(ILCode.Ldloca, out stateMachineVar)) + return false; + + stateMachineStruct = stateMachineVar.Type.ResolveWithinSameModule(); + if (stateMachineStruct == null || !stateMachineStruct.IsValueType) + return false; + moveNextMethod = stateMachineStruct.Methods.FirstOrDefault(f => f.Name == "MoveNext"); + if (moveNextMethod == null) + return false; + + // Check third-to-last instruction (copy of builder): + // stloc(builder, ldfld(StateMachine::<>t__builder, ldloca(stateMachine))) + ILExpression loadBuilderExpr; + if (!method.Body[method.Body.Count - 3].MatchStloc(builderVar, out loadBuilderExpr)) + return false; + FieldReference builderFieldRef; + ILExpression loadStateMachineForBuilderExpr; + if (!loadBuilderExpr.Match(ILCode.Ldfld, out builderFieldRef, out loadStateMachineForBuilderExpr)) + return false; + if (!loadStateMachineForBuilderExpr.MatchLdloca(stateMachineVar)) + return false; + builderField = builderFieldRef.ResolveWithinSameModule(); + if (builderField == null) + return false; + + // Check the last instruction (ret) + if (methodType == AsyncMethodType.Void) { + if (!method.Body[method.Body.Count - 1].Match(ILCode.Ret)) + return false; + } else { + // ret(call(AsyncTaskMethodBuilder::get_Task, ldflda(StateMachine::<>t__builder, ldloca(stateMachine)))) + ILExpression returnValue; + if (!method.Body[method.Body.Count - 1].Match(ILCode.Ret, out returnValue)) + return false; + MethodReference getTaskMethod; + ILExpression builderExpr; + if (!returnValue.Match(ILCode.Call, out getTaskMethod, out builderExpr)) + return false; + ILExpression loadStateMachineForBuilderExpr2; + FieldReference builderField2; + if (!builderExpr.Match(ILCode.Ldflda, out builderField2, out loadStateMachineForBuilderExpr2)) + return false; + if (builderField2.ResolveWithinSameModule() != builderField || !loadStateMachineForBuilderExpr2.MatchLdloca(stateMachineVar)) + return false; + } + + // Check the last field assignment - this should be the state field + ILExpression initialStateExpr; + if (!MatchStFld(method.Body[method.Body.Count - 4], stateMachineVar, out stateField, out initialStateExpr)) + return false; + if (!initialStateExpr.Match(ILCode.Ldc_I4, out initialState)) + return false; + if (initialState != -1) + return false; + + // Check the second-to-last field assignment - this should be the builder field + FieldDefinition builderField3; + ILExpression builderInitialization; + if (!MatchStFld(method.Body[method.Body.Count - 5], stateMachineVar, out builderField3, out builderInitialization)) + return false; + MethodReference createMethodRef; + if (builderField3 != builderField || !builderInitialization.Match(ILCode.Call, out createMethodRef)) + return false; + if (createMethodRef.Name != "Create") + return false; + + for (int i = 0; i < method.Body.Count - 5; i++) { + FieldDefinition field; + ILExpression fieldInit; + if (!MatchStFld(method.Body[i], stateMachineVar, out field, out fieldInit)) + return false; + ILVariable v; + if (!fieldInit.Match(ILCode.Ldloc, out v)) + return false; + if (!v.IsParameter) + return false; + fieldToParameterMap[field] = v; + } + + return true; + } + + static bool MatchStFld(ILNode stfld, ILVariable stateMachineVar, out FieldDefinition field, out ILExpression expr) + { + field = null; + FieldReference fieldRef; + ILExpression ldloca; + if (!stfld.Match(ILCode.Stfld, out fieldRef, out ldloca, out expr)) + return false; + field = fieldRef.ResolveWithinSameModule(); + return field != null && ldloca.MatchLdloca(stateMachineVar); + } + #endregion + + #region Analyze MoveNext + void AnalyzeMoveNext() + { + ILBlock ilMethod = CreateILAst(moveNextMethod); + + if (ilMethod.Body.Count != 6) + throw new SymbolicAnalysisFailedException(); + + mainTryCatch = ilMethod.Body[0] as ILTryCatchBlock; + if (mainTryCatch == null || mainTryCatch.CatchBlocks.Count != 1) + throw new SymbolicAnalysisFailedException(); + if (mainTryCatch.FaultBlock != null || mainTryCatch.FinallyBlock != null) + throw new SymbolicAnalysisFailedException(); + + setResultAndExitLabel = ilMethod.Body[1] as ILLabel; + if (setResultAndExitLabel == null) + throw new SymbolicAnalysisFailedException(); + + if (!MatchStateAssignment(ilMethod.Body[2], out finalState)) + throw new SymbolicAnalysisFailedException(); + + // call(AsyncTaskMethodBuilder`1::SetResult, ldflda(StateMachine::<>t__builder, ldloc(this)), ldloc(<>t__result)) + MethodReference setResultMethod; + ILExpression builderExpr; + if (methodType == AsyncMethodType.TaskOfT) { + if (!ilMethod.Body[3].Match(ILCode.Call, out setResultMethod, out builderExpr, out resultExpr)) + throw new SymbolicAnalysisFailedException(); + } else { + if (!ilMethod.Body[3].Match(ILCode.Call, out setResultMethod, out builderExpr)) + throw new SymbolicAnalysisFailedException(); + } + if (!(setResultMethod.Name == "SetResult" && IsBuilderFieldOnThis(builderExpr))) + throw new SymbolicAnalysisFailedException(); + + exitLabel = ilMethod.Body[4] as ILLabel; + if (exitLabel == null) + throw new SymbolicAnalysisFailedException(); + } + + /// + /// Creates ILAst for the specified method, optimized up to before the 'YieldReturn' step. + /// + ILBlock CreateILAst(MethodDefinition method) + { + if (method == null || !method.HasBody) + throw new SymbolicAnalysisFailedException(); + + ILBlock ilMethod = new ILBlock(); + ILAstBuilder astBuilder = new ILAstBuilder(); + ilMethod.Body = astBuilder.Build(method, true, context); + ILAstOptimizer optimizer = new ILAstOptimizer(); + optimizer.Optimize(context, ilMethod, ILAstOptimizationStep.YieldReturn); + return ilMethod; + } + + void ValidateCatchBlock(ILTryCatchBlock.CatchBlock catchBlock) + { + if (catchBlock.ExceptionType == null || catchBlock.ExceptionType.Name != "Exception") + throw new SymbolicAnalysisFailedException(); + if (catchBlock.Body.Count != 3) + throw new SymbolicAnalysisFailedException(); + int stateID; + if (!(MatchStateAssignment(catchBlock.Body[0], out stateID) && stateID == finalState)) + throw new SymbolicAnalysisFailedException(); + MethodReference setExceptionMethod; + ILExpression builderExpr, exceptionExpr; + if (!catchBlock.Body[1].Match(ILCode.Call, out setExceptionMethod, out builderExpr, out exceptionExpr)) + throw new SymbolicAnalysisFailedException(); + if (!(setExceptionMethod.Name == "SetException" && IsBuilderFieldOnThis(builderExpr) && exceptionExpr.MatchLdloc(catchBlock.ExceptionVariable))) + throw new SymbolicAnalysisFailedException(); + + ILLabel label; + if (!(catchBlock.Body[2].Match(ILCode.Leave, out label) && label == exitLabel)) + throw new SymbolicAnalysisFailedException(); + } + + bool IsBuilderFieldOnThis(ILExpression builderExpr) + { + // ldflda(StateMachine::<>t__builder, ldloc(this)) + FieldReference fieldRef; + ILExpression target; + return builderExpr.Match(ILCode.Ldflda, out fieldRef, out target) + && fieldRef.ResolveWithinSameModule() == builderField + && target.MatchThis(); + } + + bool MatchStateAssignment(ILNode stfld, out int stateID) + { + // stfld(StateMachine::<>1__state, ldloc(this), ldc.i4(-2)) + stateID = 0; + FieldReference fieldRef; + ILExpression target, val; + if (stfld.Match(ILCode.Stfld, out fieldRef, out target, out val)) { + return fieldRef.ResolveWithinSameModule() == stateField + && target.MatchThis() + && val.Match(ILCode.Ldc_I4, out stateID); + } + return false; + } + #endregion + + #region AnalyzeStateMachine + ILVariable doFinallyBodies; + List newTopLevelBody; + + void AnalyzeStateMachine(ILBlock block) + { + var body = block.Body; + if (body.Count == 0) + throw new SymbolicAnalysisFailedException(); + if (DetectDoFinallyBodies(body)) { + body.RemoveAt(0); + if (body.Count == 0) + throw new SymbolicAnalysisFailedException(); + } + StateRangeAnalysis rangeAnalysis = new StateRangeAnalysis(body[0], StateRangeAnalysisMode.AsyncMoveNext, stateField); + int bodyLength = block.Body.Count; + int pos = rangeAnalysis.AssignStateRanges(body, bodyLength); + rangeAnalysis.EnsureLabelAtPos(body, ref pos, ref bodyLength); + + var labelStateRangeMapping = rangeAnalysis.CreateLabelRangeMapping(body, pos, bodyLength); + newTopLevelBody = ConvertBody(body, pos, bodyLength, labelStateRangeMapping); + newTopLevelBody.Insert(0, MakeGoTo(labelStateRangeMapping, initialState)); + newTopLevelBody.Add(setResultAndExitLabel); + if (methodType == AsyncMethodType.TaskOfT) { + newTopLevelBody.Add(new ILExpression(ILCode.Ret, null, resultExpr)); + } else { + newTopLevelBody.Add(new ILExpression(ILCode.Ret, null)); + } + } + + bool DetectDoFinallyBodies(List body) + { + ILVariable v; + ILExpression initExpr; + if (!body[0].Match(ILCode.Stloc, out v, out initExpr)) + return false; + int initialValue; + if (!(initExpr.Match(ILCode.Ldc_I4, out initialValue) && initialValue == 1)) + return false; + doFinallyBodies = v; + return true; + } + #endregion + + #region ConvertBody + ILExpression MakeGoTo(LabelRangeMapping mapping, int state) + { + foreach (var pair in mapping) { + if (pair.Value.Contains(state)) + return new ILExpression(ILCode.Br, pair.Key); + } + throw new SymbolicAnalysisFailedException(); + } + + List ConvertBody(List body, int startPos, int bodyLength, LabelRangeMapping mapping) + { + List newBody = new List(); + // Copy all instructions from the old body to newBody. + for (int pos = startPos; pos < bodyLength; pos++) { + ILTryCatchBlock tryCatchBlock = body[pos] as ILTryCatchBlock; + ILExpression expr = body[pos] as ILExpression; + if (expr != null && expr.Code == ILCode.Leave && expr.Operand == exitLabel) { + ILVariable awaiterVar; + FieldDefinition awaiterField; + int targetStateID; + HandleAwait(newBody, out awaiterVar, out awaiterField, out targetStateID); + MarkAsGeneratedVariable(awaiterVar); + newBody.Add(new ILExpression(ILCode.Await, null, new ILExpression(ILCode.Ldloca, awaiterVar))); + newBody.Add(MakeGoTo(mapping, targetStateID)); + } else if (tryCatchBlock != null) { + ILTryCatchBlock newTryCatchBlock = new ILTryCatchBlock(); + var tryBody = tryCatchBlock.TryBlock.Body; + if (tryBody.Count == 0) + throw new SymbolicAnalysisFailedException(); + StateRangeAnalysis rangeAnalysis = new StateRangeAnalysis(tryBody[0], StateRangeAnalysisMode.AsyncMoveNext, stateField); + int tryBodyLength = tryBody.Count; + int posInTryBody = rangeAnalysis.AssignStateRanges(tryBody, tryBodyLength); + rangeAnalysis.EnsureLabelAtPos(tryBody, ref posInTryBody, ref tryBodyLength); + + var mappingInTryBlock = rangeAnalysis.CreateLabelRangeMapping(tryBody, posInTryBody, tryBodyLength); + var newTryBody = ConvertBody(tryBody, posInTryBody, tryBodyLength, mappingInTryBlock); + newTryBody.Insert(0, MakeGoTo(mappingInTryBlock, initialState)); + + // If there's a label at the beginning of the state dispatcher, copy that + if (posInTryBody > 0 && tryBody.FirstOrDefault() is ILLabel) + newTryBody.Insert(0, tryBody.First()); + + newTryCatchBlock.TryBlock = new ILBlock(newTryBody); + newTryCatchBlock.CatchBlocks = new List(tryCatchBlock.CatchBlocks); + newTryCatchBlock.FaultBlock = tryCatchBlock.FaultBlock; + if (tryCatchBlock.FinallyBlock != null) + newTryCatchBlock.FinallyBlock = new ILBlock(ConvertFinally(tryCatchBlock.FinallyBlock.Body)); + + newBody.Add(newTryCatchBlock); + } else { + newBody.Add(body[pos]); + } + } + return newBody; + } + + List ConvertFinally(List body) + { + List newBody = new List(body); + ILLabel endFinallyLabel; + ILExpression ceqExpr; + if (newBody.Count > 0 && newBody[0].Match(ILCode.Brtrue, out endFinallyLabel, out ceqExpr)) { + ILExpression loadDoFinallyBodies, loadZero; + object unused; + if (ceqExpr.Match(ILCode.Ceq, out unused, out loadDoFinallyBodies, out loadZero)) { + int num; + if (loadDoFinallyBodies.MatchLdloc(doFinallyBodies) && loadZero.Match(ILCode.Ldc_I4, out num) && num == 0) { + newBody.RemoveAt(0); + } + } else if (ceqExpr.Match(ILCode.LogicNot, out loadDoFinallyBodies)) { + if (loadDoFinallyBodies.MatchLdloc(doFinallyBodies)) { + newBody.RemoveAt(0); + } + } + } + return newBody; + } + + void HandleAwait(List newBody, out ILVariable awaiterVar, out FieldDefinition awaiterField, out int targetStateID) + { + // Handle the instructions prior to the exit out of the method to detect what is being awaited. + // (analyses the last instructions in newBody and removes the analyzed instructions from newBody) + + if (doFinallyBodies != null) { + // stloc(<>t__doFinallyBodies, ldc.i4(0)) + ILExpression dfbInitExpr; + if (!newBody.LastOrDefault().MatchStloc(doFinallyBodies, out dfbInitExpr)) + throw new SymbolicAnalysisFailedException(); + int val; + if (!(dfbInitExpr.Match(ILCode.Ldc_I4, out val) && val == 0)) + throw new SymbolicAnalysisFailedException(); + newBody.RemoveAt(newBody.Count - 1); // remove doFinallyBodies assignment + } + + // call(AsyncTaskMethodBuilder::AwaitUnsafeOnCompleted, ldflda(StateMachine::<>t__builder, ldloc(this)), ldloca(CS$0$0001), ldloc(this)) + ILExpression callAwaitUnsafeOnCompleted = newBody.LastOrDefault() as ILExpression; + newBody.RemoveAt(newBody.Count - 1); // remove AwaitUnsafeOnCompleted call + if (callAwaitUnsafeOnCompleted == null || callAwaitUnsafeOnCompleted.Code != ILCode.Call) + throw new SymbolicAnalysisFailedException(); + if (((MethodReference)callAwaitUnsafeOnCompleted.Operand).Name != "AwaitUnsafeOnCompleted") + throw new SymbolicAnalysisFailedException(); + if (callAwaitUnsafeOnCompleted.Arguments.Count != 3) + throw new SymbolicAnalysisFailedException(); + if (!callAwaitUnsafeOnCompleted.Arguments[1].Match(ILCode.Ldloca, out awaiterVar)) + throw new SymbolicAnalysisFailedException(); + + // stfld(StateMachine::<>u__$awaiter6, ldloc(this), ldloc(CS$0$0001)) + FieldReference awaiterFieldRef; + ILExpression loadThis, loadAwaiterVar; + if (!newBody.LastOrDefault().Match(ILCode.Stfld, out awaiterFieldRef, out loadThis, out loadAwaiterVar)) + throw new SymbolicAnalysisFailedException(); + newBody.RemoveAt(newBody.Count - 1); // remove awaiter field assignment + awaiterField = awaiterFieldRef.ResolveWithinSameModule(); + if (!(awaiterField != null && loadThis.MatchThis() && loadAwaiterVar.MatchLdloc(awaiterVar))) + throw new SymbolicAnalysisFailedException(); + + // stfld(StateMachine::<>1__state, ldloc(this), ldc.i4(0)) + if (!MatchStateAssignment(newBody.LastOrDefault(), out targetStateID)) + throw new SymbolicAnalysisFailedException(); + newBody.RemoveAt(newBody.Count - 1); // remove awaiter field assignment + } + #endregion + + #region MarkGeneratedVariables + int smallestGeneratedVariableIndex = int.MaxValue; + + void MarkAsGeneratedVariable(ILVariable v) + { + if (v.OriginalVariable != null && v.OriginalVariable.Index >= 0) { + smallestGeneratedVariableIndex = Math.Min(smallestGeneratedVariableIndex, v.OriginalVariable.Index); + } + } + + void MarkGeneratedVariables() + { + var expressions = new ILBlock(newTopLevelBody).GetSelfAndChildrenRecursive(); + foreach (var v in expressions.Select(e => e.Operand).OfType()) { + if (v.OriginalVariable != null && v.OriginalVariable.Index >= smallestGeneratedVariableIndex) + v.IsGenerated = true; + } + } + #endregion + + #region RunStep2() method + public static void RunStep2(DecompilerContext context, ILBlock method) + { + if (context.CurrentMethodIsAsync) { + Step2(method.Body); + ILAstOptimizer.RemoveRedundantCode(method); + // Repeat the inlining/copy propagation optimization because the conversion of field access + // to local variables can open up additional inlining possibilities. + ILInlining inlining = new ILInlining(method); + inlining.InlineAllVariables(); + inlining.CopyPropagation(); + } + } + + static void Step2(List body) + { + for (int pos = 0; pos < body.Count; pos++) { + ILTryCatchBlock tc = body[pos] as ILTryCatchBlock; + if (tc != null) { + Step2(tc.TryBlock.Body); + } else { + Step2(body, ref pos); + } + } + } + + static bool Step2(List body, ref int pos) + { + // stloc(CS$0$0001, callvirt(class System.Threading.Tasks.Task`1::GetAwaiter, awaiterExpr) + // brtrue(IL_7C, call(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1::get_IsCompleted, ldloca(CS$0$0001))) + // await(ldloca(CS$0$0001)) + // ... + // IL_7C: + // arg_8B_0 = call(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1::GetResult, ldloca(CS$0$0001)) + // initobj(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1, ldloca(CS$0$0001)) + + ILExpression loadAwaiter; + ILVariable awaiterVar; + if (!body[pos].Match(ILCode.Await, out loadAwaiter)) + return false; + if (!loadAwaiter.Match(ILCode.Ldloca, out awaiterVar)) + return false; + + ILVariable stackVar; + ILExpression stackExpr; + while (pos >= 1 && body[pos - 1].Match(ILCode.Stloc, out stackVar, out stackExpr)) + pos--; + + // stloc(CS$0$0001, callvirt(class System.Threading.Tasks.Task`1::GetAwaiter, awaiterExpr) + ILExpression getAwaiterCall; + if (!(pos >= 2 && body[pos - 2].MatchStloc(awaiterVar, out getAwaiterCall))) + return false; + MethodReference getAwaiterMethod; + ILExpression awaitedExpr; + if (!(getAwaiterCall.Match(ILCode.Call, out getAwaiterMethod, out awaitedExpr) || getAwaiterCall.Match(ILCode.Callvirt, out getAwaiterMethod, out awaitedExpr))) + return false; + + if (awaitedExpr.Code == ILCode.AddressOf) { + // remove 'AddressOf()' when calling GetAwaiter() on a value type + awaitedExpr = awaitedExpr.Arguments[0]; + } + + // brtrue(IL_7C, call(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1::get_IsCompleted, ldloca(CS$0$0001))) + ILLabel label; + ILExpression getIsCompletedCall; + if (!(pos >= 1 && body[pos - 1].Match(ILCode.Brtrue, out label, out getIsCompletedCall))) + return false; + + int labelPos = body.IndexOf(label); + if (labelPos < pos) + return false; + for (int i = pos + 1; i < labelPos; i++) { + // validate that we aren't deleting any unexpected instructions - + // between the await and the label, there should only be the stack, awaiter and state logic + ILExpression expr = body[i] as ILExpression; + if (expr == null) + return false; + switch (expr.Code) { + case ILCode.Stloc: + case ILCode.Initobj: + case ILCode.Stfld: + case ILCode.Await: + // e.g. + // stloc(CS$0$0001, ldfld(StateMachine::<>u__$awaitere, ldloc(this))) + // initobj(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1, ldloca(CS$0$0002_66)) + // stfld('d__d'::<>u__$awaitere, ldloc(this), ldloc(CS$0$0002_66)) + // stfld('d__d'::<>1__state, ldloc(this), ldc.i4(-1)) + break; + default: + return false; + } + } + if (labelPos + 1 >= body.Count) + return false; + ILExpression resultAssignment = body[labelPos + 1] as ILExpression; + ILVariable resultVar; + ILExpression getResultCall; + bool isResultAssignment = resultAssignment.Match(ILCode.Stloc, out resultVar, out getResultCall); + if (!isResultAssignment) + getResultCall = resultAssignment; + if (!(getResultCall.Operand is MethodReference && ((MethodReference)getResultCall.Operand).Name == "GetResult")) + return false; + + pos -= 2; // also delete 'stloc', 'brtrue' and 'await' + body.RemoveRange(pos, labelPos - pos); + Debug.Assert(body[pos] == label); + + pos++; + if (isResultAssignment) { + Debug.Assert(body[pos] == resultAssignment); + resultAssignment.Arguments[0] = new ILExpression(ILCode.Await, null, awaitedExpr); + } else { + body[pos] = new ILExpression(ILCode.Await, null, awaitedExpr); + } + + // if the awaiter variable is cleared out in the next instruction, remove that instruction + if (IsVariableReset(body.ElementAtOrDefault(pos + 1), awaiterVar)) { + body.RemoveAt(pos + 1); + } + + return true; + } + + static bool IsVariableReset(ILNode expr, ILVariable variable) + { + object unused; + ILExpression ldloca; + return expr.Match(ILCode.Initobj, out unused, out ldloca) && ldloca.MatchLdloca(variable); + } + #endregion + } +} diff --git a/src/Libraries/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/src/Libraries/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index 051662f357..bf85ca94f4 100644 --- a/src/Libraries/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/src/Libraries/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -35,6 +35,7 @@ namespace ICSharpCode.Decompiler.ILAst InlineVariables, CopyPropagation, YieldReturn, + AsyncAwait, PropertyAccessInstructions, SplitToMovableBlocks, TypeInference, @@ -107,6 +108,10 @@ namespace ICSharpCode.Decompiler.ILAst if (abortBeforeStep == ILAstOptimizationStep.YieldReturn) return; YieldReturnDecompiler.Run(context, method); + AsyncDecompiler.RunStep1(context, method); + + if (abortBeforeStep == ILAstOptimizationStep.AsyncAwait) return; + AsyncDecompiler.RunStep2(context, method); if (abortBeforeStep == ILAstOptimizationStep.PropertyAccessInstructions) return; IntroducePropertyAccessInstructions(method); @@ -256,7 +261,7 @@ namespace ICSharpCode.Decompiler.ILAst /// Ignore arguments of 'leave' /// /// - void RemoveRedundantCode(ILBlock method) + internal static void RemoveRedundantCode(ILBlock method) { Dictionary labelRefCount = new Dictionary(); foreach (ILLabel target in method.GetSelfAndChildrenRecursive(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) { @@ -286,7 +291,13 @@ namespace ICSharpCode.Decompiler.ILAst prevExpr.ILRanges.AddRange(((ILExpression)body[i]).ILRanges); // Ignore pop } else { - newBody.Add(body[i]); + ILLabel label = body[i] as ILLabel; + if (label != null) { + if (labelRefCount.GetOrDefault(label) > 0) + newBody.Add(label); + } else { + newBody.Add(body[i]); + } } } block.Body = newBody; diff --git a/src/Libraries/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/src/Libraries/ICSharpCode.Decompiler/ILAst/ILCodes.cs index cbf5486988..5ca063b752 100644 --- a/src/Libraries/ICSharpCode.Decompiler/ILAst/ILCodes.cs +++ b/src/Libraries/ICSharpCode.Decompiler/ILAst/ILCodes.cs @@ -333,7 +333,11 @@ namespace ICSharpCode.Decompiler.ILAst /// The last child of this node is the call constructing the expression tree, all other children are the /// assignments to the ParameterExpression variables. /// - ExpressionTreeParameterDeclarations + ExpressionTreeParameterDeclarations, + /// + /// C# 5 await + /// + Await } public static class ILCodeUtil diff --git a/src/Libraries/ICSharpCode.Decompiler/ILAst/PatternMatching.cs b/src/Libraries/ICSharpCode.Decompiler/ILAst/PatternMatching.cs index cc4e5ee5f5..31fcf62b15 100644 --- a/src/Libraries/ICSharpCode.Decompiler/ILAst/PatternMatching.cs +++ b/src/Libraries/ICSharpCode.Decompiler/ILAst/PatternMatching.cs @@ -155,5 +155,17 @@ namespace ICSharpCode.Decompiler.ILAst ILVariable v; return node.Match(ILCode.Ldloc, out v) && v == expectedVar; } + + public static bool MatchLdloca(this ILNode node, ILVariable expectedVar) + { + ILVariable v; + return node.Match(ILCode.Ldloca, out v) && v == expectedVar; + } + + public static bool MatchStloc(this ILNode node, ILVariable expectedVar, out ILExpression expr) + { + ILVariable v; + return node.Match(ILCode.Stloc, out v, out expr) && v == expectedVar; + } } } diff --git a/src/Libraries/ICSharpCode.Decompiler/ILAst/StateRange.cs b/src/Libraries/ICSharpCode.Decompiler/ILAst/StateRange.cs new file mode 100644 index 0000000000..4a55e1d0b6 --- /dev/null +++ b/src/Libraries/ICSharpCode.Decompiler/ILAst/StateRange.cs @@ -0,0 +1,310 @@ +// Copyright (c) 2012 AlphaSierraPapa for the SharpDevelop Team +// +// 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 +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + struct Interval + { + public readonly int Start, End; + + public Interval(int start, int end) + { + Debug.Assert(start <= end || (start == 0 && end == -1)); + this.Start = start; + this.End = end; + } + + public override string ToString() + { + return string.Format("({0} to {1})", Start, End); + } + } + + class StateRange + { + readonly List data = new List(); + + public StateRange() + { + } + + public StateRange(int start, int end) + { + this.data.Add(new Interval(start, end)); + } + + public bool IsEmpty { + get { return data.Count == 0; } + } + + public bool Contains(int val) + { + foreach (Interval v in data) { + if (v.Start <= val && val <= v.End) + return true; + } + return false; + } + + public void UnionWith(StateRange other) + { + data.AddRange(other.data); + } + + /// + /// Unions this state range with (other intersect (minVal to maxVal)) + /// + public void UnionWith(StateRange other, int minVal, int maxVal) + { + foreach (Interval v in other.data) { + int start = Math.Max(v.Start, minVal); + int end = Math.Min(v.End, maxVal); + if (start <= end) + data.Add(new Interval(start, end)); + } + } + + /// + /// Merges overlapping interval ranges. + /// + public void Simplify() + { + if (data.Count < 2) + return; + data.Sort((a, b) => a.Start.CompareTo(b.Start)); + Interval prev = data[0]; + int prevIndex = 0; + for (int i = 1; i < data.Count; i++) { + Interval next = data[i]; + Debug.Assert(prev.Start <= next.Start); + if (next.Start <= prev.End + 1) { // intervals overlapping or touching + prev = new Interval(prev.Start, Math.Max(prev.End, next.End)); + data[prevIndex] = prev; + data[i] = new Interval(0, -1); // mark as deleted + } else { + prev = next; + prevIndex = i; + } + } + data.RemoveAll(i => i.Start > i.End); // remove all entries that were marked as deleted + } + + public override string ToString() + { + return string.Join(",", data); + } + + public Interval ToEnclosingInterval() + { + if (data.Count == 0) + throw new SymbolicAnalysisFailedException(); + return new Interval(data[0].Start, data[data.Count - 1].End); + } + } + + enum StateRangeAnalysisMode + { + IteratorMoveNext, + IteratorDispose, + AsyncMoveNext + } + + class StateRangeAnalysis + { + readonly StateRangeAnalysisMode mode; + readonly FieldDefinition stateField; + internal DefaultDictionary ranges; + SymbolicEvaluationContext evalContext; + + internal Dictionary finallyMethodToStateInterval; // used only for IteratorDispose + + /// + /// Initializes the state range logic: + /// Clears 'ranges' and sets 'ranges[entryPoint]' to the full range (int.MinValue to int.MaxValue) + /// + public StateRangeAnalysis(ILNode entryPoint, StateRangeAnalysisMode mode, FieldDefinition stateField) + { + this.mode = mode; + this.stateField = stateField; + if (mode == StateRangeAnalysisMode.IteratorDispose) { + finallyMethodToStateInterval = new Dictionary(); + } + + ranges = new DefaultDictionary(n => new StateRange()); + ranges[entryPoint] = new StateRange(int.MinValue, int.MaxValue); + evalContext = new SymbolicEvaluationContext(stateField); + } + + public int AssignStateRanges(List body, int bodyLength) + { + if (bodyLength == 0) + return 0; + for (int i = 0; i < bodyLength; i++) { + StateRange nodeRange = ranges[body[i]]; + nodeRange.Simplify(); + + ILLabel label = body[i] as ILLabel; + if (label != null) { + ranges[body[i + 1]].UnionWith(nodeRange); + continue; + } + + ILTryCatchBlock tryFinally = body[i] as ILTryCatchBlock; + if (tryFinally != null) { + if (mode == StateRangeAnalysisMode.IteratorDispose) { + if (tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null) + throw new SymbolicAnalysisFailedException(); + ranges[tryFinally.TryBlock].UnionWith(nodeRange); + if (tryFinally.TryBlock.Body.Count != 0) { + ranges[tryFinally.TryBlock.Body[0]].UnionWith(nodeRange); + AssignStateRanges(tryFinally.TryBlock.Body, tryFinally.TryBlock.Body.Count); + } + continue; + } else if (mode == StateRangeAnalysisMode.AsyncMoveNext) { + return i; + } else { + throw new SymbolicAnalysisFailedException(); + } + } + + ILExpression expr = body[i] as ILExpression; + if (expr == null) + throw new SymbolicAnalysisFailedException(); + switch (expr.Code) { + case ILCode.Switch: + { + SymbolicValue val = evalContext.Eval(expr.Arguments[0]); + if (val.Type != SymbolicValueType.State) + goto default; + ILLabel[] targetLabels = (ILLabel[])expr.Operand; + for (int j = 0; j < targetLabels.Length; j++) { + int state = j - val.Constant; + ranges[targetLabels[j]].UnionWith(nodeRange, state, state); + } + StateRange nextRange = ranges[body[i + 1]]; + nextRange.UnionWith(nodeRange, int.MinValue, -1 - val.Constant); + nextRange.UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue); + break; + } + case ILCode.Br: + case ILCode.Leave: + ranges[(ILLabel)expr.Operand].UnionWith(nodeRange); + break; + case ILCode.Brtrue: + { + SymbolicValue val = evalContext.Eval(expr.Arguments[0]); + if (val.Type == SymbolicValueType.StateEquals) { + ranges[(ILLabel)expr.Operand].UnionWith(nodeRange, val.Constant, val.Constant); + StateRange nextRange = ranges[body[i + 1]]; + nextRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); + nextRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); + break; + } else if (val.Type == SymbolicValueType.StateInEquals) { + ranges[body[i + 1]].UnionWith(nodeRange, val.Constant, val.Constant); + StateRange targetRange = ranges[(ILLabel)expr.Operand]; + targetRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); + targetRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); + break; + } else { + goto default; + } + } + case ILCode.Nop: + ranges[body[i + 1]].UnionWith(nodeRange); + break; + case ILCode.Ret: + break; + case ILCode.Stloc: + { + SymbolicValue val = evalContext.Eval(expr.Arguments[0]); + if (val.Type == SymbolicValueType.State && val.Constant == 0) { + evalContext.AddStateVariable((ILVariable)expr.Operand); + goto case ILCode.Nop; + } else { + goto default; + } + } + case ILCode.Call: + // in some cases (e.g. foreach over array) the C# compiler produces a finally method outside of try-finally blocks + if (mode == StateRangeAnalysisMode.IteratorDispose) { + MethodDefinition mdef = (expr.Operand as MethodReference).ResolveWithinSameModule(); + if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) + throw new SymbolicAnalysisFailedException(); + finallyMethodToStateInterval.Add(mdef, nodeRange.ToEnclosingInterval()); + break; + } else { + goto default; + } + default: + if (mode == StateRangeAnalysisMode.IteratorDispose) { + throw new SymbolicAnalysisFailedException(); + } else { + return i; + } + } + } + return bodyLength; + } + + public void EnsureLabelAtPos(List body, ref int pos, ref int bodyLength) + { + if (pos > 0 && body[pos - 1] is ILLabel) { + pos--; + } else { + // ensure that the first element at body[pos] is a label: + ILLabel newLabel = new ILLabel(); + newLabel.Name = "YieldReturnEntryPoint"; + ranges[newLabel] = ranges[body[pos]]; // give the label the range of the instruction at body[pos] + body.Insert(pos, newLabel); + bodyLength++; + } + } + + public LabelRangeMapping CreateLabelRangeMapping(List body, int pos, int bodyLength) + { + LabelRangeMapping result = new LabelRangeMapping(); + CreateLabelRangeMapping(body, pos, bodyLength, result, false); + return result; + } + + void CreateLabelRangeMapping(List body, int pos, int bodyLength, LabelRangeMapping result, bool onlyInitialLabels) + { + for (int i = pos; i < bodyLength; i++) { + ILLabel label = body[i] as ILLabel; + if (label != null) { + result.Add(new KeyValuePair(label, ranges[label])); + } else { + ILTryCatchBlock tryCatchBlock = body[i] as ILTryCatchBlock; + if (tryCatchBlock != null) { + CreateLabelRangeMapping(tryCatchBlock.TryBlock.Body, 0, tryCatchBlock.TryBlock.Body.Count, result, true); + } else if (onlyInitialLabels) { + break; + } + } + } + } + } + + class LabelRangeMapping : List> {} +} diff --git a/src/Libraries/ICSharpCode.Decompiler/ILAst/SymbolicExecution.cs b/src/Libraries/ICSharpCode.Decompiler/ILAst/SymbolicExecution.cs new file mode 100644 index 0000000000..98255f2f2d --- /dev/null +++ b/src/Libraries/ICSharpCode.Decompiler/ILAst/SymbolicExecution.cs @@ -0,0 +1,148 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// 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 +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + /// + /// This exception is thrown when we find something else than we expect from the C# compiler. + /// This aborts the analysis and makes the whole transform fail. + /// + class SymbolicAnalysisFailedException : Exception {} + + enum SymbolicValueType + { + /// + /// Unknown value + /// + Unknown, + /// + /// int: Constant (result of ldc.i4) + /// + IntegerConstant, + /// + /// int: State + Constant + /// + State, + /// + /// This pointer (result of ldarg.0) + /// + This, + /// + /// bool: State == Constant + /// + StateEquals, + /// + /// bool: State != Constant + /// + StateInEquals + } + + struct SymbolicValue + { + public readonly int Constant; + public readonly SymbolicValueType Type; + + public SymbolicValue(SymbolicValueType type, int constant = 0) + { + this.Type = type; + this.Constant = constant; + } + + public override string ToString() + { + return string.Format("[SymbolicValue {0}: {1}]", this.Type, this.Constant); + } + } + + class SymbolicEvaluationContext + { + readonly FieldDefinition stateField; + readonly List stateVariables = new List(); + + public SymbolicEvaluationContext(FieldDefinition stateField) + { + this.stateField = stateField; + } + + public void AddStateVariable(ILVariable v) + { + if (!stateVariables.Contains(v)) + stateVariables.Add(v); + } + + SymbolicValue Failed() + { + return new SymbolicValue(SymbolicValueType.Unknown); + } + + public SymbolicValue Eval(ILExpression expr) + { + SymbolicValue left, right; + switch (expr.Code) { + case ILCode.Sub: + left = Eval(expr.Arguments[0]); + right = Eval(expr.Arguments[1]); + if (left.Type != SymbolicValueType.State && left.Type != SymbolicValueType.IntegerConstant) + return Failed(); + if (right.Type != SymbolicValueType.IntegerConstant) + return Failed(); + return new SymbolicValue(left.Type, unchecked ( left.Constant - right.Constant )); + case ILCode.Ldfld: + if (Eval(expr.Arguments[0]).Type != SymbolicValueType.This) + return Failed(); + if (CecilExtensions.ResolveWithinSameModule(expr.Operand as FieldReference) != stateField) + return Failed(); + return new SymbolicValue(SymbolicValueType.State); + case ILCode.Ldloc: + ILVariable loadedVariable = (ILVariable)expr.Operand; + if (stateVariables.Contains(loadedVariable)) + return new SymbolicValue(SymbolicValueType.State); + else if (loadedVariable.IsParameter && loadedVariable.OriginalParameter.Index < 0) + return new SymbolicValue(SymbolicValueType.This); + else + return Failed(); + case ILCode.Ldc_I4: + return new SymbolicValue(SymbolicValueType.IntegerConstant, (int)expr.Operand); + case ILCode.Ceq: + case ILCode.Cne: + left = Eval(expr.Arguments[0]); + right = Eval(expr.Arguments[1]); + if (left.Type != SymbolicValueType.State || right.Type != SymbolicValueType.IntegerConstant) + return Failed(); + // bool: (state + left.Constant == right.Constant) + // bool: (state == right.Constant - left.Constant) + return new SymbolicValue(expr.Code == ILCode.Ceq ? SymbolicValueType.StateEquals : SymbolicValueType.StateInEquals, unchecked(right.Constant - left.Constant)); + case ILCode.LogicNot: + SymbolicValue val = Eval(expr.Arguments[0]); + if (val.Type == SymbolicValueType.StateEquals) + return new SymbolicValue(SymbolicValueType.StateInEquals, val.Constant); + else if (val.Type == SymbolicValueType.StateInEquals) + return new SymbolicValue(SymbolicValueType.StateEquals, val.Constant); + else + return Failed(); + default: + return Failed(); + } + } + } +} diff --git a/src/Libraries/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs b/src/Libraries/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs index 6e8444a1ca..2e46e285bf 100644 --- a/src/Libraries/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs +++ b/src/Libraries/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs @@ -790,8 +790,17 @@ namespace ICSharpCode.Decompiler.ILAst case ILCode.YieldBreak: return null; case ILCode.Ret: - if (forceInferChildren && expr.Arguments.Count == 1) - InferTypeForExpression(expr.Arguments[0], context.CurrentMethod.ReturnType); + if (forceInferChildren && expr.Arguments.Count == 1) { + TypeReference returnType = context.CurrentMethod.ReturnType; + if (context.CurrentMethodIsAsync && returnType != null && returnType.Namespace == "System.Threading.Tasks") { + if (returnType.Name == "Task") { + returnType = typeSystem.Void; + } else if (returnType.Name == "Task`1" && returnType.IsGenericInstance) { + returnType = ((GenericInstanceType)returnType).GenericArguments[0]; + } + } + InferTypeForExpression(expr.Arguments[0], returnType); + } return null; case ILCode.YieldReturn: if (forceInferChildren) { @@ -803,6 +812,14 @@ namespace ICSharpCode.Decompiler.ILAst } } return null; + case ILCode.Await: + { + TypeReference taskType = InferTypeForExpression(expr.Arguments[0], null); + if (taskType.Name == "Task`1" && taskType.IsGenericInstance && taskType.Namespace == "System.Threading.Tasks") { + return ((GenericInstanceType)taskType).GenericArguments[0]; + } + return null; + } #endregion case ILCode.Pop: return null; diff --git a/src/Libraries/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/src/Libraries/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs index 06a211eea8..b4b6183bf8 100644 --- a/src/Libraries/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs +++ b/src/Libraries/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -24,7 +24,7 @@ using Mono.Cecil; namespace ICSharpCode.Decompiler.ILAst { - public class YieldReturnDecompiler + class YieldReturnDecompiler { // For a description on the code generated by the C# compiler for yield return: // http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx @@ -34,11 +34,8 @@ namespace ICSharpCode.Decompiler.ILAst // - Figure out which of the fields is the state field // - Construct an exception table based on states. This allows us to determine, for each state, what the parent try block is. - /// - /// This exception is thrown when we find something else than we expect from the C# compiler. - /// This aborts the analysis and makes the whole transform fail. - /// - class YieldAnalysisFailedException : Exception {} + // See http://community.sharpdevelop.net/blogs/danielgrunwald/archive/2011/03/06/ilspy-yield-return.aspx + // for a description of this step. DecompilerContext context; TypeDefinition enumeratorType; @@ -66,7 +63,7 @@ namespace ICSharpCode.Decompiler.ILAst #endif try { yrd.Run(); - } catch (YieldAnalysisFailedException) { + } catch (SymbolicAnalysisFailedException) { return; } #if DEBUG @@ -211,7 +208,7 @@ namespace ICSharpCode.Decompiler.ILAst } } if (stateField == null) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); } /// @@ -220,7 +217,7 @@ namespace ICSharpCode.Decompiler.ILAst ILBlock CreateILAst(MethodDefinition method) { if (method == null || !method.HasBody) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); ILBlock ilMethod = new ILBlock(); ILAstBuilder astBuilder = new ILAstBuilder(); @@ -269,7 +266,7 @@ namespace ICSharpCode.Decompiler.ILAst } } if (currentField == null) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); } #endregion @@ -322,337 +319,30 @@ namespace ICSharpCode.Decompiler.ILAst disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose"); ILBlock ilMethod = CreateILAst(disposeMethod); - finallyMethodToStateInterval = new Dictionary(); - - InitStateRanges(ilMethod.Body[0]); - AssignStateRanges(ilMethod.Body, ilMethod.Body.Count, forDispose: true); + var rangeAnalysis = new StateRangeAnalysis(ilMethod.Body[0], StateRangeAnalysisMode.IteratorDispose, stateField); + rangeAnalysis.AssignStateRanges(ilMethod.Body, ilMethod.Body.Count); + finallyMethodToStateInterval = rangeAnalysis.finallyMethodToStateInterval; // Now look at the finally blocks: foreach (var tryFinally in ilMethod.GetSelfAndChildrenRecursive()) { - Interval interval = ranges[tryFinally.TryBlock.Body[0]].ToEnclosingInterval(); + Interval interval = rangeAnalysis.ranges[tryFinally.TryBlock.Body[0]].ToEnclosingInterval(); var finallyBody = tryFinally.FinallyBlock.Body; if (finallyBody.Count != 2) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); ILExpression call = finallyBody[0] as ILExpression; if (call == null || call.Code != ILCode.Call || call.Arguments.Count != 1) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); if (!call.Arguments[0].MatchThis()) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); if (!finallyBody[1].Match(ILCode.Endfinally)) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); MethodDefinition mdef = GetMethodDefinition(call.Operand as MethodReference); if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); finallyMethodToStateInterval.Add(mdef, interval); } - ranges = null; - } - #endregion - - #region Assign StateRanges / Symbolic Execution (used for analysis of Dispose() and MoveNext()) - #region struct Interval / class StateRange - struct Interval - { - public readonly int Start, End; - - public Interval(int start, int end) - { - Debug.Assert(start <= end || (start == 0 && end == -1)); - this.Start = start; - this.End = end; - } - - public override string ToString() - { - return string.Format("({0} to {1})", Start, End); - } - } - - class StateRange - { - readonly List data = new List(); - - public StateRange() - { - } - - public StateRange(int start, int end) - { - this.data.Add(new Interval(start, end)); - } - - public bool Contains(int val) - { - foreach (Interval v in data) { - if (v.Start <= val && val <= v.End) - return true; - } - return false; - } - - public void UnionWith(StateRange other) - { - data.AddRange(other.data); - } - - /// - /// Unions this state range with (other intersect (minVal to maxVal)) - /// - public void UnionWith(StateRange other, int minVal, int maxVal) - { - foreach (Interval v in other.data) { - int start = Math.Max(v.Start, minVal); - int end = Math.Min(v.End, maxVal); - if (start <= end) - data.Add(new Interval(start, end)); - } - } - - /// - /// Merges overlapping interval ranges. - /// - public void Simplify() - { - if (data.Count < 2) - return; - data.Sort((a, b) => a.Start.CompareTo(b.Start)); - Interval prev = data[0]; - int prevIndex = 0; - for (int i = 1; i < data.Count; i++) { - Interval next = data[i]; - Debug.Assert(prev.Start <= next.Start); - if (next.Start <= prev.End + 1) { // intervals overlapping or touching - prev = new Interval(prev.Start, Math.Max(prev.End, next.End)); - data[prevIndex] = prev; - data[i] = new Interval(0, -1); // mark as deleted - } else { - prev = next; - prevIndex = i; - } - } - data.RemoveAll(i => i.Start > i.End); // remove all entries that were marked as deleted - } - - public override string ToString() - { - return string.Join(",", data); - } - - public Interval ToEnclosingInterval() - { - if (data.Count == 0) - throw new YieldAnalysisFailedException(); - return new Interval(data[0].Start, data[data.Count - 1].End); - } - } - #endregion - - DefaultDictionary ranges; - ILVariable rangeAnalysisStateVariable; - - /// - /// Initializes the state range logic: - /// Clears 'ranges' and sets 'ranges[entryPoint]' to the full range (int.MinValue to int.MaxValue) - /// - void InitStateRanges(ILNode entryPoint) - { - ranges = new DefaultDictionary(n => new StateRange()); - ranges[entryPoint] = new StateRange(int.MinValue, int.MaxValue); - rangeAnalysisStateVariable = null; - } - - int AssignStateRanges(List body, int bodyLength, bool forDispose) - { - if (bodyLength == 0) - return 0; - for (int i = 0; i < bodyLength; i++) { - StateRange nodeRange = ranges[body[i]]; - nodeRange.Simplify(); - - ILLabel label = body[i] as ILLabel; - if (label != null) { - ranges[body[i + 1]].UnionWith(nodeRange); - continue; - } - - ILTryCatchBlock tryFinally = body[i] as ILTryCatchBlock; - if (tryFinally != null) { - if (!forDispose || tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null) - throw new YieldAnalysisFailedException(); - ranges[tryFinally.TryBlock].UnionWith(nodeRange); - if (tryFinally.TryBlock.Body.Count != 0) { - ranges[tryFinally.TryBlock.Body[0]].UnionWith(nodeRange); - AssignStateRanges(tryFinally.TryBlock.Body, tryFinally.TryBlock.Body.Count, forDispose); - } - continue; - } - - ILExpression expr = body[i] as ILExpression; - if (expr == null) - throw new YieldAnalysisFailedException(); - switch (expr.Code) { - case ILCode.Switch: - { - SymbolicValue val = Eval(expr.Arguments[0]); - if (val.Type != SymbolicValueType.State) - throw new YieldAnalysisFailedException(); - ILLabel[] targetLabels = (ILLabel[])expr.Operand; - for (int j = 0; j < targetLabels.Length; j++) { - int state = j - val.Constant; - ranges[targetLabels[j]].UnionWith(nodeRange, state, state); - } - StateRange nextRange = ranges[body[i + 1]]; - nextRange.UnionWith(nodeRange, int.MinValue, -1 - val.Constant); - nextRange.UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue); - break; - } - case ILCode.Br: - case ILCode.Leave: - ranges[(ILLabel)expr.Operand].UnionWith(nodeRange); - break; - case ILCode.Brtrue: - { - SymbolicValue val = Eval(expr.Arguments[0]); - if (val.Type == SymbolicValueType.StateEquals) { - ranges[(ILLabel)expr.Operand].UnionWith(nodeRange, val.Constant, val.Constant); - StateRange nextRange = ranges[body[i + 1]]; - nextRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); - nextRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); - } else if (val.Type == SymbolicValueType.StateInEquals) { - ranges[body[i + 1]].UnionWith(nodeRange, val.Constant, val.Constant); - StateRange targetRange = ranges[(ILLabel)expr.Operand]; - targetRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); - targetRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); - } else { - throw new YieldAnalysisFailedException(); - } - break; - } - case ILCode.Nop: - ranges[body[i + 1]].UnionWith(nodeRange); - break; - case ILCode.Ret: - break; - case ILCode.Stloc: - { - SymbolicValue val = Eval(expr.Arguments[0]); - if (val.Type == SymbolicValueType.State && val.Constant == 0 && rangeAnalysisStateVariable == null) - rangeAnalysisStateVariable = (ILVariable)expr.Operand; - else - throw new YieldAnalysisFailedException(); - goto case ILCode.Nop; - } - case ILCode.Call: - // in some cases (e.g. foreach over array) the C# compiler produces a finally method outside of try-finally blocks - if (forDispose) { - MethodDefinition mdef = GetMethodDefinition(expr.Operand as MethodReference); - if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) - throw new YieldAnalysisFailedException(); - finallyMethodToStateInterval.Add(mdef, nodeRange.ToEnclosingInterval()); - } else { - throw new YieldAnalysisFailedException(); - } - break; - default: - if (forDispose) - throw new YieldAnalysisFailedException(); - else - return i; - } - } - return bodyLength; - } - - enum SymbolicValueType - { - /// - /// int: Constant (result of ldc.i4) - /// - IntegerConstant, - /// - /// int: State + Constant - /// - State, - /// - /// This pointer (result of ldarg.0) - /// - This, - /// - /// bool: State == Constant - /// - StateEquals, - /// - /// bool: State != Constant - /// - StateInEquals - } - - struct SymbolicValue - { - public readonly int Constant; - public readonly SymbolicValueType Type; - - public SymbolicValue(SymbolicValueType type, int constant = 0) - { - this.Type = type; - this.Constant = constant; - } - - public override string ToString() - { - return string.Format("[SymbolicValue {0}: {1}]", this.Type, this.Constant); - } - } - - SymbolicValue Eval(ILExpression expr) - { - SymbolicValue left, right; - switch (expr.Code) { - case ILCode.Sub: - left = Eval(expr.Arguments[0]); - right = Eval(expr.Arguments[1]); - if (left.Type != SymbolicValueType.State && left.Type != SymbolicValueType.IntegerConstant) - throw new YieldAnalysisFailedException(); - if (right.Type != SymbolicValueType.IntegerConstant) - throw new YieldAnalysisFailedException(); - return new SymbolicValue(left.Type, unchecked ( left.Constant - right.Constant )); - case ILCode.Ldfld: - if (Eval(expr.Arguments[0]).Type != SymbolicValueType.This) - throw new YieldAnalysisFailedException(); - if (GetFieldDefinition(expr.Operand as FieldReference) != stateField) - throw new YieldAnalysisFailedException(); - return new SymbolicValue(SymbolicValueType.State); - case ILCode.Ldloc: - ILVariable loadedVariable = (ILVariable)expr.Operand; - if (loadedVariable == rangeAnalysisStateVariable) - return new SymbolicValue(SymbolicValueType.State); - else if (loadedVariable.IsParameter && loadedVariable.OriginalParameter.Index < 0) - return new SymbolicValue(SymbolicValueType.This); - else - throw new YieldAnalysisFailedException(); - case ILCode.Ldc_I4: - return new SymbolicValue(SymbolicValueType.IntegerConstant, (int)expr.Operand); - case ILCode.Ceq: - case ILCode.Cne: - left = Eval(expr.Arguments[0]); - right = Eval(expr.Arguments[1]); - if (left.Type != SymbolicValueType.State || right.Type != SymbolicValueType.IntegerConstant) - throw new YieldAnalysisFailedException(); - // bool: (state + left.Constant == right.Constant) - // bool: (state == right.Constant - left.Constant) - return new SymbolicValue(expr.Code == ILCode.Ceq ? SymbolicValueType.StateEquals : SymbolicValueType.StateInEquals, unchecked(right.Constant - left.Constant)); - case ILCode.LogicNot: - SymbolicValue val = Eval(expr.Arguments[0]); - if (val.Type == SymbolicValueType.StateEquals) - return new SymbolicValue(SymbolicValueType.StateInEquals, val.Constant); - else if (val.Type == SymbolicValueType.StateInEquals) - return new SymbolicValue(SymbolicValueType.StateEquals, val.Constant); - else - throw new YieldAnalysisFailedException(); - default: - throw new YieldAnalysisFailedException(); - } + rangeAnalysis = null; } #endregion @@ -667,10 +357,10 @@ namespace ICSharpCode.Decompiler.ILAst ILBlock ilMethod = CreateILAst(moveNextMethod); if (ilMethod.Body.Count == 0) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); ILExpression lastReturnArg; if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturnArg)) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); // There are two possibilities: if (lastReturnArg.Code == ILCode.Ldloc) { @@ -678,14 +368,14 @@ namespace ICSharpCode.Decompiler.ILAst returnVariable = (ILVariable)lastReturnArg.Operand; returnLabel = ilMethod.Body.ElementAtOrDefault(ilMethod.Body.Count - 2) as ILLabel; if (returnLabel == null) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); } else { // b) the compiler directly returns constants returnVariable = null; returnLabel = null; // In this case, the last return must return false. if (lastReturnArg.Code != ILCode.Ldc_I4 || (int)lastReturnArg.Operand != 0) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); } ILTryCatchBlock tryFaultBlock = ilMethod.Body[0] as ILTryCatchBlock; @@ -694,23 +384,23 @@ namespace ICSharpCode.Decompiler.ILAst if (tryFaultBlock != null) { // there are try-finally blocks if (returnVariable == null) // in this case, we must use a return variable - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); // must be a try-fault block: if (tryFaultBlock.CatchBlocks.Count != 0 || tryFaultBlock.FinallyBlock != null || tryFaultBlock.FaultBlock == null) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); ILBlock faultBlock = tryFaultBlock.FaultBlock; // Ensure the fault block contains the call to Dispose(). if (faultBlock.Body.Count != 2) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); MethodReference disposeMethodRef; ILExpression disposeArg; if (!faultBlock.Body[0].Match(ILCode.Call, out disposeMethodRef, out disposeArg)) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); if (GetMethodDefinition(disposeMethodRef) != disposeMethod || !disposeArg.MatchThis()) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); if (!faultBlock.Body[1].Match(ILCode.Endfinally)) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); body = tryFaultBlock.TryBlock.Body; bodyLength = body.Count; @@ -734,39 +424,22 @@ namespace ICSharpCode.Decompiler.ILAst bodyLength--; ILExpression store0 = body.ElementAtOrDefault(bodyLength - 1) as ILExpression; if (store0 == null || store0.Code != ILCode.Stloc || store0.Operand != returnVariable) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); if (store0.Arguments[0].Code != ILCode.Ldc_I4 || (int)store0.Arguments[0].Operand != 0) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); bodyLength--; // don't conside the stloc instruction to be part of the body } // verify that the last element in the body is a label pointing to the 'ret(false)' returnFalseLabel = body.ElementAtOrDefault(bodyLength - 1) as ILLabel; if (returnFalseLabel == null) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); - InitStateRanges(body[0]); - int pos = AssignStateRanges(body, bodyLength, forDispose: false); - if (pos > 0 && body[pos - 1] is ILLabel) { - pos--; - } else { - // ensure that the first element at body[pos] is a label: - ILLabel newLabel = new ILLabel(); - newLabel.Name = "YieldReturnEntryPoint"; - ranges[newLabel] = ranges[body[pos]]; // give the label the range of the instruction at body[pos] - - body.Insert(pos, newLabel); - bodyLength++; - } - - List> labels = new List>(); - for (int i = pos; i < bodyLength; i++) { - ILLabel label = body[i] as ILLabel; - if (label != null) { - labels.Add(new KeyValuePair(label, ranges[label])); - } - } + var rangeAnalysis = new StateRangeAnalysis(body[0], StateRangeAnalysisMode.IteratorMoveNext, stateField); + int pos = rangeAnalysis.AssignStateRanges(body, bodyLength); + rangeAnalysis.EnsureLabelAtPos(body, ref pos, ref bodyLength); + var labels = rangeAnalysis.CreateLabelRangeMapping(body, pos, bodyLength); ConvertBody(body, pos, bodyLength, labels); } #endregion @@ -797,7 +470,7 @@ namespace ICSharpCode.Decompiler.ILAst // Handle stores to 'state' or 'current' if (GetFieldDefinition(expr.Operand as FieldReference) == stateField) { if (expr.Arguments[1].Code != ILCode.Ldc_I4) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); currentState = (int)expr.Arguments[1].Operand; stateChanges.Add(new SetState(newBody.Count, currentState)); } else if (GetFieldDefinition(expr.Operand as FieldReference) == currentField) { @@ -809,18 +482,18 @@ namespace ICSharpCode.Decompiler.ILAst // handle store+branch to the returnVariable ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression; if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnLabel || expr.Arguments[0].Code != ILCode.Ldc_I4) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); int val = (int)expr.Arguments[0].Operand; if (val == 0) { newBody.Add(MakeGoTo(returnFalseLabel)); } else if (val == 1) { newBody.Add(MakeGoTo(labels, currentState)); } else { - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); } } else if (expr != null && expr.Code == ILCode.Ret) { if (expr.Arguments.Count != 1 || expr.Arguments[0].Code != ILCode.Ldc_I4) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); // handle direct return (e.g. in release builds) int val = (int)expr.Arguments[0].Operand; if (val == 0) { @@ -828,24 +501,24 @@ namespace ICSharpCode.Decompiler.ILAst } else if (val == 1) { newBody.Add(MakeGoTo(labels, currentState)); } else { - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); } } else if (expr != null && expr.Code == ILCode.Call && expr.Arguments.Count == 1 && expr.Arguments[0].MatchThis()) { MethodDefinition method = GetMethodDefinition(expr.Operand as MethodReference); if (method == null) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); Interval interval; if (method == disposeMethod) { // Explicit call to dispose is used for "yield break;" within the method. ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression; if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnFalseLabel) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); newBody.Add(MakeGoTo(returnFalseLabel)); } else if (finallyMethodToStateInterval.TryGetValue(method, out interval)) { // Call to Finally-method int index = stateChanges.FindIndex(ss => ss.NewState >= interval.Start && ss.NewState <= interval.End); if (index < 0) - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); ILLabel label = new ILLabel(); label.Name = "JumpOutOfTryFinally" + interval.Start + "_" + interval.End; @@ -883,7 +556,7 @@ namespace ICSharpCode.Decompiler.ILAst if (pair.Value.Contains(state)) return MakeGoTo(pair.Key); } - throw new YieldAnalysisFailedException(); + throw new SymbolicAnalysisFailedException(); } ILBlock ConvertFinallyBlock(MethodDefinition finallyMethod) @@ -907,6 +580,11 @@ namespace ICSharpCode.Decompiler.ILAst #region TranslateFieldsToLocalAccess void TranslateFieldsToLocalAccess() + { + TranslateFieldsToLocalAccess(newBody, fieldToParameterMap); + } + + internal static void TranslateFieldsToLocalAccess(List newBody, Dictionary fieldToParameterMap) { var fieldToLocalMap = new DefaultDictionary(f => new ILVariable { Name = f.Name, Type = f.FieldType }); foreach (ILNode node in newBody) { diff --git a/src/Libraries/ICSharpCode.Decompiler/Properties/AssemblyInfo.cs b/src/Libraries/ICSharpCode.Decompiler/Properties/AssemblyInfo.cs index 9260b6ba55..818b72b10b 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Properties/AssemblyInfo.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Properties/AssemblyInfo.cs @@ -19,8 +19,8 @@ using System.Runtime.InteropServices; // If you need to expose a type to COM, use [ComVisible(true)] on that type. [assembly: ComVisible(false)] -[assembly: AssemblyVersion("2.0.0.1595")] -[assembly: AssemblyInformationalVersion("2.0.0.1595-5773d3d2")] +[assembly: AssemblyVersion("2.1.0.1603")] +[assembly: AssemblyInformationalVersion("2.1.0.1603-1170e2f8")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2243:AttributeStringLiteralsShouldParseCorrectly", diff --git a/src/Libraries/ICSharpCode.Decompiler/Tests/Async.cs b/src/Libraries/ICSharpCode.Decompiler/Tests/Async.cs new file mode 100644 index 0000000000..ccd1222556 --- /dev/null +++ b/src/Libraries/ICSharpCode.Decompiler/Tests/Async.cs @@ -0,0 +1,144 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// 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 +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#pragma warning disable 1998 +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +public class Async +{ + 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 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 SimpleBoolTaskMethod() + { + Console.WriteLine("Before"); + await Task.Delay(TimeSpan.FromSeconds(1.0)); + Console.WriteLine("After"); + return true; + } + + public async void TwoAwaitsWithDifferentAwaiterTypes() + { + Console.WriteLine("Before"); + if (await this.SimpleBoolTaskMethod()) + { + await Task.Delay(TimeSpan.FromSeconds(1.0)); + } + Console.WriteLine("After"); + } + + public async void StreamCopyTo(Stream destination, int bufferSize) + { + byte[] array = new byte[bufferSize]; + int count; + while ((count = await destination.ReadAsync(array, 0, array.Length)) != 0) + { + await destination.WriteAsync(array, 0, count); + } + } + + public async void StreamCopyToWithConfigureAwait(Stream destination, int bufferSize) + { + byte[] array = new byte[bufferSize]; + int count; + while ((count = await destination.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(array, 0, count).ConfigureAwait(false); + } + } + + public async void AwaitInLoopCondition() + { + while (await this.SimpleBoolTaskMethod()) + { + Console.WriteLine("Body"); + } + } + + public async Task AwaitInForEach(IEnumerable> elements) + { + int num = 0; + foreach (Task current in elements) + { + num += await current; + } + return num; + } + + public async Task TaskMethodWithoutAwaitButWithExceptionHandling() + { + try + { + using (new StringWriter()) + { + Console.WriteLine("No Await"); + } + } + catch (Exception) + { + Console.WriteLine("Crash"); + } + } + + public async Task NestedAwait(Task> task) + { + return await(await task); + } + + public async Task AwaitWithStack(Task task) + { + Console.WriteLine("A", 1, await task); + } + + public async Task AwaitWithStack2(Task task) + { + if (await this.SimpleBoolTaskMethod()) + { + Console.WriteLine("A", 1, await task); + } + else + { + int num = 1; + Console.WriteLine("A", 1, num); + } + } +} diff --git a/src/Libraries/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/src/Libraries/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index 52a4f950da..69598ac73f 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/src/Libraries/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -7,7 +7,7 @@ Library ICSharpCode.Decompiler.Tests ICSharpCode.Decompiler.Tests - v4.0 + v4.5 Properties True False @@ -15,6 +15,7 @@ false False 67,169,1058,728,1720,649 + x86 @@ -62,6 +63,7 @@ + diff --git a/src/Libraries/ICSharpCode.Decompiler/Tests/PropertiesAndEvents.cs b/src/Libraries/ICSharpCode.Decompiler/Tests/PropertiesAndEvents.cs index bdc4f7dd1f..9b3d165893 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Tests/PropertiesAndEvents.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Tests/PropertiesAndEvents.cs @@ -24,7 +24,7 @@ public class PropertiesAndEvents public event EventHandler AutomaticEvent; [field: NonSerialized] - public event EventHandler AutomaticEventWithInitializer = delegate + public event EventHandler AutomaticEventWithInitializer = delegate(object sender, EventArgs e) { }; diff --git a/src/Libraries/ICSharpCode.Decompiler/Tests/TestRunner.cs b/src/Libraries/ICSharpCode.Decompiler/Tests/TestRunner.cs index 8160a7376d..52a03a4623 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Tests/TestRunner.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Tests/TestRunner.cs @@ -34,6 +34,12 @@ namespace ICSharpCode.Decompiler.Tests [TestFixture] public class TestRunner { + [Test] + public void Async() + { + TestFile(@"..\..\Tests\Async.cs"); + } + [Test, Ignore("disambiguating overloads is not yet implemented")] public void CallOverloadedMethod() {