Browse Source

Improved async/await decompiler.

pull/276/merge
Daniel Grunwald 13 years ago
parent
commit
8acf17d50a
  1. 8
      ICSharpCode.Decompiler/Ast/AstBuilder.cs
  2. 44
      ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs
  3. 8
      ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs
  4. 43
      ICSharpCode.Decompiler/Tests/Async.cs
  5. 6
      ICSharpCode.Decompiler/Tests/TestRunner.cs
  6. 3
      ILSpy/Languages/ILAstLanguage.cs

8
ICSharpCode.Decompiler/Ast/AstBuilder.cs

@ -81,6 +81,8 @@ namespace ICSharpCode.Decompiler.Ast
return true; return true;
if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(type)) if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(type))
return true; return true;
if (settings.AsyncAwait && AsyncDecompiler.IsCompilerGeneratedStateMachine(type))
return true;
} else if (type.IsCompilerGenerated()) { } else if (type.IsCompilerGenerated()) {
if (type.Name.StartsWith("<PrivateImplementationDetails>", StringComparison.Ordinal)) if (type.Name.StartsWith("<PrivateImplementationDetails>", StringComparison.Ordinal))
return true; return true;
@ -1357,6 +1359,12 @@ namespace ICSharpCode.Decompiler.Ast
// don't show the ParamArrayAttribute (it's converted to the 'params' modifier) // don't show the ParamArrayAttribute (it's converted to the 'params' modifier)
continue; continue;
} }
if (customAttribute.AttributeType.Name == "DebuggerStepThroughAttribute" && customAttribute.AttributeType.Namespace == "System.Diagnostics") {
// don't show the attribute if the method is async
EntityDeclaration entityDecl = attributedNode as EntityDeclaration;
if (entityDecl != null && entityDecl.HasModifier(Modifiers.Async))
continue;
}
var attribute = new ICSharpCode.NRefactory.CSharp.Attribute(); var attribute = new ICSharpCode.NRefactory.CSharp.Attribute();
attribute.AddAnnotation(customAttribute); attribute.AddAnnotation(customAttribute);

44
ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs

@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil;
namespace ICSharpCode.Decompiler.ILAst namespace ICSharpCode.Decompiler.ILAst
{ {
@ -29,6 +30,17 @@ namespace ICSharpCode.Decompiler.ILAst
/// </summary> /// </summary>
class AsyncDecompiler 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 enum AsyncMethodType
{ {
Void, Void,
@ -90,6 +102,7 @@ namespace ICSharpCode.Decompiler.ILAst
ValidateCatchBlock(mainTryCatch.CatchBlocks[0]); ValidateCatchBlock(mainTryCatch.CatchBlocks[0]);
AnalyzeStateMachine(mainTryCatch.TryBlock); AnalyzeStateMachine(mainTryCatch.TryBlock);
// AnalyzeStateMachine invokes ConvertBody // AnalyzeStateMachine invokes ConvertBody
MarkGeneratedVariables();
YieldReturnDecompiler.TranslateFieldsToLocalAccess(newTopLevelBody, fieldToParameterMap); YieldReturnDecompiler.TranslateFieldsToLocalAccess(newTopLevelBody, fieldToParameterMap);
} }
#endregion #endregion
@ -384,6 +397,7 @@ namespace ICSharpCode.Decompiler.ILAst
FieldDefinition awaiterField; FieldDefinition awaiterField;
int targetStateID; int targetStateID;
HandleAwait(newBody, out awaiterVar, out awaiterField, out 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(new ILExpression(ILCode.Await, null, new ILExpression(ILCode.Ldloca, awaiterVar)));
newBody.Add(MakeGoTo(mapping, targetStateID)); newBody.Add(MakeGoTo(mapping, targetStateID));
} else if (tryCatchBlock != null) { } else if (tryCatchBlock != null) {
@ -431,6 +445,10 @@ namespace ICSharpCode.Decompiler.ILAst
if (loadDoFinallyBodies.MatchLdloc(doFinallyBodies) && loadZero.Match(ILCode.Ldc_I4, out num) && num == 0) { if (loadDoFinallyBodies.MatchLdloc(doFinallyBodies) && loadZero.Match(ILCode.Ldc_I4, out num) && num == 0) {
newBody.RemoveAt(0); newBody.RemoveAt(0);
} }
} else if (ceqExpr.Match(ILCode.LogicNot, out loadDoFinallyBodies)) {
if (loadDoFinallyBodies.MatchLdloc(doFinallyBodies)) {
newBody.RemoveAt(0);
}
} }
} }
return newBody; return newBody;
@ -481,6 +499,26 @@ namespace ICSharpCode.Decompiler.ILAst
} }
#endregion #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<ILExpression>();
foreach (var v in expressions.Select(e => e.Operand).OfType<ILVariable>()) {
if (v.OriginalVariable != null && v.OriginalVariable.Index >= smallestGeneratedVariableIndex)
v.IsGenerated = true;
}
}
#endregion
#region RunStep2() method #region RunStep2() method
public static void RunStep2(DecompilerContext context, ILBlock method) public static void RunStep2(DecompilerContext context, ILBlock method)
{ {
@ -524,6 +562,11 @@ namespace ICSharpCode.Decompiler.ILAst
if (!loadAwaiter.Match(ILCode.Ldloca, out awaiterVar)) if (!loadAwaiter.Match(ILCode.Ldloca, out awaiterVar))
return false; 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<bool>::GetAwaiter, awaiterExpr) // stloc(CS$0$0001, callvirt(class System.Threading.Tasks.Task`1<bool>::GetAwaiter, awaiterExpr)
ILExpression getAwaiterCall; ILExpression getAwaiterCall;
if (!(pos >= 2 && body[pos - 2].MatchStloc(awaiterVar, out getAwaiterCall))) if (!(pos >= 2 && body[pos - 2].MatchStloc(awaiterVar, out getAwaiterCall)))
@ -552,6 +595,7 @@ namespace ICSharpCode.Decompiler.ILAst
case ILCode.Stloc: case ILCode.Stloc:
case ILCode.Initobj: case ILCode.Initobj:
case ILCode.Stfld: case ILCode.Stfld:
case ILCode.Await:
// e.g. // e.g.
// stloc(CS$0$0001, ldfld(StateMachine::<>u__$awaitere, ldloc(this))) // stloc(CS$0$0001, ldfld(StateMachine::<>u__$awaitere, ldloc(this)))
// initobj(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<bool>, ldloca(CS$0$0002_66)) // initobj(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<bool>, ldloca(CS$0$0002_66))

8
ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs

@ -812,6 +812,14 @@ namespace ICSharpCode.Decompiler.ILAst
} }
} }
return null; 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 #endregion
case ILCode.Pop: case ILCode.Pop:
return null; return null;

43
ICSharpCode.Decompiler/Tests/Async.cs

@ -59,7 +59,8 @@ public class Async
public async void TwoAwaitsWithDifferentAwaiterTypes() public async void TwoAwaitsWithDifferentAwaiterTypes()
{ {
Console.WriteLine("Before"); Console.WriteLine("Before");
if (await SimpleBoolTaskMethod()) { if (await this.SimpleBoolTaskMethod())
{
await Task.Delay(TimeSpan.FromSeconds(1.0)); await Task.Delay(TimeSpan.FromSeconds(1.0));
} }
Console.WriteLine("After"); Console.WriteLine("After");
@ -77,27 +78,33 @@ public class Async
public async void AwaitInLoopCondition() public async void AwaitInLoopCondition()
{ {
while (await SimpleBoolTaskMethod()) { while (await this.SimpleBoolTaskMethod())
{
Console.WriteLine("Body"); Console.WriteLine("Body");
} }
} }
public async Task<int> AwaitInForEach(IEnumerable<Task<int>> elements) public async Task<int> AwaitInForEach(IEnumerable<Task<int>> elements)
{ {
int sum = 0; int num = 0;
foreach (Task<int> element in elements) { foreach (Task<int> current in elements)
sum += await element; {
num += await current;
} }
return sum; return num;
} }
public async Task TaskMethodWithoutAwaitButWithExceptionHandling() public async Task TaskMethodWithoutAwaitButWithExceptionHandling()
{ {
try { try
using (new StringWriter()) { {
using (new StringWriter())
{
Console.WriteLine("No Await"); Console.WriteLine("No Await");
} }
} catch (Exception) { }
catch (Exception)
{
Console.WriteLine("Crash"); Console.WriteLine("Crash");
} }
} }
@ -106,4 +113,22 @@ public class Async
{ {
return await(await task); return await(await task);
} }
public async Task AwaitWithStack(Task<int> task)
{
Console.WriteLine("A", 1, await task);
}
public async Task AwaitWithStack2(Task<int> task)
{
if (await this.SimpleBoolTaskMethod())
{
Console.WriteLine("A", 1, await task);
}
else
{
int num = 1;
Console.WriteLine("A", 1, num);
}
}
} }

6
ICSharpCode.Decompiler/Tests/TestRunner.cs

@ -34,6 +34,12 @@ namespace ICSharpCode.Decompiler.Tests
[TestFixture] [TestFixture]
public class TestRunner public class TestRunner
{ {
[Test]
public void Async()
{
TestFile(@"..\..\Tests\Async.cs");
}
[Test, Ignore("disambiguating overloads is not yet implemented")] [Test, Ignore("disambiguating overloads is not yet implemented")]
public void CallOverloadedMethod() public void CallOverloadedMethod()
{ {

3
ILSpy/Languages/ILAstLanguage.cs

@ -71,6 +71,9 @@ namespace ICSharpCode.ILSpy
output.Write("pinned "); output.Write("pinned ");
v.Type.WriteTo(output, ILNameSyntax.ShortTypeName); v.Type.WriteTo(output, ILNameSyntax.ShortTypeName);
} }
if (v.IsGenerated) {
output.Write(" [generated]");
}
output.WriteLine(); output.WriteLine();
} }
output.WriteLine(); output.WriteLine();

Loading…
Cancel
Save