diff --git a/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/ICSharpCode.Decompiler/Ast/AstBuilder.cs
index fc288d80f..d6951a600 100644
--- a/ICSharpCode.Decompiler/Ast/AstBuilder.cs
+++ b/ICSharpCode.Decompiler/Ast/AstBuilder.cs
@@ -770,6 +770,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) {
diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs
index 5de5b0ba5..8ad9047c1 100644
--- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs
+++ b/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/ICSharpCode.Decompiler/Ast/DecompilerContext.cs b/ICSharpCode.Decompiler/Ast/DecompilerContext.cs
index 4a3f27782..624b1b33f 100644
--- a/ICSharpCode.Decompiler/Ast/DecompilerContext.cs
+++ b/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/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs
index 9264000f0..9c5aa97f6 100644
--- a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs
+++ b/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/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index 435124491..f78194a6f 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/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/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 4dd1e3a43..245c993e7 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -101,6 +101,7 @@
+
@@ -114,6 +115,8 @@
+
+
diff --git a/ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs b/ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs
new file mode 100644
index 000000000..e24dd48bc
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs
@@ -0,0 +1,604 @@
+// 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
+{
+ ///
+ /// Decompiler step for C# 5 async/await.
+ ///
+ class AsyncDecompiler
+ {
+ 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
+ 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);
+ 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);
+ }
+ }
+ }
+ 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 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;
+
+ // 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;
+
+ // 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:
+ // 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/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs
index 051662f35..bf85ca94f 100644
--- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs
+++ b/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/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/ICSharpCode.Decompiler/ILAst/ILCodes.cs
index cbf548698..5ca063b75 100644
--- a/ICSharpCode.Decompiler/ILAst/ILCodes.cs
+++ b/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/ICSharpCode.Decompiler/ILAst/PatternMatching.cs b/ICSharpCode.Decompiler/ILAst/PatternMatching.cs
index cc4e5ee5f..31fcf62b1 100644
--- a/ICSharpCode.Decompiler/ILAst/PatternMatching.cs
+++ b/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/ICSharpCode.Decompiler/ILAst/StateRange.cs b/ICSharpCode.Decompiler/ILAst/StateRange.cs
new file mode 100644
index 000000000..4a55e1d0b
--- /dev/null
+++ b/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/ICSharpCode.Decompiler/ILAst/SymbolicExecution.cs b/ICSharpCode.Decompiler/ILAst/SymbolicExecution.cs
new file mode 100644
index 000000000..98255f2f2
--- /dev/null
+++ b/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/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs
index 6e8444a1c..3d7dd0787 100644
--- a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs
+++ b/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) {
diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs
index 06a211eea..b4b6183bf 100644
--- a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs
+++ b/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/ICSharpCode.Decompiler/Tests/Async.cs b/ICSharpCode.Decompiler/Tests/Async.cs
new file mode 100644
index 000000000..da9b520e4
--- /dev/null
+++ b/ICSharpCode.Decompiler/Tests/Async.cs
@@ -0,0 +1,85 @@
+// 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 AwaitInLoopCondition()
+ {
+ while (await SimpleBoolTaskMethod()) {
+ Console.WriteLine("Body");
+ }
+ }
+
+ public async Task AwaitInForEach(IEnumerable> elements)
+ {
+ int sum = 0;
+ foreach (Task element in elements) {
+ sum += await element;
+ }
+ return sum;
+ }
+
+ public async Task TaskMethodWithoutAwaitButWithExceptionHandling()
+ {
+ try {
+ using (new StringWriter()) {
+ Console.WriteLine("No Await");
+ }
+ } catch (Exception) {
+ Console.WriteLine("Crash");
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
index 52a4f950d..69598ac73 100644
--- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/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/ILSpy.sln b/ILSpy.sln
index 7ec42f0d8..82285a7ff 100644
--- a/ILSpy.sln
+++ b/ILSpy.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
-# SharpDevelop 4.2.0.8200-alpha
+# SharpDevelop 4.2.0.8657-Beta 2
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{F45DB999-7E72-4000-B5AD-3A7B485A0896}"
ProjectSection(SolutionItems) = postProject
doc\Command Line.txt = doc\Command Line.txt
diff --git a/ILSpy/Languages/ILAstLanguage.cs b/ILSpy/Languages/ILAstLanguage.cs
index 7f4af7a79..46a242e21 100644
--- a/ILSpy/Languages/ILAstLanguage.cs
+++ b/ILSpy/Languages/ILAstLanguage.cs
@@ -58,6 +58,9 @@ namespace ICSharpCode.ILSpy
new ILAstOptimizer().Optimize(context, ilMethod, abortBeforeStep.Value);
}
+ if (context.CurrentMethodIsAsync)
+ output.WriteLine("async/await");
+
var allVariables = ilMethod.GetSelfAndChildrenRecursive().Select(e => e.Operand as ILVariable)
.Where(v => v != null && !v.IsParameter).Distinct();
foreach (ILVariable v in allVariables) {
diff --git a/ILSpy/Options/DecompilerSettingsPanel.xaml b/ILSpy/Options/DecompilerSettingsPanel.xaml
index 79b9bdc32..f72c387d8 100644
--- a/ILSpy/Options/DecompilerSettingsPanel.xaml
+++ b/ILSpy/Options/DecompilerSettingsPanel.xaml
@@ -5,6 +5,7 @@
Decompile anonymous methods/lambdas
Decompile enumerators (yield return)
+ Decompile async methods (async/await)
Decompile query expressions
Use variable names from debug symbols, if available
Show XML documentation in decompiled code
diff --git a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs
index 54a269b38..e665f5eac 100644
--- a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs
+++ b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs
@@ -53,6 +53,7 @@ namespace ICSharpCode.ILSpy.Options
DecompilerSettings s = new DecompilerSettings();
s.AnonymousMethods = (bool?)e.Attribute("anonymousMethods") ?? s.AnonymousMethods;
s.YieldReturn = (bool?)e.Attribute("yieldReturn") ?? s.YieldReturn;
+ s.AsyncAwait = (bool?)e.Attribute("asyncAwait") ?? s.AsyncAwait;
s.QueryExpressions = (bool?)e.Attribute("queryExpressions") ?? s.QueryExpressions;
s.UseDebugSymbols = (bool?)e.Attribute("useDebugSymbols") ?? s.UseDebugSymbols;
s.ShowXmlDocumentation = (bool?)e.Attribute("xmlDoc") ?? s.ShowXmlDocumentation;
@@ -65,6 +66,7 @@ namespace ICSharpCode.ILSpy.Options
XElement section = new XElement("DecompilerSettings");
section.SetAttributeValue("anonymousMethods", s.AnonymousMethods);
section.SetAttributeValue("yieldReturn", s.YieldReturn);
+ section.SetAttributeValue("asyncAwait", s.AsyncAwait);
section.SetAttributeValue("queryExpressions", s.QueryExpressions);
section.SetAttributeValue("useDebugSymbols", s.UseDebugSymbols);
section.SetAttributeValue("xmlDoc", s.ShowXmlDocumentation);
diff --git a/NRefactory/ICSharpCode.NRefactory.VB/OutputVisitor/OutputVisitor.cs b/NRefactory/ICSharpCode.NRefactory.VB/OutputVisitor/OutputVisitor.cs
index 40b17bcf1..7edd3a525 100644
--- a/NRefactory/ICSharpCode.NRefactory.VB/OutputVisitor/OutputVisitor.cs
+++ b/NRefactory/ICSharpCode.NRefactory.VB/OutputVisitor/OutputVisitor.cs
@@ -1963,6 +1963,7 @@ namespace ICSharpCode.NRefactory.VB
WriteKeyword("For");
WriteKeyword("Each");
forEachStatement.Variable.AcceptVisitor(this, data);
+ Space();
WriteKeyword("In");
forEachStatement.InExpression.AcceptVisitor(this, data);
NewLine();
diff --git a/NRefactory/ICSharpCode.NRefactory.VB/Visitors/CSharpToVBConverterVisitor.cs b/NRefactory/ICSharpCode.NRefactory.VB/Visitors/CSharpToVBConverterVisitor.cs
index 1725d7ec9..48a2c34d2 100644
--- a/NRefactory/ICSharpCode.NRefactory.VB/Visitors/CSharpToVBConverterVisitor.cs
+++ b/NRefactory/ICSharpCode.NRefactory.VB/Visitors/CSharpToVBConverterVisitor.cs
@@ -671,6 +671,12 @@ namespace ICSharpCode.NRefactory.VB.Visitors
((InvocationExpression)expr).Target = new IdentifierExpression() { Identifier = "__Dereference" };
((InvocationExpression)expr).Arguments.Add((Expression)unaryOperatorExpression.Expression.AcceptVisitor(this, data));
break;
+ case ICSharpCode.NRefactory.CSharp.UnaryOperatorType.Await:
+ expr = new UnaryOperatorExpression() {
+ Expression = (Expression)unaryOperatorExpression.Expression.AcceptVisitor(this, data),
+ Operator = UnaryOperatorType.Await
+ };
+ break;
default:
throw new Exception("Invalid value for UnaryOperatorType");
}
@@ -2134,6 +2140,8 @@ namespace ICSharpCode.NRefactory.VB.Visitors
mod |= Modifiers.Override;
if ((modifier & CSharp.Modifiers.Virtual) == CSharp.Modifiers.Virtual)
mod |= Modifiers.Overridable;
+ if ((modifier & CSharp.Modifiers.Async) == CSharp.Modifiers.Async)
+ mod |= Modifiers.Async;
return mod;
}