зеркало из https://github.com/DeGsoft/maui-linux.git
705 строки
27 KiB
C#
705 строки
27 KiB
C#
// 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
|
|
{
|
|
/// <summary>
|
|
/// Decompiler step for C# 5 async/await.
|
|
/// </summary>
|
|
internal 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<FieldDefinition, ILVariable> fieldToParameterMap = new Dictionary<FieldDefinition, ILVariable>();
|
|
ILVariable cachedStateVar;
|
|
|
|
// 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) || loadStateMachineForBuilderExpr.MatchLdloc(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);
|
|
|
|
int startIndex;
|
|
if (ilMethod.Body.Count == 6) {
|
|
startIndex = 0;
|
|
} else if (ilMethod.Body.Count == 7) {
|
|
// stloc(cachedState, ldfld(valuetype StateMachineStruct::<>1__state, ldloc(this)))
|
|
ILExpression cachedStateInit;
|
|
if (!ilMethod.Body[0].Match(ILCode.Stloc, out cachedStateVar, out cachedStateInit))
|
|
throw new SymbolicAnalysisFailedException();
|
|
ILExpression instanceExpr;
|
|
FieldReference loadedField;
|
|
if (!cachedStateInit.Match(ILCode.Ldfld, out loadedField, out instanceExpr) || loadedField.ResolveWithinSameModule() != stateField || !instanceExpr.MatchThis())
|
|
throw new SymbolicAnalysisFailedException();
|
|
startIndex = 1;
|
|
} else {
|
|
throw new SymbolicAnalysisFailedException();
|
|
}
|
|
|
|
mainTryCatch = ilMethod.Body[startIndex + 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[startIndex + 1] as ILLabel;
|
|
if (setResultAndExitLabel == null)
|
|
throw new SymbolicAnalysisFailedException();
|
|
|
|
if (!MatchStateAssignment(ilMethod.Body[startIndex + 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[startIndex + 3].Match(ILCode.Call, out setResultMethod, out builderExpr, out resultExpr))
|
|
throw new SymbolicAnalysisFailedException();
|
|
} else {
|
|
if (!ilMethod.Body[startIndex + 3].Match(ILCode.Call, out setResultMethod, out builderExpr))
|
|
throw new SymbolicAnalysisFailedException();
|
|
}
|
|
if (!(setResultMethod.Name == "SetResult" && IsBuilderFieldOnThis(builderExpr)))
|
|
throw new SymbolicAnalysisFailedException();
|
|
|
|
exitLabel = ilMethod.Body[startIndex + 4] as ILLabel;
|
|
if (exitLabel == null)
|
|
throw new SymbolicAnalysisFailedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates ILAst for the specified method, optimized up to before the 'YieldReturn' step.
|
|
/// </summary>
|
|
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(stateId))
|
|
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;
|
|
}
|
|
|
|
bool MatchRoslynStateAssignment(List<ILNode> block, int index, out int stateID)
|
|
{
|
|
// v = ldc.i4(stateId)
|
|
// stloc(cachedState, v)
|
|
// stfld(StateMachine::<>1__state, ldloc(this), v)
|
|
stateID = 0;
|
|
if (index < 0)
|
|
return false;
|
|
ILVariable v;
|
|
ILExpression val;
|
|
if (!block[index].Match(ILCode.Stloc, out v, out val) || !val.Match(ILCode.Ldc_I4, out stateID))
|
|
return false;
|
|
ILExpression loadV;
|
|
if (!block[index + 1].MatchStloc(cachedStateVar, out loadV) || !loadV.MatchLdloc(v))
|
|
return false;
|
|
ILExpression target;
|
|
FieldReference fieldRef;
|
|
if (block[index + 2].Match(ILCode.Stfld, out fieldRef, out target, out loadV)) {
|
|
return fieldRef.ResolveWithinSameModule() == stateField
|
|
&& target.MatchThis()
|
|
&& loadV.MatchLdloc(v);
|
|
}
|
|
return false;
|
|
}
|
|
#endregion
|
|
|
|
#region AnalyzeStateMachine
|
|
ILVariable doFinallyBodies;
|
|
List<ILNode> 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, cachedStateVar);
|
|
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<ILNode> 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<ILNode> ConvertBody(List<ILNode> body, int startPos, int bodyLength, LabelRangeMapping mapping)
|
|
{
|
|
List<ILNode> newBody = new List<ILNode>();
|
|
// 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<ILTryCatchBlock.CatchBlock>(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<ILNode> ConvertFinally(List<ILNode> body)
|
|
{
|
|
List<ILNode> newBody = new List<ILNode>(body);
|
|
if (newBody.Count == 0)
|
|
return newBody;
|
|
ILLabel endFinallyLabel;
|
|
ILExpression ceqExpr;
|
|
if (newBody[0].Match(ILCode.Brtrue, out endFinallyLabel, out ceqExpr)) {
|
|
ILExpression condition;
|
|
if (MatchLogicNot(ceqExpr, out condition)) {
|
|
if (condition.MatchLdloc(doFinallyBodies)) {
|
|
newBody.RemoveAt(0);
|
|
} else if (condition.Code == ILCode.Clt && condition.Arguments[0].MatchLdloc(cachedStateVar) && condition.Arguments[1].MatchLdcI4(0)) {
|
|
newBody.RemoveAt(0);
|
|
}
|
|
}
|
|
}
|
|
return newBody;
|
|
}
|
|
|
|
bool MatchLogicNot(ILExpression expr, out ILExpression arg)
|
|
{
|
|
ILExpression loadZero;
|
|
object unused;
|
|
if (expr.Match(ILCode.Ceq, out unused, out arg, out loadZero)) {
|
|
int num;
|
|
return loadZero.Match(ILCode.Ldc_I4, out num) && num == 0;
|
|
}
|
|
return expr.Match(ILCode.LogicNot, out arg);
|
|
}
|
|
|
|
void HandleAwait(List<ILNode> 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();
|
|
string methodName = ((MethodReference)callAwaitUnsafeOnCompleted.Operand).Name;
|
|
if (methodName != "AwaitUnsafeOnCompleted" && methodName != "AwaitOnCompleted")
|
|
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))
|
|
newBody.RemoveAt(newBody.Count - 1); // remove awaiter field assignment
|
|
else if (MatchRoslynStateAssignment(newBody, newBody.Count - 3, out targetStateID))
|
|
newBody.RemoveRange(newBody.Count - 3, 3); // 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<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
|
|
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<ILNode> 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<ILNode> body, ref int pos)
|
|
{
|
|
// stloc(CS$0$0001, callvirt(class System.Threading.Tasks.Task`1<bool>::GetAwaiter, awaiterExpr)
|
|
// brtrue(IL_7C, call(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<bool>::get_IsCompleted, ldloca(CS$0$0001)))
|
|
// await(ldloca(CS$0$0001))
|
|
// ...
|
|
// IL_7C:
|
|
// arg_8B_0 = call(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<bool>::GetResult, ldloca(CS$0$0001))
|
|
// initobj(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<bool>, 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<bool>::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<bool>::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<bool>, ldloca(CS$0$0002_66))
|
|
// stfld('<AwaitInLoopCondition>d__d'::<>u__$awaitere, ldloc(this), ldloc(CS$0$0002_66))
|
|
// stfld('<AwaitInLoopCondition>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
|
|
}
|
|
}
|